Skip to content

Commit

Permalink
feat: bot change name, picture, up sw, fix grup add
Browse files Browse the repository at this point in the history
  • Loading branch information
arugaz committed Sep 20, 2023
1 parent ac4b734 commit e694290
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 59 deletions.
5 changes: 5 additions & 0 deletions languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"change-name": "{{@ADM}} changed the group title to",
"change-picture": "{{@ADM}} has changed the group profile",
"group-add": {
"caption": "Invitation to join my WhatsApp group",
"add": "Successfully added {{@NUM}} to the group",
"invite": "Successfully invited {{@NUM}} to the group"
},
Expand Down Expand Up @@ -143,6 +144,10 @@
"enable": "Successfully activate {{@CMD}}, bot will send a welcome message when a new participant join the group",
"disable": "Successfully deactivate {{@CMD}}, bot no send a welcome message when a new participant join the group"
}
},
"owner": {
"bot-name": "Successfully change bot profile name",
"bot-picture": "Successfully change bot profile picture"
}
}
}
5 changes: 5 additions & 0 deletions languages/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"change-name": "{{@ADM}} mengubah judul grup menjadi",
"change-picture": "{{@ADM}} telah mengubah profil grup",
"group-add": {
"caption": "Undangan untuk bergabung dengan grup WhatsApp saya",
"add": "Berhasil menambahkan {{@NUM}} ke grup",
"invite": "Berhasil mengundang {{@NUM}} ke grup"
},
Expand Down Expand Up @@ -143,6 +144,10 @@
"enable": "Berhasil mengaktifkan {{@CMD}}, bot akan mengirimkan pesan selamat datang saat ada peserta baru yang bergabung ke grup",
"disable": "Berhasil menonaktifkan {{@CMD}}, bot tidak lagi mengirim pesan selamat datang saat ada peserta baru yang bergabung ke grup"
}
},
"owner": {
"bot-name": "Berhasil mengubah nama profil bot",
"bot-picture": "Berhasil mengubah gambar profil bot"
}
}
}
2 changes: 1 addition & 1 deletion src/commands/group/group-add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default <Command>{
const add = (await aruga.groupParticipantsUpdate(message.from, [member.jid], "add"))[0]

if (add.status === "403") {
await aruga.sendAcceptInviteV4(message.from, member.jid)
await aruga.sendAcceptInviteV4(message.from, add.content, member.jid, i18n.translate("commands.group.group-add.caption", {}, group.language))

const text =
"┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ ⁣𓆪 」\n" +
Expand Down
29 changes: 29 additions & 0 deletions src/commands/owner/bot-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Command } from "../../types/command"
import i18n from "../../libs/international"
import config from "../../utils/config"

export const name = "bname"

export default <Command>{
category: "owner",
aliases: ["botname"],
desc: "Change bot profile name",
ownerOnly: true,
example: `
@PREFIX@CMD name~
`.trimEnd(),
execute: async ({ aruga, arg, user, message }) => {
if (!arg) throw "noCmd"

await aruga.updateProfileName(arg)

const text =
"┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ ⁣𓆪 」\n" +
"┃\n" +
`┃ ${i18n.translate("commands.owner.bot-name", {}, user.language)}\n` +
"┃\n" +
`┗━━「 ꗥ${config.name}ꗥ 」`

return await message.reply(text, true)
}
}
42 changes: 42 additions & 0 deletions src/commands/owner/bot-picture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import i18n from "../../libs/international"
import config from "../../utils/config"
import type { Command } from "../../types/command"

export const name = "bpicture"

export default <Command>{
category: "owner",
aliases: ["botpicture"],
desc: "Change bot profile picture",
ownerOnly: true,
example: `
Send a image message with caption
@PREFIX@CMD
--------
or Reply a image message with text
@PREFIX@CMD
--------
If you want to crop the image
@PREFIX@CMD crop
--------
`.trimEnd(),
execute: async ({ aruga, message, arg, user }) => {
if (message.type.includes("image") || (message.quoted && message.quoted.type.includes("image"))) {
const imgBuffer: Buffer = message.quoted ? await aruga.downloadMediaMessage(message.quoted) : await aruga.downloadMediaMessage(message)
const crop = arg && arg.toLowerCase() === "crop"

await aruga.updateProfilePicture(aruga.decodeJid(aruga.user.id), imgBuffer, crop)

const text =
"┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ ⁣𓆪 」\n" +
"┃\n" +
`┃ ${i18n.translate("commands.owner.bot-picture", {}, user.language)}\n` +
"┃\n" +
`┗━━「 ꗥ${config.name}ꗥ 」`

return await message.reply(text, true)
}

throw "noCmd"
}
}
53 changes: 53 additions & 0 deletions src/commands/owner/upsw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { database } from "../../libs/whatsapp"
import type { Command } from "../../types/command"

export default <Command>{
category: "owner",
aliases: ["upstatus", "upstory"],
desc: "Upload bot status",
ownerOnly: true,
example: `
Send a image/video message with caption
@PREFIX@CMD status caption
--------
or Reply a image/video message with text
@PREFIX@CMD status caption
--------
Send a text message
@PREFIX@CMD status caption
`.trimEnd(),
execute: async ({ aruga, message, arg }) => {
const contactList = await database.getUsers()

/**
* just arrange it as you like
*/

await aruga.sendMessage(
"status@broadcast",
{
...(message.type === "image" || (message.quoted && message.quoted.type === "image")
? {
image: await aruga.downloadMediaMessage(message.quoted || message),
caption: message.quoted?.body || arg
}
: message.type === "video" || (message.quoted && message.quoted.type === "video")
? {
video: await aruga.downloadMediaMessage(message.quoted || message),
caption: message.quoted?.body || arg
}
: {
text: arg
})
},
{
backgroundColor: "#123456",
font: 3,
//it is always necessary to inform the list of contacts that will have access to the posted status
statusJidList: contactList.map((user) => user.userId).concat(aruga.decodeJid(aruga.user.id))
}
)

return await message.reply("Status uploaded successfully", true)
}
}
31 changes: 28 additions & 3 deletions src/libs/server/routes.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { FastifyInstance } from "fastify"
import WAClient from "../../libs/whatsapp"

/**
* This APP Just an example how to use fastify with aruga
*/

export const whatsappRoutes = (fastify: FastifyInstance, aruga: WAClient) => {
fastify.register(
async (child) => {
child.addHook("onRequest", async (request, reply) => {
async (instance) => {
instance.addHook("onRequest", async (request, reply) => {
const { secret } = request.query as { secret: string }
if (!secret || secret !== process.env.SECRET_API) {
reply.code(403)
Expand All @@ -13,13 +17,34 @@ export const whatsappRoutes = (fastify: FastifyInstance, aruga: WAClient) => {
})

// http://127.0.0.1:PORT/api/status?secret=yoursecret
child.get("/status", () => {
instance.get("/status", () => {
return {
message: aruga.status,
error: "Success",
statusCode: 200
}
})

// http://127.0.0.1:PORT/api/send-message?secret=yoursecret&number=628xxx&message=Hello
instance.route({
url: "/send-message",
method: "GET",
handler: async (request, reply) => {
const { number, message } = request.query as { number: string; message: string }
if (aruga.status !== "open") {
reply.code(500)
throw new Error("Client not ready")
}

await aruga.sendMessage(number.replace(/[^0-9]/g, "") + "@s.whatsapp.net", { text: message })

return reply.send({
message: "Succesfully send message",
error: "Success",
statusCode: 200
})
}
})
},
{ prefix: "/api" }
)
Expand Down
26 changes: 22 additions & 4 deletions src/libs/whatsapp/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import P from "pino"
import { Boom } from "@hapi/boom"
import EventEmitter from "@arugaz/eventemitter"
import makeWASocket, { BaileysEventMap, DisconnectReason, downloadMediaMessage, fetchLatestBaileysVersion, generateForwardMessageContent, generateWAMessageFromContent, jidDecode, makeCacheableSignalKeyStore, MessageGenerationOptionsFromContent, proto, toBuffer, WAMediaUpload, WAMessageStubType } from "baileys"
import makeWASocket, {
BaileysEventMap,
BinaryNode,
DisconnectReason,
downloadMediaMessage,
fetchLatestBaileysVersion,
generateForwardMessageContent,
generateWAMessageFromContent,
getBinaryNodeChild,
jidDecode,
makeCacheableSignalKeyStore,
MessageGenerationOptionsFromContent,
proto,
toBuffer,
WAMediaUpload,
WAMessageStubType
} from "baileys"

import { auth, database } from "../../libs/whatsapp"
import Database from "../../libs/database"
Expand Down Expand Up @@ -98,15 +114,17 @@ class WAClient extends (EventEmitter as new () => ArugaEventEmitter) implements
})
}

public async sendAcceptInviteV4(jid: string, participants: string, caption = "Invitation to join my WhatsApp group") {
public async sendAcceptInviteV4(jid: string, node: BinaryNode, participants: string, caption = "Invitation to join my WhatsApp group") {
if (!jid.endsWith("g.us")) throw new TypeError("Invalid jid")
const inviteCode = await this.groupInviteCode(jid)
const result = getBinaryNodeChild(node, "add_request")
const inviteCode = result.attrs.code
const inviteExpiration = result.attrs.expration
const groupName = (await database.getGroup(jid)).name

const content = proto.Message.fromObject({
groupInviteMessage: proto.Message.GroupInviteMessage.fromObject({
inviteCode,
inviteExpiration: Date.now() + 3 * 24 * 60 * 60 * 1000,
inviteExpiration,
groupJid: jid,
groupName: groupName,
caption
Expand Down
12 changes: 11 additions & 1 deletion src/libs/whatsapp/database/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { User } from "@prisma/client"
import type { Prisma, User } from "@prisma/client"
import NodeCache from "node-cache"
import Database from "../../../libs/database"
import config from "../../../utils/config"
Expand All @@ -24,6 +24,16 @@ export const getUser = async (userId: string) => {
}
}

export const getUsers = async (opts?: Prisma.UserFindManyArgs) => {
try {
const userData = await Database.user.findMany(opts)

return userData
} catch {
return null
}
}

export const createUser = async (userId: string, metadata: Partial<Omit<User, "id" | "userId">>) => {
try {
if (user.has(userId)) return user.get(userId) as User
Expand Down
82 changes: 42 additions & 40 deletions src/libs/whatsapp/serialize/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,27 @@ export const message = async (aruga: WAClient, msg: WAMessage): Promise<MessageS
m.type = Object.keys(m.message).find((type) => type !== "senderKeyDistributionMessage" && type !== "messageContextInfo")
m.sender = aruga.decodeJid(m.fromMe ? aruga.user.id : m.isGroupMsg || m.from === "status@broadcast" ? m.key.participant || msg.participant : m.from)
m.key.participant = !m.key.participant || m.key.participant === "status_me" ? m.sender : m.key.participant
m.body =
m.message.conversation && m.type === "conversation"
? m.message.conversation
: m.message.extendedTextMessage && m.type === "extendedTextMessage"
? m.message.extendedTextMessage.text
: m.message.imageMessage && m.type === "imageMessage"
? m.message.imageMessage.caption
: m.message.videoMessage && m.type === "videoMessage"
? m.message.videoMessage.caption
: m.message.documentMessage && m.type === "documentMessage"
? m.message.documentMessage.caption
: m.message.buttonsResponseMessage && m.type === "buttonsResponseMessage"
? m.message.buttonsResponseMessage.selectedButtonId
: m.message.listResponseMessage && m.type === "listResponseMessage"
? m.message.listResponseMessage.singleSelectReply.selectedRowId
: m.message.templateButtonReplyMessage && m.type === "templateButtonReplyMessage"
? m.message.templateButtonReplyMessage.selectedId
: m.message.reactionMessage && m.type === "reactionMessage"
? m.message.reactionMessage.text
: ""
m.body = m.message.conversation
? m.message.conversation
: m.message.extendedTextMessage
? m.message.extendedTextMessage.text
: m.message.imageMessage
? m.message.imageMessage.caption
: m.message.videoMessage
? m.message.videoMessage.caption
: m.message.documentMessage
? m.message.documentMessage.caption
: m.message.buttonsResponseMessage
? m.message.buttonsResponseMessage.selectedButtonId
: m.message.listResponseMessage
? m.message.listResponseMessage.singleSelectReply.selectedRowId
: m.message.templateButtonReplyMessage
? m.message.templateButtonReplyMessage.selectedId
: m.message.reactionMessage
? m.message.reactionMessage.text
: m.message.locationMessage
? m.message.locationMessage.comment
: ""
m.mentions = m.message[m.type]?.contextInfo?.mentionedJid || []
m.viewOnce = !!msg.message?.viewOnceMessage || !!msg.message?.viewOnceMessageV2 || !!msg.message?.viewOnceMessageV2Extension
function reply(text: string, quoted = false) {
Expand Down Expand Up @@ -99,26 +100,27 @@ export const message = async (aruga: WAClient, msg: WAMessage): Promise<MessageS
m.quoted.type = Object.keys(m.quoted.message).find((type) => type !== "senderKeyDistributionMessage" && type !== "messageContextInfo")
m.quoted.sender = m.quoted.key.participant
m.quoted.key.participant = !m.quoted.key.participant ? m.sender : m.quoted.key.participant
m.quoted.body =
m.quoted.message.conversation && m.quoted.type === "conversation"
? m.quoted.message.conversation
: m.quoted.message.extendedTextMessage && m.quoted.type === "extendedTextMessage"
? m.quoted.message.extendedTextMessage.text
: m.quoted.message.imageMessage && m.quoted.type === "imageMessage"
? m.quoted.message.imageMessage.caption
: m.quoted.message.videoMessage && m.quoted.type === "videoMessage"
? m.quoted.message.videoMessage.caption
: m.quoted.message.documentMessage && m.quoted.type === "documentMessage"
? m.quoted.message.documentMessage.caption
: m.quoted.message.buttonsResponseMessage && m.quoted.type === "buttonsResponseMessage"
? m.quoted.message.buttonsResponseMessage.selectedButtonId
: m.quoted.message.listResponseMessage && m.quoted.type === "listResponseMessage"
? m.quoted.message.listResponseMessage.singleSelectReply.selectedRowId
: m.quoted.message.templateButtonReplyMessage && m.quoted.type === "templateButtonReplyMessage"
? m.quoted.message.templateButtonReplyMessage.selectedId
: m.quoted.message.reactionMessage && m.quoted.type === "reactionMessage"
? m.quoted.message.reactionMessage.text
: ""
m.quoted.body = m.quoted.message.conversation
? m.quoted.message.conversation
: m.quoted.message.extendedTextMessage
? m.quoted.message.extendedTextMessage.text
: m.quoted.message.imageMessage
? m.quoted.message.imageMessage.caption
: m.quoted.message.videoMessage
? m.quoted.message.videoMessage.caption
: m.quoted.message.documentMessage
? m.quoted.message.documentMessage.caption
: m.quoted.message.buttonsResponseMessage
? m.quoted.message.buttonsResponseMessage.selectedButtonId
: m.quoted.message.listResponseMessage
? m.quoted.message.listResponseMessage.singleSelectReply.selectedRowId
: m.quoted.message.templateButtonReplyMessage
? m.quoted.message.templateButtonReplyMessage.selectedId
: m.quoted.message.reactionMessage
? m.quoted.message.reactionMessage.text
: m.quoted.message.locationMessage
? m.quoted.message.locationMessage.comment
: ""
m.quoted.mentions = m.quoted.message[m.quoted.type]?.contextInfo?.mentionedJid || []
m.quoted.viewOnce = !!m.message[m.type].contextInfo.quotedMessage?.viewOnceMessage || !!m.message[m.type].contextInfo.quotedMessage?.viewOnceMessageV2 || !!m.message[m.type].contextInfo.quotedMessage?.viewOnceMessageV2Extension
function reply(text: string, quoted = false) {
Expand Down
Loading

0 comments on commit e694290

Please sign in to comment.