UNPKG

naruyaizumi

Version:

A WebSockets library for interacting with WhatsApp Web

366 lines (365 loc) 14.5 kB
import { proto } from "../../WAProto/index.js"; import { WAMessageAddressingMode, WAMessageStubType } from "../Types/index.js"; import { generateMessageIDV2, unixTimestampSeconds } from "../Utils/index.js"; import { getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString, isLidUser, isPnUser, jidEncode, jidNormalizedUser, } from "../WABinary/index.js"; import { makeChatsSocket } from "./chats.js"; export const makeGroupsSocket = (config) => { const sock = makeChatsSocket(config); const { authState, ev, query, upsertMessage } = sock; const groupQuery = async (jid, type, content) => query({ tag: "iq", attrs: { type, xmlns: "w:g2", to: jid, }, content, }); const groupMetadata = async (jid) => { const result = await groupQuery(jid, "get", [ { tag: "query", attrs: { request: "interactive" } }, ]); return extractGroupMetadata(result); }; const groupFetchAllParticipating = async () => { const result = await query({ tag: "iq", attrs: { to: "@g.us", xmlns: "w:g2", type: "get", }, content: [ { tag: "participating", attrs: {}, content: [ { tag: "participants", attrs: {} }, { tag: "description", attrs: {} }, ], }, ], }); const data = {}; const groupsChild = getBinaryNodeChild(result, "groups"); if (groupsChild) { const groups = getBinaryNodeChildren(groupsChild, "group"); for (const groupNode of groups) { const meta = extractGroupMetadata({ tag: "result", attrs: {}, content: [groupNode], }); data[meta.id] = meta; } } // TODO: properly parse LID / PN DATA sock.ev.emit("groups.update", Object.values(data)); return data; }; sock.ws.on("CB:ib,,dirty", async (node) => { const { attrs } = getBinaryNodeChild(node, "dirty"); if (attrs.type !== "groups") { return; } await groupFetchAllParticipating(); await sock.cleanDirtyBits("groups"); }); return { ...sock, groupMetadata, groupCreate: async (subject, participants) => { const key = generateMessageIDV2(); const result = await groupQuery("@g.us", "set", [ { tag: "create", attrs: { subject, key, }, content: participants.map((jid) => ({ tag: "participant", attrs: { jid }, })), }, ]); return extractGroupMetadata(result); }, groupLeave: async (id) => { await groupQuery("@g.us", "set", [ { tag: "leave", attrs: {}, content: [{ tag: "group", attrs: { id } }], }, ]); }, groupUpdateSubject: async (jid, subject) => { await groupQuery(jid, "set", [ { tag: "subject", attrs: {}, content: Buffer.from(subject, "utf-8"), }, ]); }, groupRequestParticipantsList: async (jid) => { const result = await groupQuery(jid, "get", [ { tag: "membership_approval_requests", attrs: {}, }, ]); const node = getBinaryNodeChild(result, "membership_approval_requests"); const participants = getBinaryNodeChildren(node, "membership_approval_request"); return participants.map((v) => v.attrs); }, groupRequestParticipantsUpdate: async (jid, participants, action) => { const result = await groupQuery(jid, "set", [ { tag: "membership_requests_action", attrs: {}, content: [ { tag: action, attrs: {}, content: participants.map((jid) => ({ tag: "participant", attrs: { jid }, })), }, ], }, ]); const node = getBinaryNodeChild(result, "membership_requests_action"); const nodeAction = getBinaryNodeChild(node, action); const participantsAffected = getBinaryNodeChildren(nodeAction, "participant"); return participantsAffected.map((p) => { return { status: p.attrs.error || "200", jid: p.attrs.jid }; }); }, groupParticipantsUpdate: async (jid, participants, action) => { const result = await groupQuery(jid, "set", [ { tag: action, attrs: {}, content: participants.map((jid) => ({ tag: "participant", attrs: { jid }, })), }, ]); const node = getBinaryNodeChild(result, action); const participantsAffected = getBinaryNodeChildren(node, "participant"); return participantsAffected.map((p) => { return { status: p.attrs.error || "200", jid: p.attrs.jid, content: p }; }); }, groupUpdateDescription: async (jid, description) => { const metadata = await groupMetadata(jid); const prev = metadata.descId ?? null; await groupQuery(jid, "set", [ { tag: "description", attrs: { ...(description ? { id: generateMessageIDV2() } : { delete: "true" }), ...(prev ? { prev } : {}), }, content: description ? [{ tag: "body", attrs: {}, content: Buffer.from(description, "utf-8") }] : undefined, }, ]); }, groupInviteCode: async (jid) => { const result = await groupQuery(jid, "get", [{ tag: "invite", attrs: {} }]); const inviteNode = getBinaryNodeChild(result, "invite"); return inviteNode?.attrs.code; }, groupRevokeInvite: async (jid) => { const result = await groupQuery(jid, "set", [{ tag: "invite", attrs: {} }]); const inviteNode = getBinaryNodeChild(result, "invite"); return inviteNode?.attrs.code; }, groupAcceptInvite: async (code) => { const results = await groupQuery("@g.us", "set", [{ tag: "invite", attrs: { code } }]); const result = getBinaryNodeChild(results, "group"); return result?.attrs.jid; }, /** * revoke a v4 invite for someone * @param groupJid group jid * @param invitedJid jid of person you invited * @returns true if successful */ groupRevokeInviteV4: async (groupJid, invitedJid) => { const result = await groupQuery(groupJid, "set", [ { tag: "revoke", attrs: {}, content: [{ tag: "participant", attrs: { jid: invitedJid } }], }, ]); return !!result; }, /** * accept a GroupInviteMessage * @param key the key of the invite message, or optionally only provide the jid of the person who sent the invite * @param inviteMessage the message to accept */ groupAcceptInviteV4: ev.createBufferedFunction(async (key, inviteMessage) => { key = typeof key === "string" ? { remoteJid: key } : key; const results = await groupQuery(inviteMessage.groupJid, "set", [ { tag: "accept", attrs: { code: inviteMessage.inviteCode, expiration: inviteMessage.inviteExpiration.toString(), admin: key.remoteJid, }, }, ]); // if we have the full message key // update the invite message to be expired if (key.id) { // create new invite message that is expired inviteMessage = proto.Message.GroupInviteMessage.fromObject(inviteMessage); inviteMessage.inviteExpiration = 0; inviteMessage.inviteCode = ""; ev.emit("messages.update", [ { key, update: { message: { groupInviteMessage: inviteMessage, }, }, }, ]); } // generate the group add message await upsertMessage( { key: { remoteJid: inviteMessage.groupJid, id: generateMessageIDV2(sock.user?.id), fromMe: false, participant: key.remoteJid, }, messageStubType: WAMessageStubType.GROUP_PARTICIPANT_ADD, messageStubParameters: [JSON.stringify(authState.creds.me)], participant: key.remoteJid, messageTimestamp: unixTimestampSeconds(), }, "notify" ); return results.attrs.from; }), groupGetInviteInfo: async (code) => { const results = await groupQuery("@g.us", "get", [{ tag: "invite", attrs: { code } }]); return extractGroupMetadata(results); }, groupToggleEphemeral: async (jid, ephemeralExpiration) => { const content = ephemeralExpiration ? { tag: "ephemeral", attrs: { expiration: ephemeralExpiration.toString() } } : { tag: "not_ephemeral", attrs: {} }; await groupQuery(jid, "set", [content]); }, groupSettingUpdate: async (jid, setting) => { await groupQuery(jid, "set", [{ tag: setting, attrs: {} }]); }, groupMemberAddMode: async (jid, mode) => { await groupQuery(jid, "set", [{ tag: "member_add_mode", attrs: {}, content: mode }]); }, groupJoinApprovalMode: async (jid, mode) => { await groupQuery(jid, "set", [ { tag: "membership_approval_mode", attrs: {}, content: [{ tag: "group_join", attrs: { state: mode } }], }, ]); }, groupFetchAllParticipating, }; }; export const extractGroupMetadata = (result) => { const group = getBinaryNodeChild(result, "group"); const descChild = getBinaryNodeChild(group, "description"); let desc; let descId; let descOwner; let descOwnerPn; let descTime; if (descChild) { desc = getBinaryNodeChildString(descChild, "body"); descOwner = descChild.attrs.participant ? jidNormalizedUser(descChild.attrs.participant) : undefined; descOwnerPn = descChild.attrs.participant_pn ? jidNormalizedUser(descChild.attrs.participant_pn) : undefined; descTime = +descChild.attrs.t; descId = descChild.attrs.id; } const groupId = group.attrs.id.includes("@") ? group.attrs.id : jidEncode(group.attrs.id, "g.us"); const eph = getBinaryNodeChild(group, "ephemeral")?.attrs.expiration; const memberAddMode = getBinaryNodeChildString(group, "member_add_mode") === "all_member_add"; const metadata = { id: groupId, notify: group.attrs.notify, addressingMode: group.attrs.addressing_mode === "lid" ? WAMessageAddressingMode.LID : WAMessageAddressingMode.PN, subject: group.attrs.subject, subjectOwner: group.attrs.s_o, subjectOwnerPn: group.attrs.s_o_pn, subjectTime: +group.attrs.s_t, size: group.attrs.size ? +group.attrs.size : getBinaryNodeChildren(group, "participant").length, creation: +group.attrs.creation, owner: group.attrs.creator ? jidNormalizedUser(group.attrs.creator) : undefined, ownerPn: group.attrs.creator_pn ? jidNormalizedUser(group.attrs.creator_pn) : undefined, owner_country_code: group.attrs.creator_country_code, desc, descId, descOwner, descOwnerPn, descTime, linkedParent: getBinaryNodeChild(group, "linked_parent")?.attrs.jid || undefined, restrict: !!getBinaryNodeChild(group, "locked"), announce: !!getBinaryNodeChild(group, "announcement"), isCommunity: !!getBinaryNodeChild(group, "parent"), isCommunityAnnounce: !!getBinaryNodeChild(group, "default_sub_group"), joinApprovalMode: !!getBinaryNodeChild(group, "membership_approval_mode"), memberAddMode, participants: getBinaryNodeChildren(group, "participant").map(({ attrs }) => { // TODO: Store LID MAPPINGS return { id: attrs.jid, phoneNumber: isLidUser(attrs.jid) && isPnUser(attrs.phone_number) ? attrs.phone_number : undefined, lid: isPnUser(attrs.jid) && isLidUser(attrs.lid) ? attrs.lid : undefined, admin: attrs.type || null, }; }), ephemeralDuration: eph ? +eph : undefined, }; return metadata; }; //# sourceMappingURL=groups.js.map