store.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import {readFileSync, writeFileSync, existsSync} from 'fs';
  2. /**
  3. * @type {import('@whiskeysockets/baileys')}
  4. */
  5. const {initAuthCreds, BufferJSON, proto} = (await import('@whiskeysockets/baileys')).default;
  6. /**
  7. * @param {import('@whiskeysockets/baileys').WASocket | import('@whiskeysockets/baileys').WALegacySocket}
  8. */
  9. function bind(conn) {
  10. if (!conn.chats) conn.chats = {};
  11. /**
  12. *
  13. * @param {import('@whiskeysockets/baileys').Contact[]|{contacts:import('@whiskeysockets/baileys').Contact[]}} contacts
  14. * @returns
  15. */
  16. function updateNameToDb(contacts) {
  17. if (!contacts) return;
  18. try {
  19. contacts = contacts.contacts || contacts;
  20. for (const contact of contacts) {
  21. const id = conn.decodeJid(contact.id);
  22. if (!id || id === 'status@broadcast') continue;
  23. let chats = conn.chats[id];
  24. if (!chats) chats = conn.chats[id] = {...contact, id};
  25. conn.chats[id] = {
  26. ...chats,
  27. ...({
  28. ...contact, id, ...(id.endsWith('@g.us') ?
  29. {subject: contact.subject || contact.name || chats.subject || ''} :
  30. {name: contact.notify || contact.name || chats.name || chats.notify || ''}),
  31. } || {}),
  32. };
  33. }
  34. } catch (e) {
  35. // console.error(e);
  36. }
  37. }
  38. conn.ev.on('contacts.upsert', updateNameToDb);
  39. conn.ev.on('groups.update', updateNameToDb);
  40. conn.ev.on('contacts.set', updateNameToDb);
  41. conn.ev.on('chats.set', async ({chats}) => {
  42. try {
  43. for (let {id, name, readOnly} of chats) {
  44. id = conn.decodeJid(id);
  45. if (!id || id === 'status@broadcast') continue;
  46. const isGroup = id.endsWith('@g.us');
  47. let chats = conn.chats[id];
  48. if (!chats) chats = conn.chats[id] = {id};
  49. chats.isChats = !readOnly;
  50. if (name) chats[isGroup ? 'subject' : 'name'] = name;
  51. if (isGroup) {
  52. const metadata = await conn.groupMetadata(id).catch((_) => null);
  53. if (name || metadata?.subject) chats.subject = name || metadata.subject;
  54. if (!metadata) continue;
  55. chats.metadata = metadata;
  56. }
  57. }
  58. } catch (e) {
  59. // console.error(e);
  60. }
  61. });
  62. conn.ev.on('group-participants.update', async function updateParticipantsToDb({id, participants, action}) {
  63. if (!id) return;
  64. id = conn.decodeJid(id);
  65. if (id === 'status@broadcast') return;
  66. if (!(id in conn.chats)) conn.chats[id] = {id};
  67. const chats = conn.chats[id];
  68. chats.isChats = true;
  69. const groupMetadata = await conn.groupMetadata(id).catch((_) => null);
  70. if (!groupMetadata) return;
  71. chats.subject = groupMetadata.subject;
  72. chats.metadata = groupMetadata;
  73. });
  74. conn.ev.on('groups.update', async function groupUpdatePushToDb(groupsUpdates) {
  75. try {
  76. for (const update of groupsUpdates) {
  77. const id = conn.decodeJid(update.id);
  78. if (!id || id === 'status@broadcast') continue;
  79. const isGroup = id.endsWith('@g.us');
  80. if (!isGroup) continue;
  81. let chats = conn.chats[id];
  82. if (!chats) chats = conn.chats[id] = {id};
  83. chats.isChats = true;
  84. const metadata = await conn.groupMetadata(id).catch((_) => null);
  85. if (metadata) chats.metadata = metadata;
  86. if (update.subject || metadata?.subject) chats.subject = update.subject || metadata.subject;
  87. }
  88. } catch (e) {
  89. // console.error(e);
  90. }
  91. });
  92. conn.ev.on('chats.upsert', function chatsUpsertPushToDb(chatsUpsert) {
  93. try {
  94. const {id, name} = chatsUpsert;
  95. if (!id || id === 'status@broadcast') return;
  96. conn.chats[id] = {...(conn.chats[id] || {}), ...chatsUpsert, isChats: true};
  97. const isGroup = id.endsWith('@g.us');
  98. if (isGroup) conn.insertAllGroup().catch((_) => null);
  99. } catch (e) {
  100. // console.error(e);
  101. }
  102. });
  103. conn.ev.on('presence.update', async function presenceUpdatePushToDb({id, presences}) {
  104. try {
  105. const sender = Object.keys(presences)[0] || id;
  106. const _sender = conn.decodeJid(sender);
  107. const presence = presences[sender]['lastKnownPresence'] || 'composing';
  108. let chats = conn.chats[_sender];
  109. if (!chats) chats = conn.chats[_sender] = {id: sender};
  110. chats.presences = presence;
  111. if (id.endsWith('@g.us')) {
  112. let chats = conn.chats[id];
  113. if (!chats) chats = conn.chats[id] = {id};
  114. }
  115. } catch (e) {
  116. // console.error(e);
  117. }
  118. });
  119. }
  120. const KEY_MAP = {
  121. 'pre-key': 'preKeys',
  122. 'session': 'sessions',
  123. 'sender-key': 'senderKeys',
  124. 'app-state-sync-key': 'appStateSyncKeys',
  125. 'app-state-sync-version': 'appStateVersions',
  126. 'sender-key-memory': 'senderKeyMemory',
  127. };
  128. /**
  129. *
  130. * @param {String} filename
  131. * @param {import('pino').Logger} logger
  132. * @returns
  133. */
  134. function useSingleFileAuthState(filename, logger) {
  135. let creds; let keys = {}; let saveCount = 0;
  136. // save the authentication state to a file
  137. const saveState = (forceSave) => {
  138. logger?.trace('saving auth state');
  139. saveCount++;
  140. if (forceSave || saveCount > 5) {
  141. writeFileSync(
  142. filename,
  143. // BufferJSON replacer utility saves buffers nicely
  144. JSON.stringify({creds, keys}, BufferJSON.replacer, 2),
  145. );
  146. saveCount = 0;
  147. }
  148. };
  149. if (existsSync(filename)) {
  150. const result = JSON.parse(
  151. readFileSync(filename, {encoding: 'utf-8'}),
  152. BufferJSON.reviver,
  153. );
  154. creds = result.creds;
  155. keys = result.keys;
  156. } else {
  157. creds = initAuthCreds();
  158. keys = {};
  159. }
  160. return {
  161. state: {
  162. creds,
  163. keys: {
  164. get: (type, ids) => {
  165. const key = KEY_MAP[type];
  166. return ids.reduce(
  167. (dict, id) => {
  168. let value = keys[key]?.[id];
  169. if (value) {
  170. if (type === 'app-state-sync-key') {
  171. value = proto.AppStateSyncKeyData.fromObject(value);
  172. }
  173. dict[id] = value;
  174. }
  175. return dict;
  176. }, {},
  177. );
  178. },
  179. set: (data) => {
  180. for (const _key in data) {
  181. const key = KEY_MAP[_key];
  182. keys[key] = keys[key] || {};
  183. Object.assign(keys[key], data[_key]);
  184. }
  185. saveState();
  186. },
  187. },
  188. },
  189. saveState,
  190. };
  191. }
  192. function loadMessage(jid, id = null) {
  193. let message = null;
  194. // If only 1 param, first param is assumed to be id not jid
  195. if (jid && !id) {
  196. id = jid;
  197. /** @type {(m: import('@whiskeysockets/baileys').proto.WebMessageInfo) => Boolean} */
  198. const filter = (m) => m.key?.id == id;
  199. const messages = {};
  200. const messageFind = Object.entries(messages)
  201. .find(([, msgs]) => {
  202. return msgs.find(filter);
  203. });
  204. message = messageFind?.[1]?.find(filter);
  205. } else {
  206. // @ts-ignore
  207. jid = jid?.decodeJid?.();
  208. const messages = {};
  209. if (!(jid in messages)) return null;
  210. message = messages[jid].find((m) => m.key.id == id);
  211. }
  212. return message ? message : null;
  213. }
  214. export default {
  215. bind,
  216. useSingleFileAuthState,
  217. loadMessage,
  218. };