stealth

package module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2024 License: MIT Imports: 2 Imported by: 0

README

Overview

This lib uses stealth-evasions to prevent the bot detection of websites.

The examples_test.go shows how to use it.

Documentation

Overview

Package stealth is a package for anti-bot-detection with rod

Index

Constants

View Source
const JS = `;(() => {

(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:"() => {\n  utils.preloadCache()\n}",stripProxyFromErrors:"(handler = {}) => {\n  const newHandler = {\n    setPrototypeOf: function (target, proto) {\n      if (proto === null)\n        throw new TypeError('Cannot convert object to primitive value')\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\n        throw new TypeError('Cyclic __proto__ value')\n      }\n      return Reflect.setPrototypeOf(target, proto)\n    }\n  }\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\n  const traps = Object.getOwnPropertyNames(handler)\n  traps.forEach(trap => {\n    newHandler[trap] = function () {\n      try {\n        // Forward the call to the defined proxy handler\n        return handler[trap].apply(this, arguments || [])\n      } catch (err) {\n        // Stack traces differ per browser, we only support chromium based ones currently\n        if (!err || !err.stack || !err.stack.includes(` + "`" + `at ` + "`" + `)) {\n          throw err\n        }\n\n        // When something throws within one of our traps the Proxy will show up in error stacks\n        // An earlier implementation of this code would simply strip lines with a blacklist,\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\n        // We try to use a known \"anchor\" line for that and strip it with everything above it.\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\n\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\n          const blacklist = [\n            ` + "`" + `at Reflect.${trap} ` + "`" + `, // e.g. Reflect.get or Reflect.apply\n            ` + "`" + `at Object.${trap} ` + "`" + `, // e.g. Object.get or Object.apply\n            ` + "`" + `at Object.newHandler.<computed> [as ${trap}] ` + "`" + ` // caused by this very wrapper :-)\n          ]\n          return (\n            err.stack\n              .split('\\n')\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\n              .filter((line, index) => !(index === 1 && stripFirstLine))\n              // Check if the line starts with one of our blacklisted strings\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\n              .join('\\n')\n          )\n        }\n\n        const stripWithAnchor = (stack, anchor) => {\n          const stackArr = stack.split('\\n')\n          anchor = anchor || ` + "`" + `at Object.newHandler.<computed> [as ${trap}] ` + "`" + ` // Known first Proxy line in chromium\n          const anchorIndex = stackArr.findIndex(line =>\n            line.trim().startsWith(anchor)\n          )\n          if (anchorIndex === -1) {\n            return false // 404, anchor not found\n          }\n          // Strip everything from the top until we reach the anchor line\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n          stackArr.splice(1, anchorIndex)\n          return stackArr.join('\\n')\n        }\n\n        // Special cases due to our nested toString proxies\n        err.stack = err.stack.replace(\n          'at Object.toString (',\n          'at Function.toString ('\n        )\n        if ((err.stack || '').includes('at Function.toString (')) {\n          err.stack = stripWithBlacklist(err.stack, false)\n          throw err\n        }\n\n        // Try using the anchor method, fallback to blacklist if necessary\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\n\n        throw err // Re-throw our now sanitized error\n      }\n    }\n  })\n  return newHandler\n}",stripErrorWithAnchor:"(err, anchor) => {\n  const stackArr = err.stack.split('\\n')\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\n  if (anchorIndex === -1) {\n    return err // 404, anchor not found\n  }\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + "`" + `TypeError` + "`" + `)\n  stackArr.splice(1, anchorIndex)\n  err.stack = stackArr.join('\\n')\n  return err\n}",replaceProperty:"(obj, propName, descriptorOverrides = {}) => {\n  return Object.defineProperty(obj, propName, {\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\n    // Add our overrides (e.g. value, get())\n    ...descriptorOverrides\n  })\n}",preloadCache:"() => {\n  if (utils.cache) {\n    return\n  }\n  utils.cache = {\n    // Used in our proxies\n    Reflect: {\n      get: Reflect.get.bind(Reflect),\n      apply: Reflect.apply.bind(Reflect)\n    },\n    // Used in ` + "`" + `makeNativeString` + "`" + `\n    nativeToStringStr: Function.toString + '' // => ` + "`" + `function toString() { [native code] }` + "`" + `\n  }\n}",makeNativeString:"(name = '') => {\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\n}",patchToString:"(obj, str = '') => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n      // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n      if (ctx === obj) {\n        // We either return the optional string verbatim or derive the most desired result automatically\n        return str || utils.makeNativeString(obj.name)\n      }\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",patchToStringNested:"(obj = {}) => {\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\n}",redirectToString:"(proxyObj, originalObj) => {\n  const handler = {\n    apply: function (target, ctx) {\n      // This fixes e.g. ` + "`" + `HTMLMediaElement.prototype.canPlayType.toString + \"\"` + "`" + `\n      if (ctx === Function.prototype.toString) {\n        return utils.makeNativeString('toString')\n      }\n\n      // ` + "`" + `toString` + "`" + ` targeted at our proxied Object detected\n      if (ctx === proxyObj) {\n        const fallback = () =>\n          originalObj && originalObj.name\n            ? utils.makeNativeString(originalObj.name)\n            : utils.makeNativeString(proxyObj.name)\n\n        // Return the toString representation of our original object if possible\n        return originalObj + '' || fallback()\n      }\n\n      if (typeof ctx === 'undefined' || ctx === null) {\n        return target.call(ctx)\n      }\n\n      // Check if the toString protype of the context is the same as the global prototype,\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + "`" + ` test case\n      const hasSameProto = Object.getPrototypeOf(\n        Function.prototype.toString\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\n      if (!hasSameProto) {\n        // Pass the call on to the local Function.prototype.toString instead\n        return ctx.toString()\n      }\n\n      return target.call(ctx)\n    }\n  }\n\n  const toStringProxy = new Proxy(\n    Function.prototype.toString,\n    utils.stripProxyFromErrors(handler)\n  )\n  utils.replaceProperty(Function.prototype, 'toString', {\n    value: toStringProxy\n  })\n}",replaceWithProxy:"(obj, propName, handler) => {\n  const originalObj = obj[propName]\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.redirectToString(proxyObj, originalObj)\n\n  return true\n}",replaceGetterWithProxy:"(obj, propName, handler) => {\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\n  const fnStr = fn.toString() // special getter function string\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { get: proxyObj })\n  utils.patchToString(proxyObj, fnStr)\n\n  return true\n}",replaceGetterSetter:"(obj, propName, handlerGetterSetter) => {\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\n  const handler = { ...ownPropertyDescriptor }\n\n  if (handlerGetterSetter.get !== undefined) {\n    const nativeFn = ownPropertyDescriptor.get\n    handler.get = function() {\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.get, nativeFn)\n  }\n\n  if (handlerGetterSetter.set !== undefined) {\n    const nativeFn = ownPropertyDescriptor.set\n    handler.set = function(newValue) {\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\n    }\n    utils.redirectToString(handler.set, nativeFn)\n  }\n\n  Object.defineProperty(obj, propName, handler)\n}",mockWithProxy:"(obj, propName, pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n\n  utils.replaceProperty(obj, propName, { value: proxyObj })\n  utils.patchToString(proxyObj)\n\n  return true\n}",createProxy:"(pseudoTarget, handler) => {\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\n  utils.patchToString(proxyObj)\n\n  return proxyObj\n}",splitObjPath:"objPath => ({\n  // Remove last dot entry (property) ==> ` + "`" + `HTMLMediaElement.prototype` + "`" + `\n  objName: objPath.split('.').slice(0, -1).join('.'),\n  // Extract last dot entry ==> ` + "`" + `canPlayType` + "`" + `\n  propName: objPath.split('.').slice(-1)[0]\n})",replaceObjPathWithProxy:"(objPath, handler) => {\n  const { objName, propName } = utils.splitObjPath(objPath)\n  const obj = eval(objName) // eslint-disable-line no-eval\n  return utils.replaceWithProxy(obj, propName, handler)\n}",execRecursively:"(obj = {}, typeFilter = [], fn) => {\n  function recurse(obj) {\n    for (const key in obj) {\n      if (obj[key] === undefined) {\n        continue\n      }\n      if (obj[key] && typeof obj[key] === 'object') {\n        recurse(obj[key])\n      } else {\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\n          fn.call(this, obj[key])\n        }\n      }\n    }\n  }\n  recurse(obj)\n  return obj\n}",stringifyFns:"(fnObj = { hello: () => 'world' }) => {\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\n  // https://github.com/feross/fromentries\n  function fromEntries(iterable) {\n    return [...iterable].reduce((obj, [key, val]) => {\n      obj[key] = val\n      return obj\n    }, {})\n  }\n  return (Object.fromEntries || fromEntries)(\n    Object.entries(fnObj)\n      .filter(([key, value]) => typeof value === 'function')\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\n  )\n}",materializeFns:"(fnStrObj = { hello: \"() => 'world'\" }) => {\n  return Object.fromEntries(\n    Object.entries(fnStrObj).map(([key, value]) => {\n      if (value.startsWith('function')) {\n        // some trickery is needed to make oldschool functions work :-)\n        return [key, eval(` + "`" + `() => ${value}` + "`" + `)()] // eslint-disable-line no-eval\n      } else {\n        // arrow functions just work\n        return [key, eval(value)] // eslint-disable-line no-eval\n      }\n    })\n  )\n}",makeHandler:"() => ({\n  // Used by simple ` + "`" + `navigator` + "`" + ` getter evasions\n  getterValue: value => ({\n    apply(target, ctx, args) {\n      // Let's fetch the value first, to trigger and escalate potential errors\n      // Illegal invocations like ` + "`" + `navigator.__proto__.vendor` + "`" + ` will throw here\n      utils.cache.Reflect.apply(...arguments)\n      return value\n    }\n  })\n})",arrayEquals:"(array1, array2) => {\n  if (array1.length !== array2.length) {\n    return false\n  }\n  for (let i = 0; i < array1.length; ++i) {\n    if (array1[i] !== array2[i]) {\n      return false\n    }\n  }\n  return true\n}",memoize:"fn => {\n  const cache = []\n  return function(...args) {\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\n      cache.push({ key: args, value: fn.apply(this, args) })\n    }\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\n  }\n}"},_mainFunction:"(utils, opts) => {\n      const getParameterProxyHandler = {\n        apply: function(target, ctx, args) {\n          const param = (args || [])[0]\n          const result = utils.cache.Reflect.apply(target, ctx, args)\n          // UNMASKED_VENDOR_WEBGL\n          if (param === 37445) {\n            return opts.vendor || 'Intel Inc.' // default in headless: Google Inc.\n          }\n          // UNMASKED_RENDERER_WEBGL\n          if (param === 37446) {\n            return opts.renderer || 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShader\n          }\n          return result\n        }\n      }\n\n      // There's more than one WebGL rendering context\n      // https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibility\n      // To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter)\n      const addProxy = (obj, propName) => {\n        utils.replaceWithProxy(obj, propName, getParameterProxyHandler)\n      }\n      // For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing:\n      addProxy(WebGLRenderingContext.prototype, 'getParameter')\n      addProxy(WebGL2RenderingContext.prototype, 'getParameter')\n    }",_args:[{}]});
})();`

JS for stealth

View Source
const JSVersion = "v<no value>"

JSVersion for stealth

Variables

This section is empty.

Functions

func MustPage

func MustPage(b *rod.Browser) *rod.Page

MustPage creates a stealth page that can't be detected as bot.

func Page

func Page(b *rod.Browser) (*rod.Page, error)

Page creates a stealth page that can't be detected as bot.

Types

This section is empty.

Directories

Path Synopsis
Package main ...
Package main ...

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL