@lazyneoaz/metachat 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyneoaz/metachat",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "type": "commonjs",
5
5
  "types": "src/types/index.d.ts",
6
6
  "description": "Advanced Facebook Chat API client for building Messenger bots — real-time messaging, thread management, MQTT, and session stability.",
@@ -2,10 +2,6 @@
2
2
 
3
3
  const utils = require('../utils');
4
4
 
5
- function generateOfflineThreadingID() {
6
- return Date.now().toString() + Math.floor(Math.random() * 1000000).toString();
7
- }
8
-
9
5
  function getType(obj) {
10
6
  return Object.prototype.toString.call(obj).slice(8, -1);
11
7
  }
@@ -39,7 +35,7 @@ module.exports = function (defaultFuncs, api, ctx) {
39
35
  if (ctx.mqttClient) {
40
36
  const tasks = [];
41
37
  const isAdmin = adminStatus ? 1 : 0;
42
- const epochID = generateOfflineThreadingID();
38
+ const epochID = utils.generateOfflineThreadingID();
43
39
 
44
40
  if (getType(adminID) === "Array") {
45
41
  adminID.forEach((id, index) => {
@@ -93,7 +89,7 @@ module.exports = function (defaultFuncs, api, ctx) {
93
89
  } else {
94
90
  utils.warn("MQTT client not available, using HTTP fallback for changeAdminStatus");
95
91
  const tasks = [];
96
- const epochID = generateOfflineThreadingID();
92
+ const epochID = utils.generateOfflineThreadingID();
97
93
 
98
94
  if (getType(adminID) === "Array") {
99
95
  adminID.forEach((id, index) => {
@@ -61,7 +61,7 @@ module.exports = (defaultFuncs, api, ctx) => {
61
61
  type: 3
62
62
  });
63
63
 
64
- ctx.mqttClient.publish("/ls_req", form);
64
+ ctx.mqttClient.publish("/ls_req", form, { qos: 1, retain: false });
65
65
  callback(null, { success: true });
66
66
  } catch (err) {
67
67
  utils.error("createPoll", err);
@@ -4,26 +4,72 @@ const utils = require('../utils');
4
4
  const { _formatAttachment } = require('../utils/formatters/data/formatAttachment');
5
5
 
6
6
  const THEME_COLORS = [
7
- { theme_color: "FF000000", theme_id: "788274591712841", theme_emoji: "🖤", gradient: '["FFF0F0F0"]', should_show_icon: "", theme_name_with_subtitle: "Monochrome" },
8
- { theme_color: "FFFF5CA1", theme_id: "169463077092846", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Hot Pink" },
9
- { theme_color: "FF2825B5", theme_id: "271607034185782", theme_emoji: null, gradient: '["FF5E007E","FF331290","FF2825B5"]', should_show_icon: "1", theme_name_with_subtitle: "Shadow" },
10
- { theme_color: "FFD9A900", theme_id: "2533652183614000", theme_emoji: null, gradient: '["FF550029","FFAA3232","FFD9A900"]', should_show_icon: "1", theme_name_with_subtitle: "Maple" },
11
- { theme_color: "FFFB45DE", theme_id: "2873642949430623", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Tulip" },
12
- { theme_color: "FF5E007E", theme_id: "193497045377796", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Grape" },
13
- { theme_color: "FF7AA286", theme_id: "1455149831518874", theme_emoji: "🌑", gradient: '["FF25C0E1","FFCE832A"]', should_show_icon: "", theme_name_with_subtitle: "Dune" },
14
- { theme_color: "FFFAAF00", theme_id: "672058580051520", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Honey" },
15
- { theme_color: "FF0084FF", theme_id: "196241301102133", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Default Blue" },
16
- { theme_color: "FFFFC300", theme_id: "174636906462322", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Yellow" },
17
- { theme_color: "FF44BEC7", theme_id: "1928399724138152", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Teal Blue" },
18
- { theme_color: "FF7646FF", theme_id: "234137870477637", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Bright Purple" },
19
- { theme_color: "FFF25C54", theme_id: "3022526817824329", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Peach" },
20
- { theme_color: "FFF01D6A", theme_id: "724096885023603", theme_emoji: null, gradient: '["FF005FFF","FF9200FF","FFFF2E19"]', should_show_icon: "1", theme_name_with_subtitle: "Berry" },
21
- { theme_color: "FFFF7CA8", theme_id: "624266884847972", theme_emoji: null, gradient: '["FFFF8FB2","FFA797FF","FF00E5FF"]', should_show_icon: "1", theme_name_with_subtitle: "Candy" },
22
- { theme_color: "FF0084FF", theme_id: "196241301102133", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Classic" },
23
- { theme_color: "FF0099FF", theme_id: "3273938616164733", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Classic" },
24
- { theme_color: "FFFA3C4C", theme_id: "2129984390566328", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Red" },
25
- { theme_color: "FF13CF13", theme_id: "2136751179887052", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Green" },
26
- { theme_color: "FFFF7E29", theme_id: "175615189761153", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Orange" }
7
+ // ── Core solid colours ────────────────────────────────────────────────────
8
+ { theme_color: "FF0084FF", theme_id: "196241301102133", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Default Blue" },
9
+ { theme_color: "FF0099FF", theme_id: "3273938616164733", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Classic" },
10
+ { theme_color: "FF44BEC7", theme_id: "1928399724138152", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Teal Blue" },
11
+ { theme_color: "FFFFC300", theme_id: "174636906462322", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Yellow" },
12
+ { theme_color: "FFFA3C4C", theme_id: "2129984390566328", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Red" },
13
+ { theme_color: "FF7646FF", theme_id: "234137870477637", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Bright Purple" },
14
+ { theme_color: "FF13CF13", theme_id: "2136751179887052", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Green" },
15
+ { theme_color: "FFFF7E29", theme_id: "175615189761153", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Orange" },
16
+ { theme_color: "FFFF5CA1", theme_id: "169463077092846", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Hot Pink" },
17
+ { theme_color: "FF25D366", theme_id: "2442142322678320", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Deep Sky Blue" },
18
+ { theme_color: "FF7646FF", theme_id: "2058653964378557", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Lavender Purple" },
19
+ { theme_color: "FFFF4500", theme_id: "980963458735625", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Light Coral" },
20
+ { theme_color: "FF00C9C9", theme_id: "417639218648241", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Aqua" },
21
+ // ── Named / gradient themes ───────────────────────────────────────────────
22
+ { theme_color: "FF000000", theme_id: "788274591712841", theme_emoji: "🖤", gradient: '["FFF0F0F0"]', should_show_icon: "", theme_name_with_subtitle: "Monochrome" },
23
+ { theme_color: "FF2825B5", theme_id: "271607034185782", theme_emoji: null, gradient: '["FF5E007E","FF331290","FF2825B5"]', should_show_icon: "1", theme_name_with_subtitle: "Shadow" },
24
+ { theme_color: "FFD9A900", theme_id: "2533652183614000", theme_emoji: null, gradient: '["FF550029","FFAA3232","FFD9A900"]', should_show_icon: "1", theme_name_with_subtitle: "Maple" },
25
+ { theme_color: "FFFB45DE", theme_id: "2873642949430623", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Tulip" },
26
+ { theme_color: "FF5E007E", theme_id: "193497045377796", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Grape" },
27
+ { theme_color: "FF7AA286", theme_id: "1455149831518874", theme_emoji: "🌑", gradient: '["FF25C0E1","FFCE832A"]', should_show_icon: "", theme_name_with_subtitle: "Dune" },
28
+ { theme_color: "FFFAAF00", theme_id: "672058580051520", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Honey" },
29
+ { theme_color: "FFF25C54", theme_id: "3022526817824329", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Peach" },
30
+ { theme_color: "FFF01D6A", theme_id: "724096885023603", theme_emoji: null, gradient: '["FF005FFF","FF9200FF","FFFF2E19"]', should_show_icon: "1", theme_name_with_subtitle: "Berry" },
31
+ { theme_color: "FFFF7CA8", theme_id: "624266884847972", theme_emoji: null, gradient: '["FFFF8FB2","FFA797FF","FF00E5FF"]', should_show_icon: "1", theme_name_with_subtitle: "Candy" },
32
+ { theme_color: "FF930099", theme_id: "930060997172551", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Mango" },
33
+ { theme_color: "FF4267B2", theme_id: "164535220883264", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Berry" },
34
+ { theme_color: "FF00C400", theme_id: "370940413392601", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Citrus" },
35
+ { theme_color: "FF50C878", theme_id: "557344741607350", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Citrus 2" },
36
+ { theme_color: "FFFF0000", theme_id: "205488546921017", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Candy" },
37
+ { theme_color: "FF8B4513", theme_id: "1833559466821043", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Earth" },
38
+ { theme_color: "FF0084FF", theme_id: "365557122117011", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Support" },
39
+ { theme_color: "FFFF6B6B", theme_id: "339021464972092", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Music" },
40
+ { theme_color: "FFFF69B4", theme_id: "1652456634878319", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Pride" },
41
+ { theme_color: "FF8B0000", theme_id: "538280997628317", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Doctor Strange" },
42
+ { theme_color: "FF6C63FF", theme_id: "1060619084701625", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Lo-Fi" },
43
+ { theme_color: "FF87CEEB", theme_id: "3190514984517598", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Sky" },
44
+ { theme_color: "FFFF4500", theme_id: "357833546030778", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Lunar New Year" },
45
+ { theme_color: "FFFF6347", theme_id: "627144732056021", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Celebration" },
46
+ { theme_color: "FF4682B4", theme_id: "390127158985345", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Chill" },
47
+ { theme_color: "FFFF0000", theme_id: "1059859811490132", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Stranger Things" },
48
+ { theme_color: "FFD4A574", theme_id: "275041734441112", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Care" },
49
+ { theme_color: "FF9B59B6", theme_id: "3082966625307060", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Astrology" },
50
+ { theme_color: "FFFF8C00", theme_id: "184305226956268", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "J Balvin" },
51
+ { theme_color: "FFFF69B4", theme_id: "621630955405500", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Birthday" },
52
+ { theme_color: "FF228B22", theme_id: "539927563794799", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Cottagecore" },
53
+ { theme_color: "FF006994", theme_id: "736591620215564", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Ocean" },
54
+ { theme_color: "FFFF1493", theme_id: "741311439775765", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Love" },
55
+ { theme_color: "FFFF7F7F", theme_id: "230032715012014", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Tie Dye" },
56
+ { theme_color: "FF808080", theme_id: "262191918210707", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Tropical" },
57
+ { theme_color: "FF228B22", theme_id: "909695489504566", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Sushi" },
58
+ { theme_color: "FFFF69B4", theme_id: "280333826736184", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Lollipop" },
59
+ { theme_color: "FFFF007F", theme_id: "1257453361255152", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Rose" },
60
+ { theme_color: "FFE6E6FA", theme_id: "571193503540759", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Lavender" },
61
+ { theme_color: "FFFFC0CB", theme_id: "3151463484918004", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Kiwi" },
62
+ { theme_color: "FF6F2DA8", theme_id: "810978360551741", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Parenthood" },
63
+ { theme_color: "FF4169E1", theme_id: "1438011086532622", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Star Wars" },
64
+ { theme_color: "FF6B8E23", theme_id: "101275642962533", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Guardians of the Galaxy" },
65
+ { theme_color: "FFFF69B4", theme_id: "158263147151440", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Bloom" },
66
+ { theme_color: "FF9B59B6", theme_id: "195296273246380", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Bubble Tea" },
67
+ { theme_color: "FFFF8C00", theme_id: "6026716157422736", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Basketball" },
68
+ { theme_color: "FF4B0082", theme_id: "737761000603635", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Non-Binary" },
69
+ { theme_color: "FF55CDFC", theme_id: "504518465021637", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Transgender" },
70
+ { theme_color: "FFFC0080", theme_id: "769129927636836", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Taylor Swift" },
71
+ { theme_color: "FFFF7700", theme_id: "822549609168155", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Autumn" },
72
+ { theme_color: "FFFF007F", theme_id: "693996545771691", theme_emoji: null, gradient: null, should_show_icon: "1", theme_name_with_subtitle: "Elephants and Flowers" },
27
73
  ];
28
74
 
29
75
  function formatMessage(threadID, data) {
@@ -63,20 +109,28 @@ function formatMessage(threadID, data) {
63
109
  const adminType = data.extensible_message_admin_text_type;
64
110
 
65
111
  if (adminType === "CHANGE_THREAD_THEME") {
66
- const themeColor = data.extensible_message_admin_text.theme_color;
67
- const colorMatch = THEME_COLORS.find(color => color.theme_color === themeColor);
68
-
112
+ const ext = data.extensible_message_admin_text || {};
113
+ const themeColor = ext.theme_color || null;
114
+ // Prefer the theme_id field returned directly by GraphQL.
115
+ // Fall back to a reverse lookup in THEME_COLORS for older API
116
+ // responses that omit theme_id.
117
+ const directThemeId = ext.theme_id || ext.theme_fbid || null;
118
+ const colorMatch = directThemeId
119
+ ? THEME_COLORS.find(c => c.theme_id === directThemeId)
120
+ : (themeColor ? THEME_COLORS.find(c => c.theme_color === themeColor) : null);
121
+
69
122
  return {
70
123
  ...baseMessage,
71
124
  type: "event",
72
125
  logMessageType: "log:thread-color",
73
- logMessageData: colorMatch || {
126
+ logMessageData: {
74
127
  theme_color: themeColor,
75
- theme_id: null,
76
- theme_emoji: null,
77
- gradient: null,
78
- should_show_icon: null,
79
- theme_name_with_subtitle: null
128
+ theme_id: directThemeId || (colorMatch ? colorMatch.theme_id : null),
129
+ theme_fbid: directThemeId || (colorMatch ? colorMatch.theme_id : null),
130
+ theme_emoji: ext.theme_emoji || (colorMatch ? colorMatch.theme_emoji : null),
131
+ gradient: ext.gradient || (colorMatch ? colorMatch.gradient : null),
132
+ should_show_icon: ext.should_show_icon != null ? ext.should_show_icon : (colorMatch ? colorMatch.should_show_icon : null),
133
+ theme_name_with_subtitle: ext.theme_name_with_subtitle || (colorMatch ? colorMatch.theme_name_with_subtitle : null)
80
134
  },
81
135
  logMessageBody: data.snippet
82
136
  };
@@ -84,15 +84,19 @@ module.exports = function (defaultFuncs, api, ctx) {
84
84
 
85
85
  const info = Array.isArray(threadInfo) ? threadInfo[0] : threadInfo;
86
86
 
87
+ // theme_id comes from threadTheme.id (extracted at getThreadInfo level)
88
+ const themeId = info.theme_id || (info.threadTheme && info.threadTheme.id) || info.themeID || null;
87
89
  const themeInfo = {
88
90
  threadID: identifier,
89
91
  threadName: info.threadName || info.name || '',
90
92
  color: info.color || null,
91
93
  emoji: info.emoji || '👍',
92
- theme_id: info.theme_id || info.themeID || null,
94
+ theme_id: themeId,
95
+ theme_fbid: themeId,
93
96
  theme_color: info.theme_color || info.color || null,
94
97
  gradient_colors: info.gradient_colors || null,
95
- is_default: !info.color && !info.theme_id
98
+ threadTheme: info.threadTheme || null,
99
+ is_default: !info.color && !themeId
96
100
  };
97
101
 
98
102
  if (callback) {
@@ -203,7 +203,7 @@ module.exports = function (defaultFuncs, api, ctx) {
203
203
  }
204
204
 
205
205
  const form = {
206
- av: ctx.globalOptions.pageID,
206
+ av: ctx.globalOptions.pageID || ctx.userID,
207
207
  queries: JSON.stringify({
208
208
  o0: {
209
209
  doc_id: "1498317363570230",
@@ -112,12 +112,22 @@ function formatThreadGraphQLResponse(data) {
112
112
  emoji: messageThread.customization_info
113
113
  ? messageThread.customization_info.emoji
114
114
  : null,
115
- color:
116
- messageThread.customization_info &&
117
- messageThread.customization_info.outgoing_bubble_color
118
- ? messageThread.customization_info.outgoing_bubble_color.slice(2)
119
- : null,
115
+ color: (function() {
116
+ const raw = messageThread.customization_info &&
117
+ messageThread.customization_info.outgoing_bubble_color;
118
+ if (!raw) return null;
119
+ const s = String(raw);
120
+ // Format is FFRRGGBB (8 hex chars, ARGB). Strip the FF alpha prefix.
121
+ // Validate before slicing to avoid returning garbage for unexpected formats.
122
+ if (/^[0-9a-fA-F]{8}$/.test(s)) return s.slice(2);
123
+ // Handle #RRGGBB or #AARRGGBB
124
+ if (/^#[0-9a-fA-F]{6}$/.test(s)) return s.slice(1);
125
+ if (/^#[0-9a-fA-F]{8}$/.test(s)) return s.slice(3);
126
+ // Fallback: return as-is
127
+ return s;
128
+ })(),
120
129
  threadTheme: messageThread.thread_theme,
130
+ theme_id: messageThread.thread_theme ? (messageThread.thread_theme.id || null) : null,
121
131
  nicknames:
122
132
  messageThread.customization_info &&
123
133
  messageThread.customization_info.participant_customizations
@@ -102,12 +102,18 @@ function formatThreadGraphQLResponse(messageThread) {
102
102
  emoji: messageThread.customization_info
103
103
  ? messageThread.customization_info.emoji
104
104
  : null,
105
- color:
106
- messageThread.customization_info &&
107
- messageThread.customization_info.outgoing_bubble_color
108
- ? messageThread.customization_info.outgoing_bubble_color.slice(2)
109
- : null,
105
+ color: (function() {
106
+ const raw = messageThread.customization_info &&
107
+ messageThread.customization_info.outgoing_bubble_color;
108
+ if (!raw) return null;
109
+ const s = String(raw);
110
+ if (/^[0-9a-fA-F]{8}$/.test(s)) return s.slice(2);
111
+ if (/^#[0-9a-fA-F]{6}$/.test(s)) return s.slice(1);
112
+ if (/^#[0-9a-fA-F]{8}$/.test(s)) return s.slice(3);
113
+ return s;
114
+ })(),
110
115
  threadTheme: messageThread.thread_theme,
116
+ theme_id: messageThread.thread_theme ? (messageThread.thread_theme.id || null) : null,
111
117
  nicknames:
112
118
  messageThread.customization_info &&
113
119
  messageThread.customization_info.participant_customizations
@@ -449,15 +449,17 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
449
449
  }
450
450
  }
451
451
  } else if (topic === "/thread_typing" || topic === "/orca_typing_notifications") {
452
- const typ = {
453
- type: "typ",
454
- isTyping: !!jsonMessage.state,
455
- from: jsonMessage.sender_fbid.toString(),
456
- threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
457
- };
458
- globalCallback(null, typ);
452
+ if (ctx.globalOptions.listenTyping) {
453
+ const typ = {
454
+ type: "typ",
455
+ isTyping: !!jsonMessage.state,
456
+ from: jsonMessage.sender_fbid.toString(),
457
+ threadID: utils.formatID((jsonMessage.thread || jsonMessage.sender_fbid).toString())
458
+ };
459
+ globalCallback(null, typ);
460
+ }
459
461
  } else if (topic === "/orca_presence") {
460
- if (!ctx.globalOptions.updatePresence && jsonMessage.list) {
462
+ if (ctx.globalOptions.updatePresence && jsonMessage.list) {
461
463
  for (const data of jsonMessage.list) {
462
464
  globalCallback(null, {
463
465
  type: "presence",
@@ -50,23 +50,20 @@ module.exports = function (defaultFuncs, api, ctx) {
50
50
 
51
51
  callback(null);
52
52
  } else {
53
- if (!ctx.mqttClient || !ctx.mqttClient.connected) {
54
- const err = new Error("markAsRead requires an active MQTT connection. Call api.listenMqtt() first.");
55
- callback(err);
56
- return returnPromise;
57
- }
53
+ const form = {
54
+ ["ids[" + threadID + "]"]: read,
55
+ watermarkTimestamp: Date.now(),
56
+ shouldSendReadReceipt: true,
57
+ };
58
58
 
59
- const publishErr = await new Promise((r) =>
60
- ctx.mqttClient.publish(
61
- "/mark_thread",
62
- JSON.stringify({ threadID, mark: "read", state: read }),
63
- { qos: 1, retain: false },
64
- r,
65
- )
66
- );
59
+ const resData = await defaultFuncs
60
+ .post("https://www.facebook.com/ajax/mercury/change_read_status.php", ctx.jar, form)
61
+ .then(utils.saveCookies(ctx.jar))
62
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs));
67
63
 
68
- if (publishErr) {
69
- callback(publishErr instanceof Error ? publishErr : new Error(String(publishErr)));
64
+ if (resData && resData.error) {
65
+ const err = new Error(String(resData.error.message || resData.error));
66
+ callback(err);
70
67
  return returnPromise;
71
68
  }
72
69
 
@@ -183,7 +183,7 @@ function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
183
183
  } catch (err) {
184
184
  return;
185
185
  }
186
- if (!ctx.globalOptions.selfListen && fmtEvent2 && fmtEvent2.author && fmtEvent2.author.toString() === ctx.userID) return;
186
+ if (!ctx.globalOptions.selfListenEvent && fmtEvent2 && fmtEvent2.author && fmtEvent2.author.toString() === ctx.userID) return;
187
187
  if (!ctx.loggedIn) return;
188
188
  if (fmtEvent2) globalCallback(null, fmtEvent2);
189
189
  break;
@@ -193,7 +193,7 @@ function parseDelta(defaultFuncs, api, ctx, globalCallback, v) {
193
193
  var tid = v.delta.threadKey.threadFbId;
194
194
  if (mid && tid) {
195
195
  var form = {
196
- av: ctx.globalOptions.pageID,
196
+ av: ctx.globalOptions.pageID || ctx.userID,
197
197
  queries: JSON.stringify({
198
198
  o0: {
199
199
  doc_id: "2848441488556444",
@@ -99,7 +99,7 @@ module.exports = function (defaultFuncs, api, ctx) {
99
99
  };
100
100
 
101
101
  const context = {
102
- app_id: ctx.appID,
102
+ app_id: ctx.appID || "2220391788200892",
103
103
  payload: {
104
104
  epoch_id: parseInt(utils.generateOfflineThreadingID()),
105
105
  tasks: [query],
@@ -34,11 +34,13 @@ function extractAndSearchLightspeedRequest(allJsonData, options = {}) {
34
34
  }
35
35
 
36
36
 
37
- try {
38
- fs.writeFileSync(outputFile, JSON.stringify(lightReq, null, 2), "utf8");
39
- utils.log(`pin.js: Saved lightspeed_web_request to ${outputFile}`);
40
- } catch (err) {
41
- utils.error("pin.js: Failed to write lightspeed_web_request.json", err);
37
+ if (outputFile) {
38
+ try {
39
+ fs.writeFileSync(outputFile, JSON.stringify(lightReq, null, 2), "utf8");
40
+ utils.log(`pin.js: Saved lightspeed_web_request to ${outputFile}`);
41
+ } catch (err) {
42
+ utils.error("pin.js: Failed to write lightspeed_web_request.json", err);
43
+ }
42
44
  }
43
45
 
44
46
 
@@ -67,6 +67,7 @@ module.exports = (defaultFuncs, api, ctx) => {
67
67
  }
68
68
  if (jsonMsg.request_id !== reqID) return;
69
69
  responseHandled = true;
70
+ clearTimeout(timeout);
70
71
  ctx.mqttClient.removeListener("message", handleRes);
71
72
  callback(null, { success: true });
72
73
  resolveFunc({ success: true });
@@ -5,20 +5,21 @@ const utils = require('../utils');
5
5
 
6
6
  module.exports = function (defaultFuncs, api, ctx) {
7
7
  return async (reaction, messageID) => {
8
- if (!reaction) throw new Error("Please enter a valid emoji.");
9
- const defData = await defaultFuncs.postFormData("https://www.facebook.com/webgraphql/mutation/", ctx.jar, {}, {
8
+ if (reaction === undefined || reaction === null) throw new Error("Please enter a valid emoji.");
9
+ const action = reaction === "" ? "REMOVE_REACTION" : "ADD_REACTION";
10
+ const defData = await defaultFuncs.postFormData("https://www.facebook.com/webgraphql/mutation/", ctx.jar, {
10
11
  doc_id: "1491398900900362",
11
12
  variables: JSON.stringify({
12
- data: {
13
- client_mutation_id: ctx.clientMutationId++,
14
- actor_id: ctx.userID,
15
- action: reaction == "" ? "REMOVE_REACTION" : "ADD_REACTION",
16
- message_id: messageID,
17
- reaction
18
- }
19
- }),
13
+ data: {
14
+ client_mutation_id: ctx.clientMutationId++,
15
+ actor_id: ctx.userID,
16
+ action,
17
+ message_id: messageID,
18
+ reaction
19
+ }
20
+ }),
20
21
  dpr: 1
21
- });
22
+ }, {});
22
23
  const resData = await utils.parseAndCheckLogin(ctx, defaultFuncs)(defData);
23
24
  if (!resData) {
24
25
  throw new Error("setMessageReactionLegacy returned empty object.");
@@ -35,17 +35,17 @@ module.exports = function (defaultFuncs, api, ctx) {
35
35
 
36
36
  // ── Hardcoded colour aliases (fast path — no API round trip needed) ──────
37
37
  const PALETTE = {
38
- blue: "196241301102133",
39
- purple: "370940413392601",
40
- green: "169463077092846",
41
- pink: "230032715012014",
42
- orange: "175615189761153",
43
- red: "2136751179887052",
44
- yellow: "2058653964378557",
45
- teal: "417639218648241",
46
- black: "539927563794799",
47
- white: "2873642392710980",
48
- default: "196241301102133",
38
+ blue: "196241301102133", // DefaultBlue / MessengerBlue
39
+ purple: "234137870477637", // BrightPurple / MediumSlateBlue
40
+ green: "2136751179887052", // FreeSpeechGreen / Green
41
+ pink: "169463077092846", // HotPink / BrilliantRose
42
+ orange: "175615189761153", // Pumpkin / Orange
43
+ red: "2129984390566328", // RadicalRed / Red
44
+ yellow: "174636906462322", // GoldenPoppy / Yellow
45
+ teal: "1928399724138152", // TealBlue / Viking
46
+ aqua: "417639218648241", // Aqua
47
+ black: "271607034185782", // Shadow (darkest solid theme)
48
+ default: "196241301102133", // DefaultBlue
49
49
  };
50
50
 
51
51
  (async function worker() {
@@ -10,7 +10,7 @@ module.exports = function (defaultFuncs, api, ctx) {
10
10
  })
11
11
  const resData = await utils.parseAndCheckLogin(ctx, defaultFuncs)(defData);
12
12
  if (resData.error) {
13
- throw new Error(resData);
13
+ throw new Error(JSON.stringify(resData));
14
14
  }
15
15
  return resData;
16
16
  };
@@ -77,12 +77,29 @@ async function inspectResponseForSessionIssues(adapted, ctx) {
77
77
  }
78
78
 
79
79
  if (isScrapingWarning) {
80
- const err = new Error('Facebook scraping warning checkpoint detected.');
81
- err.error = 'checkpoint_scraping';
82
- err.res = body;
83
80
  if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
84
81
  ctx._emitter.emit('checkpoint', { type: 'scraping_warning', res: body });
85
82
  }
83
+ // Attempt auto-login recovery — throwing immediately here escalates
84
+ // the checkpoint into a permanent ban without giving the session a
85
+ // chance to recover.
86
+ if (!ctx.auto_login && typeof ctx.performAutoLogin === 'function') {
87
+ ctx.auto_login = true;
88
+ try {
89
+ const ok = await ctx.performAutoLogin();
90
+ if (ok) {
91
+ if (ctx._emitter && typeof ctx._emitter.emit === 'function') {
92
+ ctx._emitter.emit('autoLoginSuccess', { res: body });
93
+ }
94
+ ctx.auto_login = false;
95
+ return;
96
+ }
97
+ } catch (_) {}
98
+ ctx.auto_login = false;
99
+ }
100
+ const err = new Error('Facebook scraping warning checkpoint detected.');
101
+ err.error = 'checkpoint_scraping';
102
+ err.res = body;
86
103
  throw err;
87
104
  }
88
105
 
@@ -247,6 +264,17 @@ async function requestWithRetry(requestFunction, retries = 5, endpoint = '', thr
247
264
  if (error.response) {
248
265
  const adapted = adaptResponse(error.response);
249
266
  checkAndApplyRateLimitCooldowns(adapted.body);
267
+
268
+ // Emit rateLimit event on HTTP 429 so consumers can react
269
+ if (error.response.status === 429) {
270
+ if (ctx && ctx._emitter && typeof ctx._emitter.emit === 'function') {
271
+ try { ctx._emitter.emit('rateLimit', { res: adapted.body, status: 429 }); } catch (_) {}
272
+ }
273
+ const waitMs = Math.min(Math.pow(2, i) * 1000 + Math.floor(Math.random() * 200), 30000);
274
+ console.warn(`Rate limited (429). Waiting ${waitMs}ms before retry...`);
275
+ await delay(waitMs);
276
+ continue;
277
+ }
250
278
  }
251
279
 
252
280
  if (i === retries - 1) {
@@ -256,7 +284,7 @@ async function requestWithRetry(requestFunction, retries = 5, endpoint = '', thr
256
284
  }
257
285
  throw error;
258
286
  }
259
- const backoffTime = (Math.pow(2, i)) * 1000 + Math.floor(Math.random() * 1000);
287
+ const backoffTime = Math.min(Math.pow(2, i) * 1000 + Math.floor(Math.random() * 200), 30000);
260
288
  console.warn(`Request attempt ${i + 1} failed. Retrying in ${backoffTime}ms...`);
261
289
  await delay(backoffTime);
262
290
  }
@@ -3,6 +3,54 @@
3
3
  const { makeParsable, log, warn } = require("./constants");
4
4
  const { globalRateLimiter, configureRateLimiter } = require("./rateLimiter");
5
5
 
6
+ /**
7
+ * Safely emits an event on ctx._emitter without throwing if the emitter is absent.
8
+ * @param {Object} ctx - Application context.
9
+ * @param {string} event - Event name.
10
+ * @param {*} payload - Event payload.
11
+ */
12
+ function _emit(ctx, event, payload) {
13
+ try {
14
+ if (ctx && ctx._emitter && typeof ctx._emitter.emit === 'function') {
15
+ ctx._emitter.emit(event, payload);
16
+ }
17
+ } catch (_) {}
18
+ }
19
+
20
+ /**
21
+ * Attempts auto-login via ctx.performAutoLogin (wired by loginHelper).
22
+ * On success returns the original res so callers can continue transparently.
23
+ * On failure throws a SESSION_EXPIRED error with requiresReLogin = true.
24
+ * @param {Object} ctx
25
+ * @param {Object} http
26
+ * @param {*} res - Parsed JSON response body (passed through on success).
27
+ * @param {number} retryCount
28
+ * @returns {Promise<*>}
29
+ */
30
+ async function _maybeAutoLogin(ctx, http, res, retryCount) {
31
+ if (ctx && !ctx.auto_login && typeof ctx.performAutoLogin === 'function') {
32
+ ctx.auto_login = true;
33
+ try {
34
+ const ok = await ctx.performAutoLogin();
35
+ ctx.auto_login = false;
36
+ if (ok) {
37
+ _emit(ctx, 'autoLoginSuccess', { res });
38
+ return res;
39
+ }
40
+ } catch (e) {
41
+ ctx.auto_login = false;
42
+ _emit(ctx, 'autoLoginFailed', { error: e, res });
43
+ }
44
+ }
45
+ _emit(ctx, 'sessionExpired', { reason: 'login_redirect' });
46
+ const err = new Error("Session expired - Redirected to login page");
47
+ err.error = 401;
48
+ err.errorCode = 401;
49
+ err.errorType = "LOGIN_REDIRECT";
50
+ err.requiresReLogin = true;
51
+ throw err;
52
+ }
53
+
6
54
  /**
7
55
  * Formats a cookie array into a string for use in a cookie jar.
8
56
  * @param {Array<string>} arr - An array containing cookie parts.
@@ -40,12 +88,16 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
40
88
  err.statusCode = data.statusCode;
41
89
  err.res = data.body;
42
90
  err.error = "Request retry failed. Check the `res` and `statusCode` property on this error.";
91
+ log(`parseAndCheckLogin: Max retries (5) reached for status ${data.statusCode}`);
43
92
  throw err;
44
93
  }
45
94
 
46
95
  retryCount++;
47
- const retryTime = Math.floor(Math.random() * 5000);
96
+ const baseDelay = retryCount === 1 ? 1500 : 1000 * Math.pow(2, retryCount - 1);
97
+ const jitter = Math.floor(Math.random() * 200);
98
+ const retryTime = Math.min(baseDelay + jitter, 10000);
48
99
  const url = data.request.uri.protocol + "//" + data.request.uri.hostname + data.request.uri.pathname;
100
+ warn(`parseAndCheckLogin: HTTP ${data.statusCode} — retrying (attempt ${retryCount}/5) after ${retryTime}ms`);
49
101
 
50
102
  await delay(retryTime);
51
103
 
@@ -160,45 +212,56 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
160
212
 
161
213
  if (res.error === 1357001 || (res.errorSummary && res.errorSummary.includes("blocked"))) {
162
214
  const err = new Error("Facebook blocked the login");
163
- err.error = "Not logged in.";
215
+ err.error = "login_blocked";
164
216
  err.errorType = "BLOCKED";
217
+ err.res = res;
218
+ _emit(ctx, "loginBlocked", { res });
165
219
  throw err;
166
220
  }
167
221
 
168
222
  const resStr = JSON.stringify(res);
169
-
223
+
224
+ // Scraping warning checkpoint — emit event and attempt auto-login so the
225
+ // session can recover instead of crashing. Throwing immediately here
226
+ // escalates the checkpoint into a permanent ban.
170
227
  if (resStr.includes("XCheckpointFBScrapingWarningController") || resStr.includes("601051028565049")) {
171
- warn("Bot Detection", "Facebook checkpoint detected - scraping warning (601051028565049)");
228
+ warn("Bot Detection", "Facebook scraping-warning checkpoint detected attempting auto-login recovery");
229
+ _emit(ctx, "checkpoint", { type: "scraping_warning", res });
172
230
  try {
173
231
  globalRateLimiter.setEndpointCooldown("__GLOBAL__", 5 * 60 * 1000);
174
232
  configureRateLimiter({ maxConcurrentRequests: 2 });
175
233
  } catch (_) {}
176
- const err = new Error("Facebook detected automated behavior - Account may be flagged");
177
- err.error = "Bot checkpoint detected";
178
- err.errorCode = "CHECKPOINT_SCRAPING";
179
- err.errorType = "BOT_DETECTION";
180
- err.requiresReLogin = true;
181
- throw err;
234
+ return await _maybeAutoLogin(ctx, http, res, retryCount);
182
235
  }
183
236
 
184
237
  if (resStr.includes("1501092823525282")) {
185
- warn("Bot Detection", "Critical bot checkpoint 282 detected! Account requires immediate attention!");
186
- log("Please check your Facebook account in a browser and complete any security checks.");
238
+ warn("Bot Detection", "Critical bot checkpoint 282 detected! Please check your Facebook account.");
187
239
  try {
188
240
  globalRateLimiter.setEndpointCooldown("__GLOBAL__", 10 * 60 * 1000);
189
241
  configureRateLimiter({ maxConcurrentRequests: 1 });
190
242
  } catch (_) {}
191
- const err = new Error("Facebook detected automated behavior - Critical checkpoint required");
192
- err.error = "Critical bot checkpoint";
243
+ _emit(ctx, "checkpoint", { type: "282", res });
244
+ _emit(ctx, "checkpoint_282", { res });
245
+ const err = new Error("Checkpoint 282 detected");
246
+ err.error = "checkpoint_282";
193
247
  err.errorCode = "CHECKPOINT_282";
194
248
  err.errorType = "BOT_DETECTION_CRITICAL";
195
249
  err.requiresReLogin = true;
250
+ err.res = res;
196
251
  throw err;
197
252
  }
198
253
 
199
254
  if (resStr.includes("828281030927956")) {
200
- warn("Bot Detection", "Bot checkpoint 956 detected - Account may be restricted");
201
- log("Please verify your Facebook account status in a browser.");
255
+ warn("Bot Detection", "Bot checkpoint 956 detected account may be restricted");
256
+ _emit(ctx, "checkpoint", { type: "956", res });
257
+ _emit(ctx, "checkpoint_956", { res });
258
+ const err = new Error("Checkpoint 956 detected");
259
+ err.error = "checkpoint_956";
260
+ err.errorCode = "CHECKPOINT_956";
261
+ err.errorType = "BOT_DETECTION";
262
+ err.requiresReLogin = true;
263
+ err.res = res;
264
+ throw err;
202
265
  }
203
266
 
204
267
  // Only treat a redirect to login.php as a session expiry if the server
@@ -206,23 +269,13 @@ function parseAndCheckLogin(ctx, http, retryCount = 0) {
206
269
  // anywhere in the body JSON, which it does on authenticated pages as a
207
270
  // navigation/share link, causing false-positive session expiry errors.
208
271
  if (String(res.redirect || "").includes("login.php")) {
209
- warn("Session Status", "Redirected to login page - Session expired");
210
- const err = new Error("Session expired - Redirected to login page");
211
- err.error = 401;
212
- err.errorCode = 401;
213
- err.errorType = "LOGIN_REDIRECT";
214
- err.requiresReLogin = true;
215
- throw err;
272
+ warn("Session Status", "Redirected to login page — attempting auto-login recovery");
273
+ return await _maybeAutoLogin(ctx, http, res, retryCount);
216
274
  }
217
275
 
218
276
  if (typeof data.body === 'string' && (data.body.includes('<title>Facebook - Log In or Sign Up</title>') || data.body.includes('name="login_form"'))) {
219
- const err = new Error("Session expired - Redirected to login page");
220
- err.error = 401;
221
- err.errorCode = 401;
222
- err.errorType = "LOGIN_REDIRECT";
223
- err.requiresReLogin = true;
224
- warn("Session Status", "Detected login page redirect. Session expired.");
225
- throw err;
277
+ warn("Session Status", "Detected login page redirect — session expired");
278
+ return await _maybeAutoLogin(ctx, http, res, retryCount);
226
279
  }
227
280
 
228
281
  return res;