a9e926b907
Reverse-engineered from WA Web JS bundle and live CDP interception.
All QueryIds and XWAPaths were wrong; mute/unmute also had wrong variables structure.
- FOLLOW QueryId: 7871414976211147 → 24404358912487870
- UNFOLLOW QueryId: 7238632346214362 → 9767147403369991
- MUTE QueryId: 29766401636284406 → 31938993655691868
- UNMUTE QueryId: 9864994326891137 → 31938993655691868 (same mutation as MUTE)
- xwa2_newsletter_follow: 'xwa2_newsletter_follow' → 'xwa2_newsletter_join_v2'
- xwa2_newsletter_unfollow: 'xwa2_newsletter_unfollow' → 'xwa2_newsletter_leave_v2'
- xwa2_newsletter_mute_v2: 'xwa2_newsletter_mute_v2' → 'xwa2_newsletter_update_user_setting'
- xwa2_newsletter_unmute_v2: 'xwa2_newsletter_unmute_v2' → 'xwa2_newsletter_update_user_setting'
- newsletterMute/Unmute variables: flat {newsletter_id} → {input: {newsletter_id, type, value}}
Fixes #2346
243 lines
7.1 KiB
TypeScript
243 lines
7.1 KiB
TypeScript
import type { NewsletterCreateResponse, SocketConfig, WAMediaUpload } from '../Types'
|
|
import type { NewsletterMetadata, NewsletterUpdate } from '../Types'
|
|
import { QueryIds, XWAPaths } from '../Types'
|
|
import { generateProfilePicture } from '../Utils/messages-media'
|
|
import { getBinaryNodeChild } from '../WABinary'
|
|
import { makeGroupsSocket } from './groups'
|
|
import { executeWMexQuery as genericExecuteWMexQuery } from './mex'
|
|
|
|
const parseNewsletterCreateResponse = (response: NewsletterCreateResponse): NewsletterMetadata => {
|
|
// Validate response structure before destructuring
|
|
if (!response?.id || !response?.thread_metadata) {
|
|
throw new Error('Invalid newsletter response: missing id or thread_metadata')
|
|
}
|
|
|
|
const { id, thread_metadata: thread, viewer_metadata: viewer } = response
|
|
|
|
// Validate required thread metadata fields
|
|
if (!thread.name?.text) {
|
|
throw new Error('Invalid newsletter response: missing thread name')
|
|
}
|
|
|
|
return {
|
|
id: id,
|
|
owner: undefined,
|
|
name: thread.name.text,
|
|
creation_time: parseInt(thread.creation_time, 10) || 0,
|
|
description: thread.description?.text || '',
|
|
invite: thread.invite || '',
|
|
subscribers: parseInt(thread.subscribers_count, 10) || 0,
|
|
verification: thread.verification,
|
|
picture: thread.picture
|
|
? {
|
|
id: thread.picture.id || '',
|
|
directPath: thread.picture.direct_path || ''
|
|
}
|
|
: { id: '', directPath: '' },
|
|
mute_state: viewer?.mute
|
|
}
|
|
}
|
|
|
|
const parseNewsletterMetadata = (result: unknown): NewsletterMetadata | null => {
|
|
if (typeof result !== 'object' || result === null) {
|
|
return null
|
|
}
|
|
|
|
if ('id' in result && typeof result.id === 'string') {
|
|
return result as NewsletterMetadata
|
|
}
|
|
|
|
if ('result' in result && typeof result.result === 'object' && result.result !== null && 'id' in result.result) {
|
|
return result.result as NewsletterMetadata
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
export const makeNewsletterSocket = (config: SocketConfig) => {
|
|
const sock = makeGroupsSocket(config)
|
|
const { query, generateMessageTag } = sock
|
|
|
|
const executeWMexQuery = <T>(variables: Record<string, unknown>, queryId: string, dataPath: string): Promise<T> => {
|
|
return genericExecuteWMexQuery<T>(variables, queryId, dataPath, query, generateMessageTag)
|
|
}
|
|
|
|
const newsletterUpdate = async (jid: string, updates: NewsletterUpdate) => {
|
|
const variables = {
|
|
newsletter_id: jid,
|
|
updates: {
|
|
...updates,
|
|
settings: null
|
|
}
|
|
}
|
|
return executeWMexQuery(variables, QueryIds.UPDATE_METADATA, 'xwa2_newsletter_update')
|
|
}
|
|
|
|
return {
|
|
...sock,
|
|
newsletterCreate: async (name: string, description?: string): Promise<NewsletterMetadata> => {
|
|
const variables = {
|
|
input: {
|
|
name,
|
|
description: description ?? null
|
|
}
|
|
}
|
|
const rawResponse = await executeWMexQuery<NewsletterCreateResponse>(
|
|
variables,
|
|
QueryIds.CREATE,
|
|
XWAPaths.xwa2_newsletter_create
|
|
)
|
|
return parseNewsletterCreateResponse(rawResponse)
|
|
},
|
|
|
|
newsletterUpdate,
|
|
|
|
newsletterSubscribers: async (jid: string) => {
|
|
return executeWMexQuery<{ subscribers: number }>(
|
|
{ newsletter_id: jid },
|
|
QueryIds.SUBSCRIBERS,
|
|
XWAPaths.xwa2_newsletter_subscribers
|
|
)
|
|
},
|
|
|
|
newsletterMetadata: async (type: 'invite' | 'jid', key: string) => {
|
|
const variables = {
|
|
fetch_creation_time: true,
|
|
fetch_full_image: true,
|
|
fetch_viewer_metadata: true,
|
|
input: {
|
|
key,
|
|
type: type.toUpperCase()
|
|
}
|
|
}
|
|
const result = await executeWMexQuery<unknown>(variables, QueryIds.METADATA, XWAPaths.xwa2_newsletter_metadata)
|
|
return parseNewsletterMetadata(result)
|
|
},
|
|
|
|
newsletterFollow: (jid: string) => {
|
|
return executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_follow)
|
|
},
|
|
|
|
newsletterUnfollow: (jid: string) => {
|
|
return executeWMexQuery({ newsletter_id: jid }, QueryIds.UNFOLLOW, XWAPaths.xwa2_newsletter_unfollow)
|
|
},
|
|
|
|
newsletterMute: (jid: string) => {
|
|
return executeWMexQuery({ input: { newsletter_id: jid, type: 'MUTE_ADMIN_ACTIVITY', value: 'OFF' } }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2)
|
|
},
|
|
|
|
newsletterUnmute: (jid: string) => {
|
|
return executeWMexQuery({ input: { newsletter_id: jid, type: 'MUTE_ADMIN_ACTIVITY', value: 'ON' } }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2)
|
|
},
|
|
|
|
newsletterUpdateName: async (jid: string, name: string) => {
|
|
return await newsletterUpdate(jid, { name })
|
|
},
|
|
|
|
newsletterUpdateDescription: async (jid: string, description: string) => {
|
|
return await newsletterUpdate(jid, { description })
|
|
},
|
|
|
|
newsletterUpdatePicture: async (jid: string, content: WAMediaUpload) => {
|
|
const { img } = await generateProfilePicture(content)
|
|
return await newsletterUpdate(jid, { picture: img.toString('base64') })
|
|
},
|
|
|
|
newsletterRemovePicture: async (jid: string) => {
|
|
return await newsletterUpdate(jid, { picture: '' })
|
|
},
|
|
|
|
newsletterReactMessage: async (jid: string, serverId: string, reaction?: string) => {
|
|
await query({
|
|
tag: 'message',
|
|
attrs: {
|
|
to: jid,
|
|
...(reaction ? {} : { edit: '7' }),
|
|
type: 'reaction',
|
|
server_id: serverId,
|
|
id: generateMessageTag()
|
|
},
|
|
content: [
|
|
{
|
|
tag: 'reaction',
|
|
attrs: reaction ? { code: reaction } : {}
|
|
}
|
|
]
|
|
})
|
|
},
|
|
|
|
newsletterFetchMessages: async (jid: string, count: number, since: number, after: number) => {
|
|
const messageUpdateAttrs: { count: string; since?: string; after?: string } = {
|
|
count: count.toString()
|
|
}
|
|
if (typeof since === 'number') {
|
|
messageUpdateAttrs.since = since.toString()
|
|
}
|
|
|
|
if (after) {
|
|
messageUpdateAttrs.after = after.toString()
|
|
}
|
|
|
|
const result = await query({
|
|
tag: 'iq',
|
|
attrs: {
|
|
id: generateMessageTag(),
|
|
type: 'get',
|
|
xmlns: 'newsletter',
|
|
to: jid
|
|
},
|
|
content: [
|
|
{
|
|
tag: 'message_updates',
|
|
attrs: messageUpdateAttrs
|
|
}
|
|
]
|
|
})
|
|
return result
|
|
},
|
|
|
|
subscribeNewsletterUpdates: async (jid: string): Promise<{ duration: string } | null> => {
|
|
const result = await query({
|
|
tag: 'iq',
|
|
attrs: {
|
|
id: generateMessageTag(),
|
|
type: 'set',
|
|
xmlns: 'newsletter',
|
|
to: jid
|
|
},
|
|
content: [{ tag: 'live_updates', attrs: {}, content: [] }]
|
|
})
|
|
const liveUpdatesNode = getBinaryNodeChild(result, 'live_updates')
|
|
const duration = liveUpdatesNode?.attrs?.duration
|
|
return duration ? { duration: duration } : null
|
|
},
|
|
|
|
newsletterAdminCount: async (jid: string): Promise<number> => {
|
|
const response = await executeWMexQuery<{ admin_count: number }>(
|
|
{ newsletter_id: jid },
|
|
QueryIds.ADMIN_COUNT,
|
|
XWAPaths.xwa2_newsletter_admin_count
|
|
)
|
|
return response.admin_count
|
|
},
|
|
|
|
newsletterChangeOwner: async (jid: string, newOwnerJid: string) => {
|
|
await executeWMexQuery(
|
|
{ newsletter_id: jid, user_id: newOwnerJid },
|
|
QueryIds.CHANGE_OWNER,
|
|
XWAPaths.xwa2_newsletter_change_owner
|
|
)
|
|
},
|
|
|
|
newsletterDemote: async (jid: string, userJid: string) => {
|
|
await executeWMexQuery({ newsletter_id: jid, user_id: userJid }, QueryIds.DEMOTE, XWAPaths.xwa2_newsletter_demote)
|
|
},
|
|
|
|
newsletterDelete: async (jid: string) => {
|
|
await executeWMexQuery({ newsletter_id: jid }, QueryIds.DELETE, XWAPaths.xwa2_newsletter_delete_v2)
|
|
}
|
|
}
|
|
}
|
|
|
|
export type NewsletterSocket = ReturnType<typeof makeNewsletterSocket>
|