diff --git a/index.d.ts b/index.d.ts index c3c301aa..6f1094ef 100644 --- a/index.d.ts +++ b/index.d.ts @@ -81,15 +81,19 @@ declare module '@xaviabot/fca-unofficial' { changeApprovalMode: (approvalMode: 0 | 1, threadID: string, callback?: (err?: Error) => void) => Promise, changeArchivedStatus: (threadOrThreads: string | string[], archive: boolean, callback?: (err?: Error) => void) => Promise, changeBlockedStatus: (userID: string, blocked: boolean, callback?: (err?: Error) => void) => Promise, + changeBlockedStatusMqtt: (userID: string, status: boolean, type: string?, callback?: (err?: Error) => void) => Promise, changeGroupImage: (image: ReadableStream, threadID: string, callback?: (err?: Error) => void) => Promise, changeNickname: (nickname: string, threadID: string, pariticipantID: string, callback?: (err?: Error) => void) => Promise, changeThreadColor: (color: string, threadID: string, callback?: (err?: Error) => void) => Promise, changeThreadEmoji: (emoji: string | null, threadID: string, callback?: (err?: Error) => void) => Promise, createNewGroup: (participantIDs: string[], groupTitle?: string, callback?: (err: Error, threadID: string) => void) => Promise, createPoll: (title: string, threadID: string, options?: { [item: string]: boolean }, callback?: (err?: Error) => void) => Promise, + createPollMqtt: (title: string, options?: { [item: string]: boolean }, threadID: string, callback?: (err?: Error) => void) => Promise, deleteMessage: (messageOrMessages: string | string[], callback?: (err?: Error) => void) => Promise, deleteThread: (threadOrThreads: string | string[], callback?: (err?: Error) => void) => Promise, + editMessage: (text: string, messageID: string, callback?: (err?: Error) => void) => Promise, forwardAttachment: (attachmentID: string, userOrUsers: string | string[], callback?: (err?: Error) => void) => Promise, + forwardMessage: (messageID: string, threadID: string, callback?: (err?: Error) => void) => Promise, getAppState: () => any, getCurrentUserID: () => string, getEmojiUrl: (c: string, size: number, pixelRatio: number) => string, @@ -112,15 +116,19 @@ declare module '@xaviabot/fca-unofficial' { markAsReadAll: (callback?: (err?: Error) => void) => Promise, markAsSeen(seenTimestamp?: number, callback?: (err?: Error) => void): Promise, muteThread: (threadID: string, muteSeconds: number, callback?: (err?: Error) => void) => Promise, + pinMessage: (pinMode: boolean, messageID: string, threadID: string, callback?: (err?: Error) => void) => Promise, removeUserFromGroup: (userID: string, threadID: string, callback?: (err?: Error) => void) => Promise, resolvePhotoUrl: (photoID: string, callback?: (err: Error | null, url: string) => void) => Promise, sendMessage: typeof sendMessage, sendTypingIndicator: (threadID: string, callback?: (err?: Error) => void) => Promise, + sendTypingIndicatorMqtt: (isTyping: boolean, threadID: string, callback?: (err?: Error) => void) => Promise, setMessageReaction: (reaction: string, messageID: string, callback?: (err?: Error) => void, forceCustomReaction?: boolean) => Promise, + setMessageReactionMqtt: (reaction: string, messageID: string, threadID: string, callback?: (err?: Error) => void) => Promise, setOptions: (options: Partial) => void, setTitle: (newTitle: string, threadID: string, callback?: (err?: Error) => void) => Promise, + setTheme: (themeID?: string, threadID: string, callback?: (err?: Error) => void) => Promise, unsendMessage: (messageID: string, callback?: (err?: Error) => void) => Promise, - editMessage: (text: string, messageID: string, callback?: (err?: Error) => void) => Promise + unsendMessageMqtt: (messageID: string, threadID: string, callback?: (err?: Error) => void) => Promise, } export type IFCAU_ListenMessage = diff --git a/index.js b/index.js index ec0915d5..111ff0a5 100644 --- a/index.js +++ b/index.js @@ -207,15 +207,19 @@ function buildAPI(globalOptions, html, jar) { "changeArchivedStatus", "changeBio", "changeBlockedStatus", + "changeBlockedStatusMqtt", "changeGroupImage", "changeNickname", "changeThreadColor", "changeThreadEmoji", "createNewGroup", "createPoll", + "createPollMqtt", "deleteMessage", "deleteThread", + "editMessage", "forwardAttachment", + "forwardMessage", "getCurrentUserID", "getEmojiUrl", "getFriendsList", @@ -233,17 +237,21 @@ function buildAPI(globalOptions, html, jar) { "markAsReadAll", "markAsSeen", "muteThread", + "pinMessage", "removeUserFromGroup", "resolvePhotoUrl", "searchForThread", "sendMessage", "sendTypingIndicator", + "sendTypingIndicatorMqtt", "setMessageReaction", + "setMessageReactionMqtt", "setTitle", + "setTheme", "threadColors", "unsendMessage", + "unsendMessageMqtt", "unfriend", - "editMessage", // HTTP "httpGet", diff --git a/src/changeBlockedStatusMqtt.js b/src/changeBlockedStatusMqtt.js new file mode 100644 index 00000000..bd695c77 --- /dev/null +++ b/src/changeBlockedStatusMqtt.js @@ -0,0 +1,79 @@ +'use strict'; + +const { generateOfflineThreadingID, getCurrentTimestamp, getGUID } = require('@xaviabot/fca-unofficial/utils'); + +function isCallable(func) { + try { + Reflect.apply(func, null, []); + return true; + } catch (error) { + return false; + } +} + +module.exports = function (defaultFuncs, api, ctx) { + return function changeBlockedStatusMqtt(userID, status, type, callback) { + if (!ctx.mqttClient) { + throw new Error('Not connected to MQTT'); + } + + ctx.wsReqNumber += 1; + ctx.wsTaskNumber += 1; + + const label = '334'; + let userBlockAction = 0; + + switch (type) { + case 'messenger': + if (status) { + userBlockAction = 1; // Block + } else { + userBlockAction = 0; // Unblock + } + break; + case 'facebook': + if (status) { + userBlockAction = 3; // Block + } else { + userBlockAction = 2; // Unblock + } + break; + default: + throw new Error('Invalid type'); + } + + const taskPayload = { + blockee_id: userID, + request_id: getGUID(), + user_block_action: userBlockAction, + }; + + const payload = JSON.stringify(taskPayload); + const version = '25393437286970779'; + + const task = { + failure_count: null, + label: label, + payload: payload, + queue_name: 'native_sync_block', + task_id: ctx.wsTaskNumber, + }; + + const content = { + app_id: '2220391788200892', + payload: JSON.stringify({ + tasks: [task], + epoch_id: parseInt(generateOfflineThreadingID()), + version_id: version, + }), + request_id: ctx.wsReqNumber, + type: 3, + }; + + if (isCallable(callback)) { + ctx.reqCallbacks[ctx.wsReqNumber] = callback; + } + + ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false }); + }; +}; diff --git a/src/createPollMqtt.js b/src/createPollMqtt.js new file mode 100644 index 00000000..444b93c3 --- /dev/null +++ b/src/createPollMqtt.js @@ -0,0 +1,56 @@ +'use strict'; + +const { generateOfflineThreadingID, getCurrentTimestamp } = require('@xaviabot/fca-unofficial/utils'); + +function isCallable(func) { + try { + Reflect.apply(func, null, []); + return true; + } catch (error) { + return false; + } +} + +module.exports = function (defaultFuncs, api, ctx) { + return function createPollMqtt(title, options, threadID, callback) { + if (!ctx.mqttClient) { + throw new Error('Not connected to MQTT'); + } + + ctx.wsReqNumber += 1; + ctx.wsTaskNumber += 1; + + const taskPayload = { + question_text: title, + thread_key: threadID, + options: options, + sync_group: 1, + }; + + const task = { + failure_count: null, + label: '163', + payload: JSON.stringify(taskPayload), + queue_name: 'poll_creation', + task_id: ctx.wsTaskNumber, + }; + + const content = { + app_id: '2220391788200892', + payload: JSON.stringify({ + data_trace_id: null, + epoch_id: parseInt(generateOfflineThreadingID()), + tasks: [task], + version_id: '7158486590867448', + }), + request_id: ctx.wsReqNumber, + type: 3, + }; + + if (isCallable(callback)) { + ctx.reqCallbacks[ctx.wsReqNumber] = callback; + } + + ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false }); + }; +}; diff --git a/src/forwardMessage.js b/src/forwardMessage.js new file mode 100644 index 00000000..d70a86b2 --- /dev/null +++ b/src/forwardMessage.js @@ -0,0 +1,60 @@ +'use strict'; + +const { generateOfflineThreadingID, getCurrentTimestamp } = require('../utils'); + +function isCallable(func) { + try { + Reflect.apply(func, null, []); + return true; + } catch (error) { + return false; + } +} + +module.exports = function (defaultFuncs, api, ctx) { + return function forwardMessage(messageID, threadID, callback) { + if (!ctx.mqttClient) { + throw new Error('Not connected to MQTT'); + } + + ctx.wsReqNumber += 1; + ctx.wsTaskNumber += 1; + + const taskPayload = { + thread_id: threadID, + otid: parseInt(generateOfflineThreadingID()), + source: 65544, + send_type: 5, + sync_group: 1, + forwarded_msg_id: messageID, + strip_forwarded_msg_caption: 0, + initiating_source: 1, + }; + + const task = { + failure_count: null, + label: '46', + payload: JSON.stringify(taskPayload), + queue_name: `${threadID}`, + task_id: ctx.wsTaskNumber, + }; + + const content = { + app_id: '2220391788200892', + payload: JSON.stringify({ + data_trace_id: null, + epoch_id: parseInt(generateOfflineThreadingID()), + tasks: [task], + version_id: '25095469420099952', + }), + request_id: ctx.wsReqNumber, + type: 3, + }; + + if (isCallable(callback)) { + ctx.reqCallbacks[ctx.wsReqNumber] = callback; + } + + ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false }); + }; +}; diff --git a/src/pinMessage.js b/src/pinMessage.js new file mode 100644 index 00000000..9edda55f --- /dev/null +++ b/src/pinMessage.js @@ -0,0 +1,58 @@ +'use strict'; + +const { generateOfflineThreadingID, getCurrentTimestamp } = require('../utils'); + +function isCallable(func) { + try { + Reflect.apply(func, null, []); + return true; + } catch (error) { + return false; + } +} + +module.exports = function (defaultFuncs, api, ctx) { + return function pinMessage(pinMode, messageID, threadID, callback) { + if (!ctx.mqttClient) { + throw new Error('Not connected to MQTT'); + } + + ctx.wsReqNumber += 1; + ctx.wsTaskNumber += 1; + + const taskLabel = pinMode ? '430' : '431'; + const queueNamePrefix = pinMode ? 'pin_msg_v2_' : 'unpin_msg_v2_'; + + const taskPayload = { + thread_key: threadID, + message_id: messageID, + timestamp_ms: getCurrentTimestamp(), + }; + + const task = { + failure_count: null, + label: taskLabel, + payload: JSON.stringify(taskPayload), + queue_name: `${queueNamePrefix}${threadID}`, + task_id: ctx.wsTaskNumber, + }; + + const content = { + app_id: '2220391788200892', + payload: JSON.stringify({ + data_trace_id: null, + epoch_id: parseInt(generateOfflineThreadingID()), + tasks: [task], + version_id: '25095469420099952', + }), + request_id: ctx.wsReqNumber, + type: 3, + }; + + if (isCallable(callback)) { + ctx.reqCallbacks[ctx.wsReqNumber] = callback; + } + + ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false }); + }; +}; diff --git a/src/sendTypingIndicatorMqtt.js b/src/sendTypingIndicatorMqtt.js new file mode 100644 index 00000000..1dea3294 --- /dev/null +++ b/src/sendTypingIndicatorMqtt.js @@ -0,0 +1,57 @@ +'use strict'; + +const { generateOfflineThreadingID, getCurrentTimestamp } = require('../utils'); + +function isCallable(func) { + try { + Reflect.apply(func, null, []); + return true; + } catch (error) { + return false; + } +} + +module.exports = function (defaultFuncs, api, ctx) { + return function sendTypingIndicatorMqtt(isTyping, threadID, callback) { + if (!ctx.mqttClient) { + throw new Error('Not connected to MQTT'); + } + + ctx.wsReqNumber += 1; + + api.getThreadInfo(threadID).then(threadData => { + const label = '3'; + const isGroupThread = threadData.isGroup ? 1 : 0; + const attribution = 0; + + const taskPayload = { + thread_key: threadID, + is_group_thread: isGroupThread, + is_typing: isTyping ? 1 : 0, + attribution: attribution, + }; + + const payload = JSON.stringify(taskPayload); + const version = '25393437286970779'; + + const content = { + app_id: '2220391788200892', + payload: JSON.stringify({ + label: label, + payload: payload, + version: version, + }), + request_id: ctx.wsReqNumber, + type: 4, + }; + + if (isCallable(callback)) { + ctx.reqCallbacks[ctx.wsReqNumber] = callback; + } + + ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false }); + }).catch(error => { + throw new Error('Failed to get thread info'); + }); + }; +}; diff --git a/src/setMessageReactionMqtt.js b/src/setMessageReactionMqtt.js new file mode 100644 index 00000000..a93bea0a --- /dev/null +++ b/src/setMessageReactionMqtt.js @@ -0,0 +1,60 @@ +'use strict'; + +const { generateOfflineThreadingID, getCurrentTimestamp } = require('../utils'); + +function isCallable(func) { + try { + Reflect.apply(func, null, []); + return true; + } catch (error) { + return false; + } +} + +module.exports = function (defaultFuncs, api, ctx) { + return function setMessageReactionMqtt(reaction, messageID, threadID, callback) { + if (!ctx.mqttClient) { + throw new Error('Not connected to MQTT'); + } + + ctx.wsReqNumber += 1; + ctx.wsTaskNumber += 1; + + const taskPayload = { + thread_key: threadID, + timestamp_ms: getCurrentTimestamp(), + message_id: messageID, + reaction: reaction, + actor_id: ctx.userID, + reaction_style: null, + sync_group: 1, + send_attribution: Math.random() < 0.5 ? 65537 : 524289 + }; + + const task = { + failure_count: null, + label: '29', + payload: JSON.stringify(taskPayload), + queue_name: JSON.stringify(['reaction', messageID]), + task_id: ctx.wsTaskNumber, + }; + + const content = { + app_id: '2220391788200892', + payload: JSON.stringify({ + data_trace_id: null, + epoch_id: parseInt(generateOfflineThreadingID()), + tasks: [task], + version_id: '7158486590867448', + }), + request_id: ctx.wsReqNumber, + type: 3, + }; + + if (isCallable(callback)) { + ctx.reqCallbacks[ctx.wsReqNumber] = callback; + } + + ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false }); + }; +}; diff --git a/src/setTheme.js b/src/setTheme.js new file mode 100644 index 00000000..0cad208e --- /dev/null +++ b/src/setTheme.js @@ -0,0 +1,310 @@ +'use strict'; + +const { generateOfflineThreadingID, getCurrentTimestamp } = require('../utils'); + +function isCallable(func) { + try { + Reflect.apply(func, null, []); + return true; + } catch (error) { + return false; + } +} + +const themes = [ + { + "id": "3650637715209675", + "name": "Besties" + }, + { + "id": "769656934577391", + "name": "Women's History Month" + }, + { + "id": "702099018755409", + "name": "Dune: Part Two" + }, + { + "id": "1480404512543552", + "name": "Avatar: The Last Airbender" + }, + { + "id": "952656233130616", + "name": "J.Lo" + }, + { + "id": "741311439775765", + "name": "Love" + }, + { + "id": "215565958307259", + "name": "Bob Marley: One Love" + }, + { + "id": "194982117007866", + "name": "Football" + }, + { + "id": "1743641112805218", + "name": "Soccer" + }, + { + "id": "730357905262632", + "name": "Mean Girls" + }, + { + "id": "1270466356981452", + "name": "Wonka" + }, + { + "id": "704702021720552", + "name": "Pizza" + }, + { + "id": "1013083536414851", + "name": "Wish" + }, + { + "id": "359537246600743", + "name": "Trolls" + }, + { + "id": "173976782455615", + "name": "The Marvels" + }, + { + "id": "2317258455139234", + "name": "One Piece" + }, + { + "id": "6685081604943977", + "name": "1989" + }, + { + "id": "1508524016651271", + "name": "Avocado" + }, + { + "id": "265997946276694", + "name": "Loki Season 2" + }, + { + "id": "6584393768293861", + "name": "olivia rodrigo" + }, + { + "id": "845097890371902", + "name": "Baseball" + }, + { + "id": "292955489929680", + "name": "Lollipop" + }, + { + "id": "976389323536938", + "name": "Loops" + }, + { + "id": "810978360551741", + "name": "Parenthood" + }, + { + "id": "195296273246380", + "name": "Bubble Tea" + }, + { + "id": "6026716157422736", + "name": "Basketball" + }, + { + "id": "693996545771691", + "name": "Elephants & Flowers" + }, + { + "id": "390127158985345", + "name": "Chill" + }, + { + "id": "365557122117011", + "name": "Support" + }, + { + "id": "339021464972092", + "name": "Music" + }, + { + "id": "1060619084701625", + "name": "Lo-Fi" + }, + { + "id": "3190514984517598", + "name": "Sky" + }, + { + "id": "627144732056021", + "name": "Celebration" + }, + { + "id": "275041734441112", + "name": "Care" + }, + { + "id": "3082966625307060", + "name": "Astrology" + }, + { + "id": "539927563794799", + "name": "Cottagecore" + }, + { + "id": "527564631955494", + "name": "Ocean" + }, + { + "id": "230032715012014", + "name": "Tie-Dye" + }, + { + "id": "788274591712841", + "name": "Monochrome" + }, + { + "id": "3259963564026002", + "name": "Default" + }, + { + "id": "724096885023603", + "name": "Berry" + }, + { + "id": "624266884847972", + "name": "Candy" + }, + { + "id": "273728810607574", + "name": "Unicorn" + }, + { + "id": "262191918210707", + "name": "Tropical" + }, + { + "id": "2533652183614000", + "name": "Maple" + }, + { + "id": "909695489504566", + "name": "Sushi" + }, + { + "id": "582065306070020", + "name": "Rocket" + }, + { + "id": "557344741607350", + "name": "Citrus" + }, + { + "id": "280333826736184", + "name": "Lollipop" + }, + { + "id": "271607034185782", + "name": "Shadow" + }, + { + "id": "1257453361255152", + "name": "Rose" + }, + { + "id": "571193503540759", + "name": "Lavender" + }, + { + "id": "2873642949430623", + "name": "Tulip" + }, + { + "id": "3273938616164733", + "name": "Classic" + }, + { + "id": "403422283881973", + "name": "Apple" + }, + { + "id": "3022526817824329", + "name": "Peach" + }, + { + "id": "672058580051520", + "name": "Honey" + }, + { + "id": "3151463484918004", + "name": "Kiwi" + }, + { + "id": "736591620215564", + "name": "Ocean" + }, + { + "id": "193497045377796", + "name": "Grape" + } + ]; + + module.exports = function (defaultFuncs, api, ctx) { + return function setTheme(themeID, threadID, callback) { + if (!ctx.mqttClient) { + throw new Error('Not connected to MQTT'); + } + + ctx.wsReqNumber += 1; + ctx.wsTaskNumber += 1; + + let selectedThemeID; + + if (!themeID) { + // If no theme ID is provided, select a random theme from the themes array + const randomIndex = Math.floor(Math.random() * themes.length); + selectedThemeID = themes[randomIndex].id; + } else { + selectedThemeID = themeID; + } + + const taskPayload = { + thread_key: threadID, + theme_fbid: selectedThemeID, + source: null, + sync_group: 1, + payload: null, + }; + + const task = { + failure_count: null, + label: '43', + payload: JSON.stringify(taskPayload), + queue_name: 'thread_theme', + task_id: ctx.wsTaskNumber, + }; + + const content = { + app_id: '2220391788200892', + payload: JSON.stringify({ + data_trace_id: null, + epoch_id: parseInt(generateOfflineThreadingID()), + tasks: [task], + version_id: '25095469420099952', + }), + request_id: ctx.wsReqNumber, + type: 3, + }; + + if (isCallable(callback)) { + ctx.reqCallbacks[ctx.wsReqNumber] = callback; + } + + ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false }); + }; + }; diff --git a/src/unsendMessageMqtt.js b/src/unsendMessageMqtt.js new file mode 100644 index 00000000..fb36eb95 --- /dev/null +++ b/src/unsendMessageMqtt.js @@ -0,0 +1,59 @@ +'use strict'; + +const { generateOfflineThreadingID, getCurrentTimestamp } = require('../utils'); + +function isCallable(func) { + try { + Reflect.apply(func, null, []); + return true; + } catch (error) { + return false; + } +} + +module.exports = function (defaultFuncs, api, ctx) { + return function unsendMessageMqtt(messageID, threadID, callback) { + if (!ctx.mqttClient) { + throw new Error('Not connected to MQTT'); + } + + ctx.wsReqNumber += 1; + ctx.wsTaskNumber += 1; + + const label = '33'; + + const taskPayload = { + message_id: messageID, + thread_key: threadID, + sync_group: 1, + }; + + const payload = JSON.stringify(taskPayload); + const version = '25393437286970779'; + + const task = { + failure_count: null, + label: label, + payload: payload, + queue_name: 'unsend_message', + task_id: ctx.wsTaskNumber, + }; + + const content = { + app_id: '2220391788200892', + payload: JSON.stringify({ + tasks: [task], + epoch_id: parseInt(generateOfflineThreadingID()), + version_id: version, + }), + request_id: ctx.wsReqNumber, + type: 3, + }; + + if (isCallable(callback)) { + ctx.reqCallbacks[ctx.wsReqNumber] = callback; + } + + ctx.mqttClient.publish('/ls_req', JSON.stringify(content), { qos: 1, retain: false }); + }; +}; diff --git a/utils.js b/utils.js index d48335b7..b07225ef 100644 --- a/utils.js +++ b/utils.js @@ -120,6 +120,12 @@ function generateThreadingID(clientID) { return "<" + k + ":" + l + "-" + m + "@mail.projektitan.com>"; } +function getCurrentTimestamp() { + const date = new Date(); + const unixTime = date.getTime(); + return unixTime; +} + function binaryToDecimal(data) { var ret = ""; while (data !== "0") { @@ -1403,6 +1409,6 @@ module.exports = { decodeClientPayload, getAppState, getAdminTextMessageType, - setProxy + setProxy, + getCurrentTimestamp }; -