diff --git a/.github/workflows/update-nightly.yml b/.github/workflows/update-nightly.yml new file mode 100644 index 0000000..8d08f6b --- /dev/null +++ b/.github/workflows/update-nightly.yml @@ -0,0 +1,79 @@ +name: Update Nightly + +permissions: + contents: write + +on: + push: + branches: + - master + +jobs: + update-nightly: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Fetching tags + run: git fetch --tags -f || true + + - name: Setup Node + uses: actions/setup-node@v3.6.0 + with: + node-version: 18.x + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install Dependencies + run: yarn + + - name: Update version to alpha + run: yarn version --prerelease --preid=alpha --no-git --no-git-tag-version + + - name: Build NPM package + run: yarn pack && mv whiskeysockets-baileys-*.tgz whiskeysockets-baileys-nightly.tgz + + - name: Generate Changelog + id: generate_changelog + run: | + changelog=$(yarn run --silent changelog:preview) + echo "changelog<> $GITHUB_OUTPUT + echo "${changelog}" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Update Nightly TAG + uses: richardsimko/update-tag@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: nightly + + - name: Update Nightly Release + uses: meeDamian/github-release@2.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: nightly + commitish: ${{ github.sha }} + name: Nightly Release + body: ${{ steps.generate_changelog.outputs.changelog }} + draft: false + prerelease: true + files: > + whiskeysockets-baileys-nightly.tgz + gzip: folders + allow_override: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 8db411a..261c607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ -# 6.0.0 (2023-05-10) +## 6.0.1 (2023-05-18) + + +### Bug Fixes + +* In memory store: normalize user when asserting message list to update messages ([#49](https://github.com/WhiskeySockets/Baileys/issues/49)) ([7b4abcd](https://github.com/WhiskeySockets/Baileys/commit/7b4abcdb231434c08c14dbb5879cd1fd4939fc41)) diff --git a/Example/example.ts b/Example/example.ts index 12c2340..3aec923 100644 --- a/Example/example.ts +++ b/Example/example.ts @@ -5,12 +5,8 @@ import P from 'pino' import readline from 'readline' import makeWASocket, { AnyMessageContent, delay, DisconnectReason, fetchLatestBaileysVersion, getAggregateVotesInPollMessage, makeCacheableSignalKeyStore, makeInMemoryStore, PHONENUMBER_MCC, proto, useMultiFileAuthState, WAMessageContent, WAMessageKey } from '../src' -const logger = P({ - transport: { - target: 'pino-pretty' - }, - level: 'trace' -}) +const logger = MAIN_LOGGER.child({}) +logger.level = 'trace' const useStore = !process.argv.includes('--no-store') const doReplies = !process.argv.includes('--no-reply') @@ -159,6 +155,15 @@ const startSock = async() => { await saveCreds() } + if(events['labels.association']) { + console.log(events['labels.association']) + } + + + if(events['labels.edit']) { + console.log(events['labels.edit']) + } + if(events.call) { console.log('recv call event', events.call) } diff --git a/README.md b/README.md index c0047f7..a5a8821 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,8 @@ export type BaileysEventMap = { 'chats.update': Partial[] /** delete chats with given ID */ 'chats.delete': string[] + 'labels.association': LabelAssociation + 'labels.edit': Label /** presence of contact in a chat updated */ 'presence.update': { id: string, presences: { [participant: string]: PresenceData } } diff --git a/package.json b/package.json index 3b25fca..d15b233 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@whiskeysockets/baileys", - "version": "6.0.0", + "version": "6.0.1", "description": "WhatsApp API", "keywords": [ "whatsapp", @@ -71,7 +71,7 @@ "sharp": "^0.30.5", "ts-jest": "^27.0.3", "ts-node": "^10.8.1", - "typedoc": "^0.22.0", + "typedoc": "^0.24.7", "typescript": "^4.0.0" }, "peerDependencies": { diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index ce3e87f..1f08f25 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -52,7 +52,7 @@ export const makeChatsSocket = (config: SocketConfig) => { type: 'get' }, content: [ - { tag: 'privacy', attrs: { } } + { tag: 'privacy', attrs: {} } ] }) privacySettings = reduceBinaryNodeToDictionary(content?.[0] as BinaryNode, 'category') @@ -118,7 +118,7 @@ export const makeChatsSocket = (config: SocketConfig) => { content: [{ tag: 'disappearing_mode', attrs: { - duration : duration.toString() + duration: duration.toString() } }] }) @@ -146,12 +146,12 @@ export const makeChatsSocket = (config: SocketConfig) => { content: [ { tag: 'query', - attrs: { }, - content: [ queryNode ] + attrs: {}, + content: [queryNode] }, { tag: 'list', - attrs: { }, + attrs: {}, content: userNodes } ] @@ -171,17 +171,17 @@ export const makeChatsSocket = (config: SocketConfig) => { [ { tag: 'user', - attrs: { }, + attrs: {}, content: jids.map( jid => ({ tag: 'contact', - attrs: { }, + attrs: {}, content: `+${jid}` }) ) } ], - { tag: 'contact', attrs: { } } + { tag: 'contact', attrs: {} } ) return results.map(user => { @@ -193,7 +193,7 @@ export const makeChatsSocket = (config: SocketConfig) => { const fetchStatus = async(jid: string) => { const [result] = await interactiveQuery( [{ tag: 'user', attrs: { jid } }], - { tag: 'status', attrs: { } } + { tag: 'status', attrs: {} } ) if(result) { const status = getBinaryNodeChild(result, 'status') @@ -248,7 +248,7 @@ export const makeChatsSocket = (config: SocketConfig) => { content: [ { tag: 'status', - attrs: { }, + attrs: {}, content: Buffer.from(status, 'utf-8') } ] @@ -379,19 +379,19 @@ export const makeChatsSocket = (config: SocketConfig) => { const resyncAppState = ev.createBufferedFunction(async(collections: readonly WAPatchName[], isInitialSync: boolean) => { // we use this to determine which events to fire // otherwise when we resync from scratch -- all notifications will fire - const initialVersionMap: { [T in WAPatchName]?: number } = { } - const globalMutationMap: ChatMutationMap = { } + const initialVersionMap: { [T in WAPatchName]?: number } = {} + const globalMutationMap: ChatMutationMap = {} await authState.keys.transaction( async() => { const collectionsToHandle = new Set(collections) // in case something goes wrong -- ensure we don't enter a loop that cannot be exited from - const attemptsMap: { [T in WAPatchName]?: number } = { } + const attemptsMap: { [T in WAPatchName]?: number } = {} // keep executing till all collections are done // sometimes a single patch request will not return all the patches (God knows why) // so we fetch till they're all done (this is determined by the "has_more_patches" flag) while(collectionsToHandle.size) { - const states = { } as { [T in WAPatchName]: LTHashState } + const states = {} as { [T in WAPatchName]: LTHashState } const nodes: BinaryNode[] = [] for(const name of collectionsToHandle) { @@ -412,7 +412,7 @@ export const makeChatsSocket = (config: SocketConfig) => { nodes.push({ tag: 'collection', - attrs: { + attrs: { name, version: state.version.toString(), // return snapshot if being synced from scratch @@ -431,7 +431,7 @@ export const makeChatsSocket = (config: SocketConfig) => { content: [ { tag: 'sync', - attrs: { }, + attrs: {}, content: nodes } ] @@ -516,10 +516,10 @@ export const makeChatsSocket = (config: SocketConfig) => { }) /** - * fetch the profile picture of a user/group - * type = "preview" for a low res picture - * type = "image for the high res picture" - */ + * fetch the profile picture of a user/group + * type = "preview" for a low res picture + * type = "image for the high res picture" + */ const profilePictureUrl = async(jid: string, type: 'preview' | 'image' = 'preview', timeoutMs?: number) => { jid = jidNormalizedUser(jid) const result = await query({ @@ -564,7 +564,7 @@ export const makeChatsSocket = (config: SocketConfig) => { content: [ { tag: type === 'recording' ? 'composing' : type, - attrs: type === 'recording' ? { media : 'audio' } : {} + attrs: type === 'recording' ? { media: 'audio' } : {} } ] }) @@ -587,7 +587,7 @@ export const makeChatsSocket = (config: SocketConfig) => { ? [ { tag: 'tctoken', - attrs: { }, + attrs: {}, content: tcToken } ] @@ -669,7 +669,7 @@ export const makeChatsSocket = (config: SocketConfig) => { content: [ { tag: 'sync', - attrs: { }, + attrs: {}, content: [ { tag: 'collection', @@ -681,7 +681,7 @@ export const makeChatsSocket = (config: SocketConfig) => { content: [ { tag: 'patch', - attrs: { }, + attrs: {}, content: proto.SyncdPatch.encode(patch).finish() } ] @@ -731,7 +731,7 @@ export const makeChatsSocket = (config: SocketConfig) => { const propsNode = getBinaryNodeChild(abtNode, 'props') - let props: { [_: string]: string } = { } + let props: { [_: string]: string } = {} if(propsNode) { props = reduceBinaryNodeToDictionary(propsNode, 'prop') } @@ -751,13 +751,13 @@ export const makeChatsSocket = (config: SocketConfig) => { type: 'get', }, content: [ - { tag: 'props', attrs: { } } + { tag: 'props', attrs: {} } ] }) const propsNode = getBinaryNodeChild(resultNode, 'props') - let props: { [_: string]: string } = { } + let props: { [_: string]: string } = {} if(propsNode) { props = reduceBinaryNodeToDictionary(propsNode, 'prop') } @@ -768,15 +768,61 @@ export const makeChatsSocket = (config: SocketConfig) => { } /** - * modify a chat -- mark unread, read etc. - * lastMessages must be sorted in reverse chronologically - * requires the last messages till the last message received; required for archive & unread - */ + * modify a chat -- mark unread, read etc. + * lastMessages must be sorted in reverse chronologically + * requires the last messages till the last message received; required for archive & unread + */ const chatModify = (mod: ChatModification, jid: string) => { const patch = chatModificationToAppPatch(mod, jid) return appPatch(patch) } + /** + * Adds label for the chats + */ + const addChatLabel = (jid: string, labelId: string) => { + return chatModify({ + addChatLabel: { + labelId + } + }, jid) + } + + /** + * Removes label for the chat + */ + const removeChatLabel = (jid: string, labelId: string) => { + return chatModify({ + removeChatLabel: { + labelId + } + }, jid) + } + + /** + * Adds label for the message + */ + const addMessageLabel = (jid: string, messageId: string, labelId: string) => { + return chatModify({ + addMessageLabel: { + messageId, + labelId + } + }, jid) + } + + /** + * Removes label for the message + */ + const removeMessageLabel = (jid: string, messageId: string, labelId: string) => { + return chatModify({ + removeMessageLabel: { + messageId, + labelId + } + }, jid) + } + /** * queries need to be fired on connection open * help ensure parity with WA Web @@ -949,6 +995,10 @@ export const makeChatsSocket = (config: SocketConfig) => { getBusinessProfile, resyncAppState, chatModify, - cleanDirtyBits + cleanDirtyBits, + addChatLabel, + removeChatLabel, + addMessageLabel, + removeMessageLabel } } diff --git a/src/Store/make-in-memory-store.ts b/src/Store/make-in-memory-store.ts index f8d7a6b..8830911 100644 --- a/src/Store/make-in-memory-store.ts +++ b/src/Store/make-in-memory-store.ts @@ -1,43 +1,94 @@ -import type KeyedDB from '@adiwajshing/keyed-db' +import KeyedDB from '@adiwajshing/keyed-db' import type { Comparable } from '@adiwajshing/keyed-db/lib/Types' import type { Logger } from 'pino' import { proto } from '../../WAProto' import { DEFAULT_CONNECTION_CONFIG } from '../Defaults' import type makeMDSocket from '../Socket' import type { BaileysEventEmitter, Chat, ConnectionState, Contact, GroupMetadata, PresenceData, WAMessage, WAMessageCursor, WAMessageKey } from '../Types' +import { Label } from '../Types/Label' +import { LabelAssociation, LabelAssociationType, MessageLabelAssociation } from '../Types/LabelAssociation' import { toNumber, updateMessageWithReaction, updateMessageWithReceipt } from '../Utils' import { jidNormalizedUser } from '../WABinary' import makeOrderedDictionary from './make-ordered-dictionary' +import { ObjectRepository } from './object-repository' type WASocket = ReturnType export const waChatKey = (pin: boolean) => ({ key: (c: Chat) => (pin ? (c.pinned ? '1' : '0') : '') + (c.archived ? '0' : '1') + (c.conversationTimestamp ? c.conversationTimestamp.toString(16).padStart(8, '0') : '') + c.id, - compare: (k1: string, k2: string) => k2.localeCompare (k1) + compare: (k1: string, k2: string) => k2.localeCompare(k1) }) export const waMessageID = (m: WAMessage) => m.key.id || '' +export const waLabelAssociationKey: Comparable = { + key: (la: LabelAssociation) => (la.type === LabelAssociationType.Chat ? la.chatId + la.labelId : la.chatId + la.messageId + la.labelId), + compare: (k1: string, k2: string) => k2.localeCompare(k1) +} + export type BaileysInMemoryStoreConfig = { chatKey?: Comparable + labelAssociationKey?: Comparable logger?: Logger } const makeMessagesDictionary = () => makeOrderedDictionary(waMessageID) -export default ( - { logger: _logger, chatKey }: BaileysInMemoryStoreConfig -) => { - const logger = _logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' }) - chatKey = chatKey || waChatKey(true) - const KeyedDB = require('@adiwajshing/keyed-db').default as new (...args: any[]) => KeyedDB +const predefinedLabels = Object.freeze>({ + '0': { + id: '0', + name: 'New customer', + predefinedId: '0', + color: 0, + deleted: false + }, + '1': { + id: '1', + name: 'New order', + predefinedId: '1', + color: 1, + deleted: false + }, + '2': { + id: '2', + name: 'Pending payment', + predefinedId: '2', + color: 2, + deleted: false + }, + '3': { + id: '3', + name: 'Paid', + predefinedId: '3', + color: 3, + deleted: false + }, + '4': { + id: '4', + name: 'Order completed', + predefinedId: '4', + color: 4, + deleted: false + } +}) - const chats = new KeyedDB(chatKey, c => c.id) - const messages: { [_: string]: ReturnType } = { } - const contacts: { [_: string]: Contact } = { } - const groupMetadata: { [_: string]: GroupMetadata } = { } - const presences: { [id: string]: { [participant: string]: PresenceData } } = { } +export default ( + { logger: _logger, chatKey, labelAssociationKey }: BaileysInMemoryStoreConfig +) => { + // const logger = _logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' }) + chatKey = chatKey || waChatKey(true) + labelAssociationKey = labelAssociationKey || waLabelAssociationKey + const logger = _logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' }) + // const KeyedDB = require('@adiwajshing/keyed-db').default as new (...args: any[]) => KeyedDB + + const chats = new KeyedDB(chatKey, c => c.id) + const messages: { [_: string]: ReturnType } = {} + const contacts: { [_: string]: Contact } = {} + const groupMetadata: { [_: string]: GroupMetadata } = {} + const presences: { [id: string]: { [participant: string]: PresenceData } } = {} const state: ConnectionState = { connection: 'close' } + const labels = new ObjectRepository