the zuccing begins

This commit is contained in:
lolcat 2025-03-06 21:42:34 -05:00
commit 5e58b46e69
4 changed files with 885 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
package.json
package-lock.json

13
README.md Normal file
View File

@ -0,0 +1,13 @@
# Facebook messenger bot
Shitty facebook messenger bot unofficial API written in nodeJS. This mimicks the requests of a browser.
## Roadmap
[x] Login (with user&pass)
[x] Connect to websocket
[x] Send messages (sort of)
[ ] Receive messages
[ ] Get friend list
[ ] Eh who gives a shit i give up writing this readme
## License
wtfpl

837
login.js Normal file
View File

@ -0,0 +1,837 @@
const { fetch } = require("fetch-h2");
const tls = require("tls");
const crypto = require('crypto');
const nacl = require('tweetnacl');
const sealedbox = require('tweetnacl-sealedbox-js');
const zlib = require("zlib");
const mqtt = require("mqtt-packet");
const WebSocket = require("ws");
const user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0';
// config
config = {
email: "yourmail@gmail.com",
pass: "yourpass"
};
// firefox bypass
const agent = {
secureProtocol: "TLS_method",
minVersion: "TLSv1.2",
maxVersion: "TLSv1.3",
ciphers: [
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-ECDSA-CHACHA20-POLY1305",
"ECDHE-RSA-CHACHA20-POLY1305"
].join(":"),
honorCipherOrder: true
};
// consts
const POST_URLENCODE = 0;
const POST_JSON = 1;
const POST_RAW = 2;
//
// Helper functions
//
function merge_headers(headers){
var base_headers = {
'User-Agent': user_agent,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip',
'Referer': 'https://www.messenger.com/',
//'Content-Type': 'application/x-www-form-urlencoded',
//'Content-Length': '317',
'Origin': 'https://www.messenger.com',
'DNT': '1',
'Sec-GPC': '1',
//'Connection': 'keep-alive',
//'Cookie': 'datr=Kh69Z0FC3H-Stoj1M5vR7tXx; wd=1011x1000',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Priority': 'u=0, i',
'TE': 'trailers'
};
for(let key in headers){
base_headers[key] = headers[key];
}
return base_headers;
}
function make_guid(){
var date = Date.now();
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
const r = Math.floor((date + Math.random() * 16) % 16);
return (c == 'x' ? r : (r & 7) | 8).toString(16);
});
}
function make_otid(){
const rand = Math.floor(Math.random() * 4294967295);
const str = ('0000000000000000000000' + rand.toString(2)).slice(-22);
const msgs = Date.now().toString(2) + str;
return binaryToDecimal(msgs);
}
function binaryToDecimal(data){
let ret = '';
while (data !== '0') {
let end = 0;
let fullName = '';
let i = 0;
for (; i < data.length; i++) {
end = 2 * end + parseInt(data[i], 10);
if (end >= 10) {
fullName += '1';
end -= 10;
} else {
fullName += '0';
}
}
ret = end.toString() + ret;
data = fullName.slice(fullName.indexOf('1'));
}
return ret;
}
function http_build_query(array){
var str = [];
for(let key in array){
str.push(encodeURIComponent(key) + "=" + encodeURIComponent(array[key]));
}
return str.join("&");
}
async function get_data(url, headers = {}){
const data = await fetch(
url,
{
headers: merge_headers(headers),
httpsOptions: agent,
allowForbiddenHeaders: true
}
);
const body = await data.text();
return {
status: data.status,
headers: data.headers._data,
data: body
};
}
async function post_data(url, data, encode_as, headers = {}){
switch(encode_as){
case POST_URLENCODE:
data = http_build_query(data);
headers["Content-Type"] = "application/x-www-form-urlencoded";
break;
case POST_JSON:
data = JSON.stringify(data);
headers["Content-Type"] = "application/json";
break;
}
headers["Content-Length"] = data.length;
const res = await fetch(
url,
{
method: "POST",
headers: merge_headers(headers),
body: data,
httpsOptions: agent,
allowForbiddenHeaders: true
}
);
const body = await res.text();
return {
status: res.status,
headers: res.headers._data,
data: body
};
}
//
// Facebook login
//
init_login();
async function init_login(){
login(get_init_data());
}
async function get_init_data(){
console.log("(info) Getting FB public key");
//
// Step 1
// get keyId, publicKey, initial_request_id & other shits
//
var keys =
await get_data(
"https://www.messenger.com/"
);
var keyId = keys.data.match(/"keyId":([0-9]+)/);
if(keyId === null){
throw new Error("Could not grep keyId");
}
keyId = parseInt(keyId[1]);
console.log("(debug) Got keyId=" + keyId);
// get publicKey
var publicKey = keys.data.match(/"publicKey":"([A-Za-z0-9]+)"/);
if(publicKey === null){
throw new Error("Could not grep publicKey");
}
console.log("(debug) publicKey=" + publicKey[1]);
// convert public key to binary
publicKey = publicKey[1];
// get initial_request_id
var initial_request_id = keys.data.match(/(?:name|id)=\"initial_request_id\" value=\"([A-Za-z0-9_-]+)\"/);
if(initial_request_id === null){
throw new Error("Could not grep initial_request_id");
}
console.log("(debug) Got initial_request_id=" + initial_request_id[1]);
initial_request_id = initial_request_id[1];
// get lgnrnd
var lgnrnd = keys.data.match(/(?:name|id)=\"lgnrnd\" value=\"([A-Za-z0-9_-]+)\"/);
if(lgnrnd === null){
throw new Error("Could not grep initial_request_id");
}
console.log("(debug) Got lgnrnd=" + lgnrnd[1]);
lgnrnd = lgnrnd[1];
// get LSD token
var lsd = keys.data.match(/\["LSD"(?:,\[\])?,{"token":"([A-Za-z0-9_-]+)"}/);
if(lsd === null){
throw new Error("Could not grep lsd");
}
console.log("(debug) Got lsd=" + lsd[1]);
lsd = lsd[1];
return {
keyId: keyId,
publicKey: publicKey,
initial_request_id: initial_request_id,
lgnrnd: lgnrnd,
lsd: lsd
};
}
async function login(init){
init = await init;
//
// Step 2.
// Encrypt form data
//
const time = Math.floor(Date.now() / 1000);
const key = crypto.randomBytes(32);
const iv = Buffer.alloc(12, 0);
// Create AES-GCM cipher
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
cipher.setAAD(Buffer.from(time.toString(), "utf-8"));
// Encrypt password
let encryptedPassword = cipher.update(config.pass, "utf8", "binary");
encryptedPassword += cipher.final("binary");
encryptedPassword = Buffer.from(encryptedPassword, "binary");
const cipherTag = cipher.getAuthTag();
// Encrypt AES key with public key
const publicKey_d = Buffer.from(init.publicKey, "hex");
const sealedBox = sealedbox.seal(key, publicKey_d);
const encryptedKey = Buffer.from(sealedBox);
// Build final payload
const keyIdBuffer = Buffer.from([1, init.keyId]);
const lengthBuffer = Buffer.alloc(2);
lengthBuffer.writeUInt16LE(encryptedKey.length, 0);
const data = Buffer.concat([
keyIdBuffer,
lengthBuffer,
encryptedKey,
cipherTag,
encryptedPassword
]);
// Base64 encode and format
const encrypted_data = data.toString("base64");
//
// Step 3
// Send login req
//
console.log("(info) Initiating login in 10 seconds using " + encrypted_data);
setTimeout(async function(){
console.log("(info) Sending login information");
var redirect =
await post_data(
"https://www.messenger.com/login/password/",
{
jazoest: "21035",
lsd: init.lsd,
initial_request_id: init.initial_request_id,
timezone: "300",
lgndim: btoa('{"w":1920,"h":1080,"aw":1920,"ah":1080,"c":24}'),
lgnrnd: init.lgnrnd,
lgnjs: "n",
email: config.email,
pass: "#PWD_BROWSER:5:" + time + ":" + encrypted_data,
default_persistent: ""
},
POST_URLENCODE,
{
"Cookie": "datr=m5vDZzGXLy2_XEtAGDrB6Cgi; wd=1920x1080"
}
);
//
// Check if login was successful
//
if(
redirect.headers.has("location") === false ||
(
redirect.headers.get("location") != "https://www.messenger.com/" &&
redirect.headers.get("location") != "https://www.messenger.com"
)
){
throw new Error("Did not obtain correct redirect value");
}
//
// Get cookies
//
if(redirect.headers.has("set-cookie") === false){
throw new Error("Did not obtain any cookies");
}
const cookies_raw = redirect.headers.get("set-cookie");
var cookies = [];
for(var i=0; i<cookies_raw.length; i++){
var value = cookies_raw[i].match(/[A-Za-z0-9]+=[^;]+/);
if(value[0].match(/^user=/)){
cookies.push("c_" + value);
continue;
}
if(value[0].match(/^l=|^n=/)){
cookies.push("ps_" + value);
continue;
}
cookies.push(value[0]);
}
cookies = cookies.join("; ");
console.log("(debug) Got cookies: " + cookies);
fetch_main(cookies);
}, 10000);
}
async function fetch_main(cookies){
console.log("(info) Getting main chat page");
// fetch main page
var main_page =
await get_data(
"https://www.messenger.com/",
{
"Cookie": cookies
}
);
if(main_page.headers.has("location")){
console.log("(info) Getting main chat page through redirect");
main_page =
await get_data(
main_page.headers.get("location")[0],
{
"Cookie": cookies
}
);
}
console.log("(info) Got main page");
/*
// get clientID !!!!!!there are multiple!!!!!
var clientIDs_r = main_page.data.matchAll(/"clientID":"([A-Za-z0-9_-]+)"/g);
var clientIDs = Array.from(clientIDs_r, m => m[1]);
if(clientIDs === null){
throw new Error("Could not grep clientID(s)");
}
console.log("(debug) Got " + clientIDs.length + " clientIDs: [" + clientIDs.join(", ") + "]");
*/
// get appIDs for websocket edges
var appID_status = main_page.data.match(/"appId":([0-9]+)/);
if(appID_status === null){
throw new Error("Failed to grep appId for status websocket");
}
appID_status = parseInt(appID_status[1]);
console.log("(debug) Got appID_status " + appID_status);
var appID_msg = main_page.data.match(/"appID":([0-9]+)/);
if(appID_msg === null){
throw new Error("Failed to grep appId for msg websocket");
}
appID_msg = parseInt(appID_msg[1]);
console.log("(debug) Got appID_msg " + appID_msg);
// get version
var version = main_page.data.match(/\\"version\\":([0-9]{4,})/);
if(version === null){
throw new Error("Failed to grep version");
}
version = version[1];
console.log("(debug) Got version " + version);
//
// Connect to the websocket edges
//
// connect to the status server (random GUID, no foreground)
//ws_connect("status", cookies, appID_status, make_guid(), false);
// connect to the message server (defined GUID, has foreground, version)
ws_connect("msg", cookies, appID_status, make_guid(), true, version);
}
async function ws_connect(ident, cookies, appID, edgeName, foreground, version){
var msg_c = 0
var task_id = 1;
//
// Split up cookies
//
cookies_s = cookies.split(";");
cookies_a = [];
for(var i=0; i<cookies_s.length; i++){
var tmp = cookies_s[i].split(/=(.+)/).slice(0, 2);
cookies_a[tmp[0].trim()] = tmp[1].trim();
}
// connect to this shit
// wss://edge-chat.messenger.com/chat?region=atn&sid=2270495435128831&cid=36e7527a-1e3b-4cab-b522-6177131a26b1
const sessionID = Math.floor(Math.random() * 9007199254740991) + 1;
const ws = new WebSocket(
"wss://edge-chat.messenger.com/chat?region=atn&sid=" + sessionID + "&cid=" + edgeName,
{
headers: {
'User-Agent': user_agent,
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Sec-WebSocket-Version': '13',
'Origin': 'https://www.messenger.com',
'Sec-WebSocket-Extensions': 'permessage-deflate',
//'Sec-WebSocket-Key': '3VLUf/lsgASJEUYl9dQbIQ==',
'DNT': '1',
'Sec-GPC': '1',
'Connection': 'keep-alive, Upgrade',
'Cookie': cookies,
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'websocket',
'Sec-Fetch-Site': 'same-site',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache',
'Upgrade': 'websocket'
}
}
);
// @TODO: send ping packet
// c000
var conn_open = false;
ws.on("open", async function(){
console.log("(ws-" + ident + ") Sending login info");
setInterval(function(){
ws.send(Buffer.from([0xc0, 0x00]));
console.log("ping");
}, 10000);
// server reply: yes
await ws.send(
mqtt.generate({
cmd: "connect",
protocolId: "MQIsdp",
protocolVersion: 3,
clean: true,
clientId: "mqttwsclient",
reschedulePings: true,
keepalive: 60,
username: JSON.stringify({
a: user_agent,
asi: null,
aid: appID, // token from messenger, INTEGER
aids: null,
chat_on: false,
cp: 3,
ct: "websocket",
d: edgeName, // @TODO sometimes completely random
dc: "",
ecp: 10,
fg: foreground,
gas: null,
mqtt_sid: "",
no_auto_fg: true,
p: null,
pack: [],
php_override: "",
pm: [],
s: sessionID, // WE GENERATE THIS (also known as sid), INTEGER
st: [],
u: cookies_a.c_user
}),
password: null
})
);
// server reply: yes
await ws.send(
mqtt.generate({
cmd: "publish",
retain: false,
qos: 1,
dup: false,
topic: "/ls_app_settings",
payload: JSON.stringify({
ls_fdid: "",
ls_sv: version
}),
messageId: msg_c++,
})
);
// server reply: no
await ws.send(
mqtt.generate({
cmd: "subscribe",
retain: false,
qos: 1,
dup: false,
topic: null,
payload: null,
subscriptions: [
{ topic: "/ls_foreground_state", qos: 0 }
],
messageId: msg_c++
})
);
// server reply: no
await ws.send(
mqtt.generate({
cmd: "subscribe",
retain: false,
qos: 1,
dup: false,
topic: null,
payload: null,
subscriptions: [
{ topic: "/ls_resp", qos: 0 }
],
messageId: msg_c++
})
);
//
// @FUNCTION
// GET FRIEND LIST
//
/*
await ws.send(
mqtt.generate({
cmd: "publish",
retain: false,
qos: 1,
dup: false,
topic: "/ls_req",
messageId: msg_c++,
payload: JSON.stringify({
app_id: appID.toString(),
payload: JSON.stringify({
epoch_id: 7303517515494497649,
tasks: [
{
failure_count: null,
label: "452",
payload: JSON.stringify({ limit: 100 }),
queue_name: JSON.stringify(["search_contacts", Date.now().toString()]),
task_id: task_id++
}
],
version_id: version
}),
request_id: 21,
type: 3
})
})
);*/
//
// @FUNCTION
// RESOLVE USERID TO USERNAME
//
/*
await ws.send(
mqtt.generate({
cmd: "publish",
retain: false,
qos: 1,
dup: false,
topic: "/ls_req",
payload: JSON.stringify({
app_id: appID.toString(),
payload: JSON.stringify({ // yeah.. we need to re-json it
epoch_id: 7302231049254668045,
tasks: [
{
failure_count: null,
label: "207",
payload: JSON.stringify({ // AGAIN!!
contact_id: 0
}),
queue_name: "cpq_v2",
task_id: task_id++
},
{
failure_count: null,
label: "207",
payload: JSON.stringify({ // AGAIN!!
contact_id: 0
}),
queue_name: "cpq_v2",
task_id: task_id++
}
],
version_id: version
}),
request_id: 4,
type: 3
}),
messageId: msg_c++
})
);*/
//
// @FUNCTION
// send a message by thread ID
//
/*
await ws.send(
mqtt.generate({
cmd: "publish",
retain: false,
qos: 1,
dup: false,
topic: "/ls_req",
messageId: msg_c++,
payload: JSON.stringify({
app_id: appID.toString(),
payload: JSON.stringify({
epoch_id: parseInt(make_otid()),
tasks: [
{
failure_count: null,
label: "46",
payload: JSON.stringify({
thread_id: 0,
otid: make_otid(),
source: 65537,
send_type: 1,
sync_group: 1,
mark_thread_read: 1,
text: "https://lolcat.ca",
initiating_source: 1,
skip_url_preview_gen: 0,
text_has_links: 0,
multitab_env: 0
}),
queue_name: "0",
task_id: task_id++
},
{ // optional garbage
failure_count: null,
label: "21",
payload: JSON.stringify({
thread_id: 0,
last_read_watermark_ts: Date.now(),
sync_group: 1
}),
queue_name: "0",
task_id: task_id++
}
],
version_id: version,
data_trace_id: null
}),
request_id: msg_c,
type: 3
}),
})
);*/
// broken shit
/*
await ws.send(
mqtt.generate({
cmd: "publish",
retain: false,
qos: 1,
dup: false,
topic: "/ls_req",
messageId: msg_c++,
payload: JSON.stringify({
app_id: appID.toString(),
payload: JSON.stringify({
epoch_id: parseInt(make_otid()),
tasks: [
{
failure_count: null,
label: "145",
payload: JSON.stringify({
is_after: 0,
parent_thread_key: -12,
reference_thread_key: 0,
reference_activity_timestamp: 9999999999999,
additional_pages_to_fetch: 0,
cursor: null,
messaging_tag: null,
sync_group: 1
}),
queue_name: "trq",
task_id: task_id++
},
{
failure_count: null,
label: "145",
payload: JSON.stringify({
is_after: 0,
parent_thread_key: -12,
reference_thread_key: 0,
reference_activity_timestamp: 9999999999999,
additional_pages_to_fetch: 0,
messaging_tag: null,
sync_group: 95
}),
queue_name: "trq",
task_id: task_id++
}
],
version_id: version
}),
request_id: msg_c,
type: 3
})
})
);*/
});
ws.on("message", function(message){
/*
//
// Handle message acknowledge packet
//
if(
conn_open === false &&
message.equals(Buffer.from([0x20, 0x02, 0x00, 0x00]))
){
conn_open = true;
console.log("(ws-" + ident + ") FB ack");
return;
}*/
console.log(message.toString("hex"));
//console.log(message);
});
ws.on("error", function(error){
console.log("(ws-" + ident + ") Error: ", error);
});
ws.on("close", function(){
console.log("(ws-" + ident + ") Closed");
});
}

32
util_decoder.js Normal file
View File

@ -0,0 +1,32 @@
const mqtt = require("mqtt-packet");
const { Readable } = require("stream");
const parser = mqtt.parser({
protocolId: "MQIsdp",
protocolVersion: 3,
clean: true,
clientId: "mqttwsclient",
reschedulePings: true,
keepalive: 60
});
parser.on("packet", function(packet){
console.log(packet);
if(
typeof packet.payload != "undefined" &&
packet.payload !== null
){
console.log(JSON.parse(packet.payload.toString()));
}
});
parser.on("error", function(error){
console.log("Error: ", error);
});
parser.parse(
Buffer.from("", "hex")
);