datadome, cloudflare, google and akamai on suicide watch
This commit is contained in:
298
README.md
Normal file
298
README.md
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
# 4play
|
||||||
|
4play is a Firefox extension that lets you bot shit.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Does not expose any `webdriver.*` variables, it's just an extension
|
||||||
|
- Environment is undetectable. Inject JavaScript through JS isolated worlds.
|
||||||
|
- Simple as fuck
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
4play makes Firefox connects to a websocket server in the background. The server then sends commands to the browser. You can clean up containers, navigate to pages, inject javascript or extract raw page content & request headers.
|
||||||
|
|
||||||
|
# Fucking why?
|
||||||
|
It's a complete replacement for dogshit libraries like Puppeteer/Selenium/Playwright, which are developed by Google engineers. These libraries purposely leak bot signals even with the usage of stealth scripts. With this library, a single Firefox instance can use multiple proxies at the same time across multiple containers and completely avoid detection. Fuck your cat & mouse game, im tired of your shit.
|
||||||
|
|
||||||
|
I'm so fucking tired of retards recommending libraries that are so easily detectable. Just give me something that works you fucks.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Install the 4play extension on a **CLEAN** Firefox install. Do **NOT** use your main profile, it will mess up your tabs and containers. Enter credentials in the extension and connect.
|
||||||
|
|
||||||
|
Then, start the server:
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
npm install http ws
|
||||||
|
node hello-world.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example server script
|
||||||
|
|
||||||
|
```js
|
||||||
|
const fplay = require("./fplay.js");
|
||||||
|
|
||||||
|
var port = 3030;
|
||||||
|
var timeout = 30000;
|
||||||
|
|
||||||
|
fplay.event.on("server_ready", function(){
|
||||||
|
|
||||||
|
console.log("listening on port " + port + " (timeout=" + timeout + ")");
|
||||||
|
});
|
||||||
|
|
||||||
|
fplay.event.on("browser_connect", async function(ws){
|
||||||
|
|
||||||
|
const ua = await fplay.get_ua(ws);
|
||||||
|
console.log("Connection from " + ua);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
const blanktab = await fplay.close_all_tabs(ws);
|
||||||
|
await fplay.delete_all_containers(ws);
|
||||||
|
|
||||||
|
// create container
|
||||||
|
const container = await fplay.container_create(ws);
|
||||||
|
console.log(container);
|
||||||
|
|
||||||
|
// assign proxy
|
||||||
|
await fplay.container_attach_proxy(
|
||||||
|
ws,
|
||||||
|
container,
|
||||||
|
{
|
||||||
|
type: "socks", // socks(is socks5) http, https, socks4
|
||||||
|
host: "whatever-proxy-host-you-want.io",
|
||||||
|
port: 1339,
|
||||||
|
username: "admin",
|
||||||
|
password: "1234",
|
||||||
|
proxyDNS: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// open tab
|
||||||
|
const newtab = await fplay.tab_open(ws, "https://lolcat.ca", true, container);
|
||||||
|
console.log(newtab);
|
||||||
|
|
||||||
|
// get page's title
|
||||||
|
var result = await fplay.tab_inject_js(ws, newtab, "document.title", true);
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
fplay.init(port, timeout);
|
||||||
|
```
|
||||||
|
|
||||||
|
# 4play API Reference
|
||||||
|
|
||||||
|
## Initialization
|
||||||
|
|
||||||
|
### `fplay.init(port?, command_timeout?)`
|
||||||
|
|
||||||
|
Starts the HTTP/WebSocket server.
|
||||||
|
|
||||||
|
- `port` (`number`, default: `3030`) - Port to listen on.
|
||||||
|
- `password` (`string`, default: `cnc`) - Websocket path (acts as password)
|
||||||
|
- `command_timeout` (`number`, default: `5000`) - Timeout in ms for commands sent to the browser.
|
||||||
|
|
||||||
|
**Returns:** `void`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
|
### `fplay.get_ua(ws)`
|
||||||
|
|
||||||
|
Gets the browser's user agent string.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
|
||||||
|
**Returns:** `string` on success, `false` on failure.
|
||||||
|
|
||||||
|
### `fplay.wait_random(min, max)`
|
||||||
|
|
||||||
|
Waits for an inclusive amount of time in miliseconds.
|
||||||
|
|
||||||
|
**Returns:** `boolean` `true`.
|
||||||
|
|
||||||
|
### `fplay.parse_cookies(headers)`
|
||||||
|
|
||||||
|
Extracts a `key => value` cookie pair from raw headers.
|
||||||
|
|
||||||
|
- `headers` (`Array`) - List of header strings
|
||||||
|
|
||||||
|
**Returns:** `Array: key => value`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tabs
|
||||||
|
|
||||||
|
### `fplay.get_tab_list(ws)`
|
||||||
|
|
||||||
|
Gets a list of all open tabs.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
|
||||||
|
**Returns:** `Array<object>` on success (each object contains `id`, `index`, `status`, `active`, `title`, `url`, `container`), `false` on failure.
|
||||||
|
|
||||||
|
### `fplay.tab_open(ws, url, await_dom_ready?, container?)`
|
||||||
|
|
||||||
|
Opens a new tab.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `url` (`string`) - URL to open.
|
||||||
|
- `await_dom_ready` (`boolean`, default: `false`) - If `true`, waits for the page to fully load before resolving.
|
||||||
|
- `container` (`object | string | null`, default: `null`) - Container to open the tab in. Accepts a container object (with `.id`) or a raw container object.
|
||||||
|
|
||||||
|
**Returns:** `object` (tab data: `id`, `index`, `status`, `active`, `title`, `url`, `container`) on success, `false` on failure.
|
||||||
|
|
||||||
|
### `fplay.tab_close(ws, tab_ids)`
|
||||||
|
|
||||||
|
Closes one or more tabs.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `tab_ids` (`number | number[] | object`) - Tab ID, array of tab IDs, or a tab object (with `.id`).
|
||||||
|
|
||||||
|
**Returns:** `number` (count of closed tabs) on success, `false` on failure.
|
||||||
|
|
||||||
|
### `fplay.close_all_tabs(ws)`
|
||||||
|
|
||||||
|
Closes all tabs except a newly opened blank tab.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
|
||||||
|
**Returns:** `object` - The new blank tab's data.
|
||||||
|
|
||||||
|
### `fplay.tab_focus(ws, tabid)`
|
||||||
|
|
||||||
|
Focuses (activates) a tab.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `tabid` (`number | object`) - Tab ID or tab object (with `.id`).
|
||||||
|
|
||||||
|
**Returns:** `boolean` - `true` if focused successfully, `false` otherwise.
|
||||||
|
|
||||||
|
### `fplay.tab_exists(ws, tabid)`
|
||||||
|
|
||||||
|
Checks whether a tab exists.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `tabid` (`number | object`) - Tab ID or tab object (with `.id`).
|
||||||
|
|
||||||
|
**Returns:** `boolean` - `true` if the tab exists, `false` otherwise.
|
||||||
|
|
||||||
|
### `fplay.tab_inject_js(ws, tabid, js, isolated?)`
|
||||||
|
|
||||||
|
Injects and executes JavaScript in a tab.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `tabid` (`number | object`) - Tab ID or tab object (with `.id`).
|
||||||
|
- `js` (`string`) - JavaScript code to execute.
|
||||||
|
- `isolated` (`boolean`, default: `false`) - If `true`, runs in an isolated world. See MDN for more info.
|
||||||
|
|
||||||
|
**Returns:** `object` - `{ status: true, result: any }` on success, `{ status: string, result: null }` on error (status contains the error message), `{ status: false, result: null }` on failure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Containers
|
||||||
|
|
||||||
|
### `fplay.container_create(ws, name?)`
|
||||||
|
|
||||||
|
Creates a new container with a random color and icon.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `name` (`string | null`, default: `null`) - Container name. If `null`, auto-generates a name (`sesh1`, `sesh2`, etc.).
|
||||||
|
|
||||||
|
**Returns:** `object` (`id`, `name`, `color`, `icon`, `proxy`) on success, `false` on failure.
|
||||||
|
|
||||||
|
### `fplay.get_container_list(ws)`
|
||||||
|
|
||||||
|
Gets a list of all containers.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
|
||||||
|
**Returns:** `Array<object>` on success (each object contains `id`, `name`, `icon`, `color`, `proxy`), `false` on failure.
|
||||||
|
|
||||||
|
### `fplay.container_delete(ws, id)`
|
||||||
|
|
||||||
|
Deletes one or more containers.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `id` (`string | string[] | object`) - Container ID, array of container IDs, or a container object (with `.id`).
|
||||||
|
|
||||||
|
**Returns:** `number` (count of deleted containers) on success, `false` on failure.
|
||||||
|
|
||||||
|
### `fplay.container_exists(ws, id)`
|
||||||
|
|
||||||
|
Checks whether a container exists.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `id` (`string | object`) - Container ID or container object (with `.id`).
|
||||||
|
|
||||||
|
**Returns:** `boolean` - `true` if the container exists, `false` otherwise.
|
||||||
|
|
||||||
|
### `fplay.delete_all_containers(ws)`
|
||||||
|
|
||||||
|
Deletes all containers.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
|
||||||
|
**Returns:** `number` - Count of deleted containers.
|
||||||
|
|
||||||
|
### `fplay.container_attach_proxy(ws, id, proxy)`
|
||||||
|
|
||||||
|
Attaches a proxy configuration to a container.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `id` (`string | object`) - Container ID or container object (with `.id`).
|
||||||
|
- `proxy` (`object`) - Proxy configuration object:
|
||||||
|
- `type` (`string`) - `"socks"`, `"socks4"`, `"http"`, or `"https"`
|
||||||
|
- `host` (`string`) - Proxy host
|
||||||
|
- `port` (`number`) - Proxy port
|
||||||
|
- `username` (`string`, optional) - Proxy username
|
||||||
|
- `password` (`string`, optional) - Proxy password
|
||||||
|
- `proxyDNS` (`boolean`, optional) - Whether to proxy DNS lookups
|
||||||
|
|
||||||
|
**Returns:** `boolean` - `true` if attached successfully, `false` otherwise.
|
||||||
|
|
||||||
|
### `fplay.container_detach_proxy(ws, id)`
|
||||||
|
|
||||||
|
Removes the proxy from a container, reverting to direct connection.
|
||||||
|
|
||||||
|
- `ws` (`WebSocket`) - Active browser connection.
|
||||||
|
- `id` (`string | object`) - Container ID or container object (with `.id`).
|
||||||
|
|
||||||
|
**Returns:** `boolean` - `true` if detached successfully, `false` otherwise.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
### `fplay.wait_for_dom_ready(tabid)`
|
||||||
|
|
||||||
|
Returns a promise that resolves when a tab's DOM finishes loading.
|
||||||
|
|
||||||
|
- `tabid` (`number`) - Tab ID to wait on.
|
||||||
|
|
||||||
|
**Returns:** `Promise<object>` - Resolves with tab data on success, `false` on load failure.
|
||||||
|
|
||||||
|
### `fplay.event`
|
||||||
|
|
||||||
|
An `EventEmitter` instance that emits the following events:
|
||||||
|
|
||||||
|
| Event | Data | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `server_ready` | `{}` | Server started listening |
|
||||||
|
| `browser_connect` | `ws` | Browser connected |
|
||||||
|
| `browser_disconnect` | `{}` | Browser disconnected |
|
||||||
|
| `dom_ready` | `{ id, index, status, active, title, url, container }` | A tab finished loading |
|
||||||
|
| `dom_load_fail` | `{ id }` | A tab failed to load |
|
||||||
|
| `web_request` | `{ id, url, status, origin, type, method, container, headers }` | A main-frame request was sent |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `fplay.browser_connected` | `boolean` | Whether the browser extension is currently connected |
|
||||||
|
| `fplay.PORT` | `number` | Current server port |
|
||||||
|
| `fplay.COMMAND_TIMEOUT` | `number` | Current command timeout in ms |
|
||||||
|
| `fplay.PASSWORD` | `string` | Server password |
|
||||||
|
|
||||||
|
# License
|
||||||
|
AGPLv3
|
||||||
559
ext/bg.js
Normal file
559
ext/bg.js
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
|
||||||
|
const STATUS_CONNECTING = 0;
|
||||||
|
const STATUS_OFFLINE = 1;
|
||||||
|
const STATUS_CONNECTED = 2;
|
||||||
|
|
||||||
|
var conn_attempts = 0;
|
||||||
|
|
||||||
|
var connected = false;
|
||||||
|
var timeoutId;
|
||||||
|
var global_ws = null;
|
||||||
|
|
||||||
|
var container_count = 0;
|
||||||
|
|
||||||
|
var proxy_map = {};
|
||||||
|
|
||||||
|
browser.browserAction.setBadgeBackgroundColor({
|
||||||
|
color: [0, 0, 0, 0]
|
||||||
|
});
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
async function get_tabs(){
|
||||||
|
|
||||||
|
var tabs = await browser.tabs.query({});
|
||||||
|
|
||||||
|
return tabs.map(tab => ({
|
||||||
|
id: tab.id,
|
||||||
|
index: tab.index,
|
||||||
|
status: tab.status,
|
||||||
|
active: tab.active,
|
||||||
|
title: tab.title,
|
||||||
|
url: tab.url,
|
||||||
|
container: tab.cookieStoreId
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tab_exists(id){
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
await browser.tabs.get(id);
|
||||||
|
return true;
|
||||||
|
}catch{
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get_container_list(){
|
||||||
|
|
||||||
|
const containers = await browser.contextualIdentities.query({});
|
||||||
|
|
||||||
|
var list = [];
|
||||||
|
for(var i=0; i<containers.length; i++){
|
||||||
|
|
||||||
|
list.push({
|
||||||
|
id: containers[i].cookieStoreId,
|
||||||
|
name: containers[i].name,
|
||||||
|
icon: containers[i].icon,
|
||||||
|
color: containers[i].color,
|
||||||
|
proxy: proxy_map[containers[i].cookieStoreId]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function container_exists(id){
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
await browser.contextualIdentities.get(id);
|
||||||
|
return true;
|
||||||
|
}catch{
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(ws, seqid, msg = {}){
|
||||||
|
|
||||||
|
msg.seqid = seqid;
|
||||||
|
var msg = JSON.stringify(msg);
|
||||||
|
console.log("-> " + msg);
|
||||||
|
ws.send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function send_event(ws, msg = {}){
|
||||||
|
|
||||||
|
var msg = JSON.stringify(msg);
|
||||||
|
console.log("-> " + msg);
|
||||||
|
ws.send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function set_status(status){
|
||||||
|
|
||||||
|
switch(status){
|
||||||
|
case STATUS_CONNECTING:
|
||||||
|
conn_attempts++;
|
||||||
|
|
||||||
|
browser.browserAction.setBadgeText({text: "🔵"});
|
||||||
|
await browser.storage.local.set({"status": STATUS_CONNECTING, "attempts": conn_attempts});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATUS_CONNECTED:
|
||||||
|
conn_attempts = 0;
|
||||||
|
|
||||||
|
browser.browserAction.setBadgeText({text: "🟢"});
|
||||||
|
await browser.storage.local.set({"status": STATUS_CONNECTED, "attempts": 0});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATUS_OFFLINE:
|
||||||
|
browser.browserAction.setBadgeText({text: "🔴"});
|
||||||
|
await browser.storage.local.set({"status": STATUS_OFFLINE});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ws_connect_timeout(url, timeoutMs = 5000){
|
||||||
|
|
||||||
|
timeoutMs = parseInt(timeoutMs);
|
||||||
|
|
||||||
|
return new Promise(function(resolve, reject){
|
||||||
|
|
||||||
|
var ws = null;
|
||||||
|
global_ws = null;
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
ws = new WebSocket(url);
|
||||||
|
|
||||||
|
}catch(error){
|
||||||
|
|
||||||
|
setTimeout(async function(){
|
||||||
|
|
||||||
|
ws_init();
|
||||||
|
reject(new Error("Bad URL"));
|
||||||
|
}, timeoutMs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attach_ws_events(ws);
|
||||||
|
/*
|
||||||
|
ws.addEventListener("open", function(){console.log("open!");});
|
||||||
|
ws.addEventListener("message", function(){console.log("message!");});
|
||||||
|
ws.addEventListener("close", function(){console.log("close!");});*/
|
||||||
|
|
||||||
|
connected = false;
|
||||||
|
|
||||||
|
// Set up the timeout
|
||||||
|
timeoutId = setTimeout(function(){
|
||||||
|
if(!connected){
|
||||||
|
|
||||||
|
ws.close();
|
||||||
|
reject(new Error("Timeout"));
|
||||||
|
}
|
||||||
|
}, timeoutMs);
|
||||||
|
|
||||||
|
ws.addEventListener("open", function(){
|
||||||
|
connected = true;
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
resolve(ws);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener("close", function(){
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
setTimeout(async function(){
|
||||||
|
|
||||||
|
ws_init();
|
||||||
|
reject(new Error("Close"));
|
||||||
|
}, timeoutMs);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ws_init(){
|
||||||
|
|
||||||
|
await set_status(STATUS_CONNECTING);
|
||||||
|
var config = await browser.storage.local.get();
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
global_ws = await ws_connect_timeout(config.ws_url, config.ws_timeout);
|
||||||
|
}catch(error){
|
||||||
|
|
||||||
|
console.log("ws: " + error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// online
|
||||||
|
}
|
||||||
|
|
||||||
|
function attach_ws_events(ws){
|
||||||
|
|
||||||
|
ws.addEventListener("open", async function(){
|
||||||
|
|
||||||
|
console.log("ws: connected");
|
||||||
|
await set_status(STATUS_CONNECTED);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener("message", async function(e){
|
||||||
|
|
||||||
|
console.log("<- " + e.data);
|
||||||
|
|
||||||
|
var msg = JSON.parse(e.data);
|
||||||
|
var seqid = msg.seqid;
|
||||||
|
|
||||||
|
switch(msg.action){
|
||||||
|
|
||||||
|
//
|
||||||
|
// Misc
|
||||||
|
//
|
||||||
|
case "get_ua":
|
||||||
|
send(ws, seqid, {
|
||||||
|
"ua": navigator.userAgent
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Tabs
|
||||||
|
//
|
||||||
|
case "get_tabs":
|
||||||
|
send(ws, seqid, {
|
||||||
|
"tabs": await get_tabs()
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_open":
|
||||||
|
var tab =
|
||||||
|
await browser.tabs.create({
|
||||||
|
url: msg.url,
|
||||||
|
...(typeof msg.container == "string" && { cookieStoreId: msg.container })
|
||||||
|
});
|
||||||
|
|
||||||
|
// immediately return even if its not loaded yet
|
||||||
|
send(ws, seqid, {
|
||||||
|
data: {
|
||||||
|
id: tab.id,
|
||||||
|
index: tab.index,
|
||||||
|
status: tab.status,
|
||||||
|
active: tab.active,
|
||||||
|
title: tab.title,
|
||||||
|
url: tab.url,
|
||||||
|
container: tab.cookieStoreId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_close":
|
||||||
|
var closed_tabs = 0;
|
||||||
|
|
||||||
|
switch(typeof msg.tabid){
|
||||||
|
|
||||||
|
case "number":
|
||||||
|
var exists = await tab_exists(msg.tabid);
|
||||||
|
|
||||||
|
if(exists){
|
||||||
|
await browser.tabs.remove(msg.tabid);
|
||||||
|
closed_tabs = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "object":
|
||||||
|
var exists = false;
|
||||||
|
|
||||||
|
for(var i=0; i<msg.tabid.length; i++){
|
||||||
|
|
||||||
|
exists = await tab_exists(msg.tabid[i]);
|
||||||
|
|
||||||
|
if(exists){
|
||||||
|
await browser.tabs.remove(msg.tabid[i]);
|
||||||
|
closed_tabs++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(ws, seqid, {"closed_tab_count": closed_tabs});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_focus":
|
||||||
|
var exists = await tab_exists(msg.tabid);
|
||||||
|
|
||||||
|
if(exists){
|
||||||
|
|
||||||
|
await browser.tabs.update(msg.tabid, { active: true });
|
||||||
|
send(ws, seqid, {"status": true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(ws, seqid, {"status": false});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_exists":
|
||||||
|
var exists = await tab_exists(msg.tabid);
|
||||||
|
send(ws, seqid, {"exists": exists});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tab_inject_js":
|
||||||
|
try{
|
||||||
|
|
||||||
|
var result = await browser.scripting.executeScript({
|
||||||
|
target: { tabId: msg.tabid },
|
||||||
|
func: (code) => eval(code),
|
||||||
|
args: [msg.js],
|
||||||
|
...(msg.isolated === true && { world: "ISOLATED" })
|
||||||
|
});
|
||||||
|
|
||||||
|
send(ws, seqid, {"status": true, "result": result});
|
||||||
|
}catch(err){
|
||||||
|
|
||||||
|
send(ws, seqid, {"status": err.name + ": " + err.message});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Containers
|
||||||
|
//
|
||||||
|
case "get_container_list":
|
||||||
|
var containers = await get_container_list();
|
||||||
|
|
||||||
|
send(ws, seqid, {
|
||||||
|
"containers": containers
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "container_create":
|
||||||
|
|
||||||
|
// generate random container attributes
|
||||||
|
container_count++;
|
||||||
|
|
||||||
|
var name = null;
|
||||||
|
|
||||||
|
if(typeof msg.name != "undefined"){
|
||||||
|
|
||||||
|
name = msg.name;
|
||||||
|
}else{
|
||||||
|
|
||||||
|
name = "sesh" + container_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = [
|
||||||
|
"blue",
|
||||||
|
"turquoise",
|
||||||
|
"green",
|
||||||
|
"yellow",
|
||||||
|
"orange",
|
||||||
|
"red",
|
||||||
|
"pink",
|
||||||
|
"purple"
|
||||||
|
][Math.floor(Math.random() * 8)];
|
||||||
|
|
||||||
|
const icon = [
|
||||||
|
"fingerprint",
|
||||||
|
"briefcase",
|
||||||
|
"dollar",
|
||||||
|
"cart",
|
||||||
|
"circle",
|
||||||
|
"gift",
|
||||||
|
"vacation",
|
||||||
|
"food",
|
||||||
|
"fruit",
|
||||||
|
"pet",
|
||||||
|
"tree",
|
||||||
|
"chill",
|
||||||
|
"fence"
|
||||||
|
][Math.floor(Math.random() * 13)];
|
||||||
|
|
||||||
|
const container = await browser.contextualIdentities.create({
|
||||||
|
name: name,
|
||||||
|
color: color,
|
||||||
|
icon: icon
|
||||||
|
});
|
||||||
|
|
||||||
|
proxy_map[container.cookieStoreId] = {
|
||||||
|
type: "direct"
|
||||||
|
};
|
||||||
|
|
||||||
|
send(ws, seqid, {
|
||||||
|
id: container.cookieStoreId,
|
||||||
|
name: name,
|
||||||
|
color: color,
|
||||||
|
icon: icon,
|
||||||
|
proxy: proxy_map[container.cookieStoreId]
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "container_exists":
|
||||||
|
var exists = await container_exists(msg.id);
|
||||||
|
send(ws, seqid, {"exists": exists});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "container_delete":
|
||||||
|
var deleted_containers = 0;
|
||||||
|
|
||||||
|
switch(typeof msg.id){
|
||||||
|
|
||||||
|
case "number":
|
||||||
|
var exists = await container_exists(msg.id);
|
||||||
|
|
||||||
|
if(exists){
|
||||||
|
await browser.contextualIdentities.remove(msg.id);
|
||||||
|
delete(proxy_map[msg.id]);
|
||||||
|
deleted_containers = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "object":
|
||||||
|
var exists = false;
|
||||||
|
|
||||||
|
for(var i=0; i<msg.id.length; i++){
|
||||||
|
|
||||||
|
exists = await container_exists(msg.id[i]);
|
||||||
|
|
||||||
|
if(exists){
|
||||||
|
await browser.contextualIdentities.remove(msg.id[i]);
|
||||||
|
delete(proxy_map[msg.id[i]]);
|
||||||
|
deleted_containers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(ws, seqid, {"closed_container_count": deleted_containers});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "container_attach_proxy":
|
||||||
|
var status = await container_exists(msg.id);
|
||||||
|
|
||||||
|
if(status){
|
||||||
|
|
||||||
|
proxy_map[msg.id] = msg.proxy;
|
||||||
|
send(ws, seqid, {status: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(ws, seqid, {status: false});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "container_detach_proxy":
|
||||||
|
var status = await container_exists(msg.id);
|
||||||
|
|
||||||
|
if(status){
|
||||||
|
|
||||||
|
proxy_map[msg.id] = {type: "direct"};
|
||||||
|
send(ws, seqid, {status: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(ws, seqid, {status: false});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log(`ws: unhandled message "${msg.action}"`, msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener("close", async function(e){
|
||||||
|
|
||||||
|
console.log("ws: offline");
|
||||||
|
await set_status(STATUS_OFFLINE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Page events
|
||||||
|
//
|
||||||
|
browser.webRequest.onSendHeaders.addListener(
|
||||||
|
function(details){
|
||||||
|
|
||||||
|
var headers = [];
|
||||||
|
|
||||||
|
for(const header of details.requestHeaders){
|
||||||
|
|
||||||
|
headers.push(header.name + ": " + header.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
send_event(
|
||||||
|
global_ws,
|
||||||
|
{
|
||||||
|
"action": "web_request",
|
||||||
|
"data": {
|
||||||
|
id: details.tabId,
|
||||||
|
url: details.url,
|
||||||
|
status: details.statusCode,
|
||||||
|
origin: details.originUrl,
|
||||||
|
type: details.type,
|
||||||
|
method: details.method,
|
||||||
|
container: details.cookieStoreId,
|
||||||
|
headers: headers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{urls: ["<all_urls>"], types: ["main_frame"]},
|
||||||
|
["requestHeaders"]
|
||||||
|
);
|
||||||
|
|
||||||
|
browser.proxy.onRequest.addListener(function(request){
|
||||||
|
|
||||||
|
const proxy_config = proxy_map[request.cookieStoreId];
|
||||||
|
if(proxy_config){
|
||||||
|
|
||||||
|
return proxy_config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback, should not happen
|
||||||
|
return {
|
||||||
|
type: "direct"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urls: ["<all_urls>"]
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.tabs.onUpdated.addListener(function(tabid, event, tab){
|
||||||
|
|
||||||
|
if(connected === false){ return; }
|
||||||
|
|
||||||
|
if(event.status === "complete"){
|
||||||
|
|
||||||
|
send_event(
|
||||||
|
global_ws,
|
||||||
|
{
|
||||||
|
"action": "dom_ready",
|
||||||
|
"data": {
|
||||||
|
id: tab.id,
|
||||||
|
index: tab.index,
|
||||||
|
status: tab.status,
|
||||||
|
active: tab.active,
|
||||||
|
title: tab.title,
|
||||||
|
url: tab.url,
|
||||||
|
container: tab.cookieStoreId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.webNavigation.onErrorOccurred.addListener(function(page){
|
||||||
|
|
||||||
|
if(connected === false){ return; }
|
||||||
|
|
||||||
|
send_event(
|
||||||
|
global_ws,
|
||||||
|
{
|
||||||
|
"action": "dom_load_fail",
|
||||||
|
"data": {
|
||||||
|
id: page.tabId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
(async function(){
|
||||||
|
await ws_init();
|
||||||
|
})();
|
||||||
BIN
ext/icon.png
Normal file
BIN
ext/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 400 B |
BIN
ext/icons/connected.png
Executable file
BIN
ext/icons/connected.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 969 B |
BIN
ext/icons/connecting.png
Normal file
BIN
ext/icons/connecting.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 918 B |
BIN
ext/icons/offline.png
Executable file
BIN
ext/icons/offline.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 895 B |
30
ext/manifest.json
Normal file
30
ext/manifest.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "4play",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "4play & dominate",
|
||||||
|
"icons": {
|
||||||
|
"48": "icon.png"
|
||||||
|
},
|
||||||
|
"browser_action": {
|
||||||
|
"default_icon": "icon.png",
|
||||||
|
"default_title": "4play",
|
||||||
|
"default_popup": "popup.html"
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"tabs",
|
||||||
|
"contextualIdentities",
|
||||||
|
"cookies",
|
||||||
|
"proxy",
|
||||||
|
"webNavigation",
|
||||||
|
"webRequest",
|
||||||
|
"webRequestBlocking",
|
||||||
|
"scripting",
|
||||||
|
"<all_urls>",
|
||||||
|
"activeTab",
|
||||||
|
"storage"
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["bg.js"]
|
||||||
|
}
|
||||||
|
}
|
||||||
80
ext/popup-style.css
Normal file
80
ext/popup-style.css
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
body{
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
font-family:sans-serif;
|
||||||
|
font-size:14px;
|
||||||
|
color:#bdae93;
|
||||||
|
background:#282828;
|
||||||
|
min-width:350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header{
|
||||||
|
padding:4px 10px;
|
||||||
|
font-weight:bold;
|
||||||
|
font-size:24px;
|
||||||
|
border-bottom:1px solid #282828;
|
||||||
|
background:#3c3836;
|
||||||
|
overflow:hidden;
|
||||||
|
line-height:32px;
|
||||||
|
font-family:monospace;
|
||||||
|
color:#d5c4a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header img{
|
||||||
|
float:left;
|
||||||
|
margin-right:7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content{
|
||||||
|
margin:10px 10px 27px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size:18px;
|
||||||
|
font-weight:bold;
|
||||||
|
display:block;
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title::before{
|
||||||
|
content:"#";
|
||||||
|
color:#a89984;
|
||||||
|
}
|
||||||
|
|
||||||
|
label{
|
||||||
|
display:block;
|
||||||
|
color:#a89984;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-wrapper{
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input{
|
||||||
|
all:unset;
|
||||||
|
display:block;
|
||||||
|
width:100%;
|
||||||
|
background:#1d2021;
|
||||||
|
border:1px solid #504545;
|
||||||
|
padding:4px 7px;
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus{
|
||||||
|
border-color:#928374;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status{
|
||||||
|
font-size:14px;
|
||||||
|
overflow:hidden;
|
||||||
|
line-height:18px;
|
||||||
|
padding:4px 10px;
|
||||||
|
border-bottom:1px solid #504545;
|
||||||
|
background:#1d2021;
|
||||||
|
font-family:monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status img{
|
||||||
|
float:left;
|
||||||
|
margin-right:4px;
|
||||||
|
}
|
||||||
27
ext/popup.html
Normal file
27
ext/popup.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="popup-style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<img src="icon.png">
|
||||||
|
4play
|
||||||
|
</div>
|
||||||
|
<div id="status"></div>
|
||||||
|
<form class="content">
|
||||||
|
|
||||||
|
<div class="option-wrapper">
|
||||||
|
<label for="ws_url">C&C server</label>
|
||||||
|
<input type="text" name="ws_url" placeholder="Eg: ws://localhost:3030/cnc">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="option-wrapper">
|
||||||
|
<label for="ws_url">Connect timeout (milliseconds)</label>
|
||||||
|
<input type="text" name="ws_timeout" placeholder="Eg: 5000">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
89
ext/popup.js
Normal file
89
ext/popup.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
|
||||||
|
// this code triggers on popup load
|
||||||
|
|
||||||
|
const STATUS_CONNECTING = 0;
|
||||||
|
const STATUS_OFFLINE = 1;
|
||||||
|
const STATUS_CONNECTED = 2;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async function(){
|
||||||
|
|
||||||
|
// generate initial config
|
||||||
|
var config = await browser.storage.local.get();
|
||||||
|
|
||||||
|
if(typeof config.ws_url == "undefined"){
|
||||||
|
|
||||||
|
await browser.storage.local.set({"ws_url": "ws://localhost:3030/cnc"});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof config.ws_timeout == "undefined"){
|
||||||
|
|
||||||
|
await browser.storage.local.set({"ws_timeout": 5000});
|
||||||
|
}
|
||||||
|
|
||||||
|
// update form
|
||||||
|
config = await browser.storage.local.get(); // refresh first
|
||||||
|
|
||||||
|
var form = document.getElementsByClassName("content")[0];
|
||||||
|
|
||||||
|
var form_ws_url = document.getElementsByName("ws_url")[0];
|
||||||
|
var form_ws_timeout = document.getElementsByName("ws_timeout")[0];
|
||||||
|
|
||||||
|
form_ws_url.value = config.ws_url;
|
||||||
|
form_ws_timeout.value = config.ws_timeout;
|
||||||
|
|
||||||
|
// attach events to both form inputs to update config
|
||||||
|
form_ws_url.addEventListener("input", async function(){
|
||||||
|
|
||||||
|
await browser.storage.local.set({"ws_url": form_ws_url.value});
|
||||||
|
});
|
||||||
|
|
||||||
|
form_ws_timeout.addEventListener("input", async function(e){
|
||||||
|
|
||||||
|
form_ws_timeout.value = form_ws_timeout.value.replaceAll(/[^0-9]/g, "");
|
||||||
|
var timeout = form_ws_timeout.value;
|
||||||
|
|
||||||
|
if(form_ws_timeout.value == ""){
|
||||||
|
|
||||||
|
timeout = 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
await browser.storage.local.set({"ws_timeout": timeout});
|
||||||
|
});
|
||||||
|
|
||||||
|
// show the websocket status
|
||||||
|
await html_gen_status();
|
||||||
|
|
||||||
|
setInterval(async function(){
|
||||||
|
|
||||||
|
await html_gen_status();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function html_gen_status(){
|
||||||
|
|
||||||
|
var dom_status = document.getElementById("status");
|
||||||
|
var icon_path = null;
|
||||||
|
var text = null;
|
||||||
|
|
||||||
|
var config = await browser.storage.local.get();
|
||||||
|
|
||||||
|
switch(config.status){
|
||||||
|
|
||||||
|
case STATUS_CONNECTING:
|
||||||
|
icon_path = "connecting";
|
||||||
|
text = `Connecting... Attempt #${config.attempts}`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATUS_CONNECTED:
|
||||||
|
icon_path = "connected";
|
||||||
|
text = `Connected & ready to kick ass`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STATUS_OFFLINE:
|
||||||
|
icon_path = "offline";
|
||||||
|
text = `C&C unreachable. Attempt #${config.attempts}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dom_status.innerHTML = `<img src="icons/${icon_path}.png">${text}`;
|
||||||
|
}
|
||||||
50
server/hello-world.js
Normal file
50
server/hello-world.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
const fplay = require("./lib/fplay.js");
|
||||||
|
|
||||||
|
var port = 3030;
|
||||||
|
var timeout = 30000;
|
||||||
|
var password = "cnc";
|
||||||
|
|
||||||
|
fplay.event.on("server_ready", function(){
|
||||||
|
|
||||||
|
console.log("listening on port " + port + " (timeout=" + timeout + ")");
|
||||||
|
});
|
||||||
|
|
||||||
|
fplay.event.on("browser_connect", async function(ws){
|
||||||
|
|
||||||
|
const ua = await fplay.get_ua(ws);
|
||||||
|
console.log("Connection from " + ua);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
const blanktab = await fplay.close_all_tabs(ws);
|
||||||
|
await fplay.delete_all_containers(ws);
|
||||||
|
|
||||||
|
// create container
|
||||||
|
const container = await fplay.container_create(ws);
|
||||||
|
console.log(container);
|
||||||
|
|
||||||
|
// assign proxy
|
||||||
|
/*
|
||||||
|
await fplay.container_attach_proxy(
|
||||||
|
ws,
|
||||||
|
container,
|
||||||
|
{
|
||||||
|
type: "socks", // socks(is socks5) http, https, socks4
|
||||||
|
host: "whatever-proxy-host-you-want.io",
|
||||||
|
port: 1339,
|
||||||
|
username: "admin",
|
||||||
|
password: "1234",
|
||||||
|
proxyDNS: true,
|
||||||
|
}
|
||||||
|
);*/
|
||||||
|
|
||||||
|
// open tab
|
||||||
|
const newtab = await fplay.tab_open(ws, "https://lolcat.ca", true, container);
|
||||||
|
console.log(newtab);
|
||||||
|
|
||||||
|
// get page's title
|
||||||
|
var result = await fplay.tab_inject_js(ws, newtab, "document.title", true);
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
fplay.init(port, password, timeout);
|
||||||
538
server/lib/fplay.js
Normal file
538
server/lib/fplay.js
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
const http = require("http");
|
||||||
|
const { WebSocketServer } = require("ws");
|
||||||
|
|
||||||
|
var fplay = {};
|
||||||
|
|
||||||
|
fplay.PORT = 3030;
|
||||||
|
fplay.COMMAND_TIMEOUT = 5000;
|
||||||
|
fplay.PASSWORD = "cnc";
|
||||||
|
|
||||||
|
const { EventEmitter } = require("events");
|
||||||
|
fplay.event = new EventEmitter();
|
||||||
|
|
||||||
|
fplay.server = http.createServer(function(req, res){
|
||||||
|
|
||||||
|
switch(req.url){
|
||||||
|
|
||||||
|
case "/" + fplay.PASSWORD:
|
||||||
|
res.writeHead(426, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({"status": "No websocket upgrade received"}));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
res.writeHead(404, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ "status": "Invalid endpoint" }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fplay.wss = new WebSocketServer({ server: fplay.server, path: "/" + fplay.PASSWORD });
|
||||||
|
|
||||||
|
fplay.seqid = 0;
|
||||||
|
fplay.pending = new Map(); // promise map
|
||||||
|
fplay.pending_dom_ready = new Map();
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
fplay.send = async function(ws, action, msg = {}){
|
||||||
|
|
||||||
|
const seqid = ++fplay.seqid;
|
||||||
|
msg.action = action;
|
||||||
|
msg.seqid = seqid;
|
||||||
|
|
||||||
|
return new Promise(function(resolve){
|
||||||
|
|
||||||
|
const timer = setTimeout(function(){
|
||||||
|
fplay.pending.delete(seqid);
|
||||||
|
resolve(false);
|
||||||
|
}, fplay.COMMAND_TIMEOUT);
|
||||||
|
|
||||||
|
fplay.pending.set(seqid, {
|
||||||
|
resolve: function(data){ clearTimeout(timer); resolve(data); },
|
||||||
|
reject: function(){ clearTimeout(timer); resolve(false); }
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.send(JSON.stringify(msg));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.wait_random = async function(min, max){
|
||||||
|
|
||||||
|
return new Promise(function(resolve){
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
|
||||||
|
resolve(true);
|
||||||
|
}, min + Math.round(Math.random() * (max - min)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.parse_cookies = function(headers){
|
||||||
|
|
||||||
|
var cookies = {};
|
||||||
|
|
||||||
|
headers.forEach(function(header){
|
||||||
|
|
||||||
|
var cookie = header.split(":")
|
||||||
|
var header_name = cookie.shift();
|
||||||
|
|
||||||
|
if(header_name.toLowerCase() != "cookie"){ return; } // continue;
|
||||||
|
|
||||||
|
cookie = cookie.join(":").split(";");
|
||||||
|
|
||||||
|
cookie.forEach(function(c){
|
||||||
|
|
||||||
|
c = c.split("=");
|
||||||
|
var key = c.shift().trim();
|
||||||
|
var value = c.join("=");
|
||||||
|
|
||||||
|
cookies[key] = value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Misc protocol functions
|
||||||
|
//
|
||||||
|
fplay.get_ua = async function(ws){
|
||||||
|
|
||||||
|
var ua = await fplay.send(ws, "get_ua");
|
||||||
|
|
||||||
|
if(typeof ua.ua == "string"){
|
||||||
|
|
||||||
|
return ua.ua;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Tab functions
|
||||||
|
//
|
||||||
|
fplay.get_tab_list = async function(ws){
|
||||||
|
|
||||||
|
var tabs = await fplay.send(ws, "get_tabs");
|
||||||
|
|
||||||
|
if(typeof tabs.tabs == "object"){
|
||||||
|
|
||||||
|
return tabs.tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.tab_open = async function(ws, url, await_dom_ready = false, container = null){
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
url: url
|
||||||
|
};
|
||||||
|
|
||||||
|
if(container !== null){
|
||||||
|
|
||||||
|
if(
|
||||||
|
typeof container == "object" &&
|
||||||
|
typeof container.id != "undefined"
|
||||||
|
){
|
||||||
|
|
||||||
|
data.container = container.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof container == "string"){
|
||||||
|
|
||||||
|
data.container = container;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newtab = await fplay.send(ws, "tab_open", data);
|
||||||
|
|
||||||
|
if(typeof newtab.data == "object"){
|
||||||
|
|
||||||
|
if(await_dom_ready){
|
||||||
|
|
||||||
|
var data = await fplay.wait_for_dom_ready(newtab.data.id);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newtab.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ tab_ids: number, array or tab object
|
||||||
|
fplay.tab_close = async function(ws, tab_ids){
|
||||||
|
|
||||||
|
if(
|
||||||
|
typeof tab_ids == "object" &&
|
||||||
|
typeof tab_ids.id == "number"
|
||||||
|
){
|
||||||
|
|
||||||
|
tab_ids = tab_ids.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
var closed_tab_count = await fplay.send(ws, "tab_close", {"tabid": tab_ids});
|
||||||
|
|
||||||
|
if(typeof closed_tab_count.closed_tab_count == "number"){
|
||||||
|
|
||||||
|
return closed_tab_count.closed_tab_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.close_all_tabs = async function(ws){
|
||||||
|
|
||||||
|
const tabs = await fplay.get_tab_list(ws);
|
||||||
|
const newtab = await fplay.tab_open(ws, "about:blank");
|
||||||
|
|
||||||
|
// clean up useless tabs
|
||||||
|
var tabs_to_close = [];
|
||||||
|
|
||||||
|
for(var i=0; i<tabs.length; i++){
|
||||||
|
|
||||||
|
if(tabs[i].id !== newtab.id){
|
||||||
|
|
||||||
|
tabs_to_close.push(tabs[i].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fplay.tab_close(ws, tabs_to_close);
|
||||||
|
|
||||||
|
return newtab;
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.tab_focus = async function(ws, tabid){
|
||||||
|
|
||||||
|
if(
|
||||||
|
typeof tabid == "object" &&
|
||||||
|
typeof tabid.id == "number"
|
||||||
|
){
|
||||||
|
|
||||||
|
var id = tabid.id;
|
||||||
|
}else{
|
||||||
|
|
||||||
|
var id = tabid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tab_state = await fplay.send(ws, "tab_focus", {"tabid": id});
|
||||||
|
|
||||||
|
if(typeof tab_state.status == "boolean"){
|
||||||
|
|
||||||
|
return tab_state.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.tab_exists = async function(ws, tabid){
|
||||||
|
|
||||||
|
if(
|
||||||
|
typeof tabid == "object" &&
|
||||||
|
typeof tabid.id == "number"
|
||||||
|
){
|
||||||
|
|
||||||
|
var id = tabid.id;
|
||||||
|
}else{
|
||||||
|
|
||||||
|
var id = tabid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tab_state = await fplay.send(ws, "tab_exists", {"tabid": id});
|
||||||
|
|
||||||
|
if(typeof tab_state.exists == "boolean"){
|
||||||
|
|
||||||
|
return tab_state.exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.tab_inject_js = async function(ws, tabid, js, isolated = false){
|
||||||
|
|
||||||
|
if(
|
||||||
|
typeof tabid == "object" &&
|
||||||
|
typeof tabid.id == "number"
|
||||||
|
){
|
||||||
|
|
||||||
|
var id = tabid.id;
|
||||||
|
}else{
|
||||||
|
|
||||||
|
var id = tabid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tab_state = await fplay.send(ws, "tab_inject_js", {"tabid": id, "js": js, "isolated": isolated});
|
||||||
|
|
||||||
|
if(typeof tab_state.status != "undefined"){
|
||||||
|
|
||||||
|
if(tab_state.status === true){
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: true,
|
||||||
|
result: tab_state.result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: tab_state.status,
|
||||||
|
result: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: false,
|
||||||
|
result: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Container functions
|
||||||
|
//
|
||||||
|
fplay.container_create = async function(ws, name = null){
|
||||||
|
|
||||||
|
if(name === null){
|
||||||
|
|
||||||
|
var data = {};
|
||||||
|
}else{
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
name: name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = await fplay.send(ws, "container_create", data);
|
||||||
|
|
||||||
|
if(typeof container == "object"){
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.get_container_list = async function(ws){
|
||||||
|
|
||||||
|
var containers = await fplay.send(ws, "get_container_list");
|
||||||
|
|
||||||
|
if(typeof containers.containers == "object"){
|
||||||
|
|
||||||
|
return containers.containers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// accepts container object, array of ids or raw id
|
||||||
|
fplay.container_delete = async function(ws, id){
|
||||||
|
|
||||||
|
if(
|
||||||
|
typeof id == "object" &&
|
||||||
|
typeof id.id != "undefined"
|
||||||
|
){
|
||||||
|
|
||||||
|
id = id.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
var container_state = await fplay.send(ws, "container_delete", {"id": id});
|
||||||
|
|
||||||
|
if(typeof container_state.closed_container_count == "number"){
|
||||||
|
|
||||||
|
return container_state.closed_container_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.container_exists = async function(ws, id){
|
||||||
|
|
||||||
|
if(
|
||||||
|
typeof id == "object" &&
|
||||||
|
typeof id.id != "undefined"
|
||||||
|
){
|
||||||
|
|
||||||
|
id = id.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
var container_state = await fplay.send(ws, "container_exists", {"id": id});
|
||||||
|
|
||||||
|
if(typeof container_state.exists == "boolean"){
|
||||||
|
|
||||||
|
return container_state.exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.delete_all_containers = async function(ws){
|
||||||
|
|
||||||
|
var list = await fplay.get_container_list(ws);
|
||||||
|
|
||||||
|
var containers_to_delete = [];
|
||||||
|
|
||||||
|
for(var i=0; i<list.length; i++){
|
||||||
|
|
||||||
|
containers_to_delete.push(list[i].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = await fplay.container_delete(ws, containers_to_delete);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
proxy object can look like this
|
||||||
|
{
|
||||||
|
type: "socks", // socks(is socks5) http, https, socks4
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 1080,
|
||||||
|
username: "myuser",
|
||||||
|
password: "mypassword",
|
||||||
|
proxyDNS: true,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
fplay.container_attach_proxy = async function(ws, id, proxy){
|
||||||
|
|
||||||
|
if(
|
||||||
|
typeof id == "object" &&
|
||||||
|
typeof id.id != "undefined"
|
||||||
|
){
|
||||||
|
|
||||||
|
id = id.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxy_success = await fplay.send(ws, "container_attach_proxy", {"id": id, "proxy": proxy});
|
||||||
|
|
||||||
|
if(typeof proxy_success.status == "boolean"){
|
||||||
|
|
||||||
|
return proxy_success.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.container_detach_proxy = async function(ws, id){
|
||||||
|
|
||||||
|
if(
|
||||||
|
typeof id == "object" &&
|
||||||
|
typeof id.id != "undefined"
|
||||||
|
){
|
||||||
|
|
||||||
|
id = id.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxy_success = await fplay.send(ws, "container_detach_proxy", {"id": id});
|
||||||
|
|
||||||
|
if(typeof proxy_success.status == "boolean"){
|
||||||
|
|
||||||
|
return proxy_success.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Unsolicited event handlers
|
||||||
|
//
|
||||||
|
fplay.wait_for_dom_ready = async function(tabid){
|
||||||
|
|
||||||
|
return new Promise(function(resolve){
|
||||||
|
|
||||||
|
fplay.pending_dom_ready.set(
|
||||||
|
tabid,
|
||||||
|
{
|
||||||
|
resolve: resolve,
|
||||||
|
reject: function(){ resolve(false); }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.dom_is_ready = function(tabid, data){
|
||||||
|
|
||||||
|
const promise = fplay.pending_dom_ready.get(tabid);
|
||||||
|
|
||||||
|
if(promise){
|
||||||
|
|
||||||
|
fplay.pending_dom_ready.delete(tabid);
|
||||||
|
promise.resolve(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fplay.event.on("dom_ready", function(data){
|
||||||
|
|
||||||
|
const promise = fplay.pending_dom_ready.get(data.id);
|
||||||
|
|
||||||
|
if(promise){
|
||||||
|
|
||||||
|
fplay.pending_dom_ready.delete(data.id);
|
||||||
|
promise.resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fplay.event.on("dom_load_fail", function(data){
|
||||||
|
|
||||||
|
const promise = fplay.pending_dom_ready.get(data.id);
|
||||||
|
|
||||||
|
if(promise){
|
||||||
|
|
||||||
|
fplay.pending_dom_ready.delete(data.id);
|
||||||
|
promise.reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// log requests
|
||||||
|
/*
|
||||||
|
fplay.event.on("web_request", function(page){
|
||||||
|
|
||||||
|
console.log(page);
|
||||||
|
});*/
|
||||||
|
|
||||||
|
fplay.browser_connected = false;
|
||||||
|
|
||||||
|
fplay.wss.on("connection", async function(ws){
|
||||||
|
|
||||||
|
fplay.browser_connected = true;
|
||||||
|
fplay.event.emit("browser_connect", ws);
|
||||||
|
|
||||||
|
// register user events
|
||||||
|
ws.on("message", async function(msg){
|
||||||
|
|
||||||
|
msg = JSON.parse(msg.toString());
|
||||||
|
|
||||||
|
//console.log(msg);
|
||||||
|
|
||||||
|
if(typeof msg.seqid == "number"){
|
||||||
|
|
||||||
|
// resolve promises
|
||||||
|
fplay.pending.get(msg.seqid).resolve(msg);
|
||||||
|
fplay.pending.delete(msg.seqid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// any other message should be an unsolicited event
|
||||||
|
fplay.event.emit(msg.action, msg.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on("close", async function(){
|
||||||
|
|
||||||
|
fplay.browser_connected = false;
|
||||||
|
fplay.event.emit("browser_disconnect", {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fplay.init = function(port = 3030, password = "cnc", command_timeout = 5000){
|
||||||
|
|
||||||
|
fplay.PORT = port;
|
||||||
|
fplay.COMMAND_TIMEOUT = command_timeout;
|
||||||
|
fplay.PASSWORD = password;
|
||||||
|
|
||||||
|
fplay.server.listen(fplay.PORT, function(){
|
||||||
|
|
||||||
|
fplay.event.emit("server_ready", {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = fplay;
|
||||||
Reference in New Issue
Block a user