├── .all-contributorsrc
├── .env.example
├── .eslintrc
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── dependabot.yml
└── workflows
│ ├── lint.yml
│ └── stale.yml
├── .gitignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bin
├── generateLang
├── generateLang.arugaz
└── generateLangV2
├── config.json
├── database
└── languages.json
├── ecosystem.config.js
├── languages
├── en.json
└── id.json
├── package.json
├── prisma
└── schema.prisma
├── src
├── commands
│ ├── convert
│ │ ├── sticker.ts
│ │ ├── toanime.ts
│ │ └── toimage.ts
│ ├── general
│ │ ├── didumean_.ts
│ │ ├── language.ts
│ │ └── menu.ts
│ ├── group
│ │ ├── antibot.ts
│ │ ├── antibot_.ts
│ │ ├── anticountry.ts
│ │ ├── antilink.ts
│ │ ├── antilink_.ts
│ │ ├── antiviewonce.ts
│ │ ├── antiviewonce_.ts
│ │ ├── change-description.ts
│ │ ├── change-name.ts
│ │ ├── change-picture.ts
│ │ ├── group-add.ts
│ │ ├── group-demote.ts
│ │ ├── group-kick.ts
│ │ ├── group-language.ts
│ │ ├── group-mute.ts
│ │ ├── group-notify.ts
│ │ ├── group-promote.ts
│ │ ├── group-tagall.ts
│ │ ├── group-url.ts
│ │ ├── member-leave.ts
│ │ └── member-welcome.ts
│ ├── misc
│ │ └── ping.ts
│ └── owner
│ │ ├── bot-desc.ts
│ │ ├── bot-name.ts
│ │ ├── bot-picture.ts
│ │ ├── edit-user.ts
│ │ ├── kidnap.ts
│ │ └── upsw.ts
├── handlers
│ ├── call.ts
│ ├── group-participant.ts
│ ├── group.ts
│ └── message.ts
├── libs
│ ├── convert
│ │ ├── index.ts
│ │ ├── profile.ts
│ │ └── webp.ts
│ ├── database.ts
│ ├── international.ts
│ ├── server
│ │ ├── index.ts
│ │ ├── routes.ts
│ │ └── server.ts
│ └── whatsapp
│ │ ├── auth.ts
│ │ ├── client.ts
│ │ ├── command.ts
│ │ ├── database
│ │ ├── group-metadata.ts
│ │ ├── group.ts
│ │ ├── index.ts
│ │ └── user.ts
│ │ ├── index.ts
│ │ └── serialize
│ │ ├── call.ts
│ │ ├── group-participant.ts
│ │ ├── group.ts
│ │ ├── index.ts
│ │ └── message.ts
├── main.ts
├── types
│ ├── cfonts.d.ts
│ ├── client.d.ts
│ ├── colors.d.ts
│ ├── command.d.ts
│ ├── config.d.ts
│ ├── node-webpmux.d.ts
│ ├── serialize.d.ts
│ ├── sticker.d.ts
│ └── utils.d.ts
└── utils
│ ├── cli.ts
│ ├── color.ts
│ ├── config.ts
│ ├── cron.ts
│ ├── fetcher.ts
│ ├── filesystem.ts
│ └── format.ts
└── tsconfig.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributorsPerLine": 7,
8 | "projectName": "whatsapp-bot",
9 | "projectOwner": "arugaz",
10 | "repoType": "github",
11 | "repoHost": "https://github.com",
12 | "skipCi": true,
13 | "commitConvention": "eslint",
14 | "contributors": [
15 | {
16 | "login": "TobyG74",
17 | "name": "Tobi Saputra",
18 | "avatar_url": "https://avatars.githubusercontent.com/u/32604979?v=4",
19 | "profile": "https://github.com/TobyG74",
20 | "contributions": [
21 | "code"
22 | ]
23 | },
24 | {
25 | "login": "arugaz",
26 | "name": "ArugaZ",
27 | "avatar_url": "https://avatars.githubusercontent.com/u/53950128?v=4",
28 | "profile": "https://github.com/arugaz",
29 | "contributions": [
30 | "code",
31 | "ideas"
32 | ]
33 | },
34 | {
35 | "login": "GuckTubeYT",
36 | "name": "Muhammad Kevin",
37 | "avatar_url": "https://avatars.githubusercontent.com/u/56192597?v=4",
38 | "profile": "https://youtube.com/GuckTubeYT",
39 | "contributions": [
40 | "code"
41 | ]
42 | },
43 | {
44 | "login": "LSystemus-2",
45 | "name": "LSystemus-2",
46 | "avatar_url": "https://avatars.githubusercontent.com/u/90476449?v=4",
47 | "profile": "https://github.com/LSystemus-2",
48 | "contributions": [
49 | "bug"
50 | ]
51 | },
52 | {
53 | "login": "Issa2001",
54 | "name": "一茶",
55 | "avatar_url": "https://avatars.githubusercontent.com/u/89695452?v=4",
56 | "profile": "https://github.com/Issa2001",
57 | "contributions": [
58 | "ideas"
59 | ]
60 | }
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Mongo database for store primary data
2 | # example url: mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
3 | # you can also use the atlas version
4 | MONGODB_URL=your mongodb url
5 |
6 | # SERVER PORT
7 | PORT=3000
8 |
9 | # SECRET API
10 | SECRET_API=keepitsecret
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "plugins": ["@typescript-eslint"],
4 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
5 | "parserOptions": {
6 | "ecmaVersion": 2020,
7 | "project": ["./tsconfig.json"]
8 | },
9 | "rules": {
10 | "no-empty": [2, { "allowEmptyCatch": true }],
11 | "no-inner-declarations": [0, "both"],
12 | "@typescript-eslint/no-unused-vars": 2,
13 | "@typescript-eslint/consistent-type-definitions": [2, "type"]
14 | },
15 | "env": {
16 | "node": true
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: arugaz # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ["https://trakteer.id/arugaz/tip"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: "🐛 Bug Report"
2 | description: Create a report to help improve the repository
3 | title: "[BUG]
"
4 | labels: ["bug"]
5 | assignees:
6 | - arugaz
7 | - tobyg74
8 | body:
9 | - type: textarea
10 | id: description
11 | attributes:
12 | label: "Description"
13 | description: Please enter an explicit description of your issue
14 | placeholder: A clear and concise description of what the bug is....
15 | render: bash
16 | validations:
17 | required: true
18 | - type: textarea
19 | id: reprod
20 | attributes:
21 | label: "Reproduction steps"
22 | description: Please enter an explicit description of your issue
23 | value: |
24 | 1. Clone the repository & run the app
25 | 2. Closed & used saved credentials to log back in
26 | 3. Etc.
27 | render: bash
28 | validations:
29 | required: true
30 | - type: textarea
31 | id: screenshot
32 | attributes:
33 | label: "Screenshots"
34 | description: If applicable, add screenshots to help explain your problem.
35 | validations:
36 | required: false
37 | - type: textarea
38 | id: logs
39 | attributes:
40 | label: "Logs"
41 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
42 | render: bash
43 | validations:
44 | required: false
45 | - type: input
46 | id: whatsapp
47 | attributes:
48 | label: "Whatsapp Version"
49 | description: What version of whatsapp are you using?
50 | placeholder: ex. v2.22.13.77
51 | validations:
52 | required: true
53 | - type: dropdown
54 | id: os
55 | attributes:
56 | label: "OS"
57 | description: What environment are you having the problem on?
58 | multiple: false
59 | options:
60 | - Windows
61 | - Linux
62 | - Mac
63 | - Android (termux)
64 | - Replit
65 | validations:
66 | required: true
67 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Discussion
4 | about: Discuss your question/problem here
5 | url: https://github.com/ArugaZ/whatsapp-bot/discussions/new
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: "💡 Feature Request"
2 | description: Create a new ticket for a new feature request
3 | title: "[REQ] "
4 | labels: ["enhancement"]
5 | assignees:
6 | - arugaz
7 | - tobyg74
8 | body:
9 | - type: markdown
10 | attributes:
11 | value: |
12 | **Before adding this issue, make sure you do the following to make sure this is not a duplicate:**
13 | 1. Search through the repo's previous issues
14 | 2. Read through the readme at least once
15 | 3. Search the docs for the feature you're looking for
16 | - type: textarea
17 | id: features
18 | attributes:
19 | label: "Just describe the feature"
20 | description: Provide a brief explanation of the feature
21 | placeholder: A few specific words about your feature request.
22 | validations:
23 | required: true
24 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | time: "22:00"
8 | allow:
9 | - dependency-type: "production"
10 | ignore:
11 | - dependency-name: "axios"
12 | versions: ["0.x", "1.x"]
13 | - dependency-name: "jimp"
14 | versions: ["0.x", "1.x"]
15 | open-pull-requests-limit: 10
16 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - "master"
7 |
8 | jobs:
9 | check-lint:
10 | runs-on: "ubuntu-latest"
11 | strategy:
12 | matrix:
13 | node: ["lts/gallium", "lts/hydrogen"]
14 |
15 | name: Node ${{ matrix.node }}
16 | steps:
17 | - uses: actions/checkout@v3
18 |
19 | - name: Install Node
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: ${{ matrix.node }}
23 |
24 | - name: Install packages
25 | run: npm install
26 |
27 | - name: Check linting
28 | run: npm run lint
29 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Mark stale issues and pull requests
2 |
3 | on:
4 | schedule:
5 | - cron: "30 1 * * *"
6 |
7 | jobs:
8 | stale:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/stale@v3
12 | with:
13 | repo-token: ${{ secrets.GITHUB_TOKEN }}
14 | stale-issue-message: "This issue is stale because it has been open 6 days with no activity. Remove the stale label or comment or this will be closed in 2 days"
15 | stale-pr-message: "This PR is stale because it has been open 6 days with no activity. Remove the stale label or comment or this will be closed in 2 days"
16 | days-before-stale: 6
17 | days-before-close: 2
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # we dont need them
2 | dist
3 | node_modules
4 | .pnp
5 | .pnp.js
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 | .pnpm-debug.log*
10 | .DS_Store
11 | *.pem
12 | *.tsbuildinfo
13 | yarn.lock
14 | pnpm-lock.yaml
15 | package-lock.json
16 | .env
17 |
18 | # database ignore
19 | prisma/generated
20 | database/*
21 | !database/*.json
22 |
23 | # ignore some languages
24 | languages/*
25 | !languages/id.json
26 | !languages/en.json
27 |
28 | #my private files
29 | werewolf*.ts
30 | test*.js
31 | test*.ts
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "endOfLine": "auto",
4 | "printWidth": 333,
5 | "semi": false,
6 | "tabWidth": 2,
7 | "trailingComma": "none",
8 | "overrides": [
9 | {
10 | "files": ["src/types/**/*.ts", "src/commands/**/*.ts", "src/utils/**/*.ts"],
11 | "options": {
12 | "printWidth": 150
13 | }
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | arugaastri@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | I would ❤️ it if you contributed to the project and helped make **whatsapp-bot** even better. We will make sure that contributing to **whatsapp-bot** is easy, enjoyable, and educational for anyone and everyone. All contributions are welcome, including features, issues, documentation, guides, and more.
4 |
5 | ## Found a bug?
6 |
7 | If you find a bug in the source code, you can help us by [submitting an issue](https://github.com/arugaz/whatsapp-bot/issues/new?assignees=arugaz,tobyg74&labels=bug&template=bug_report.yml&title=[BUG]+) to our GitHub Repository. Even better, you can submit a Pull Request with a fix.
8 |
9 | ## Missing a feature?
10 |
11 | You can request a new feature by [submitting an issue](https://github.com/arugaz/whatsapp-bot/issues/new?assignees=arugaz,tobyg74&labels=enhancement&template=feature_request.yml&title=[REQ]+) to our GitHub Repository.
12 |
13 | If you'd like to implement a new feature, it's always good to be in touch with us before you invest time and effort, since not all features can be supported.
14 |
15 | - For a Major Feature, first open an issue and outline your proposal. This will let us coordinate efforts, prevent duplication of work, and help you craft the change so that it's successfully integrated into the project.
16 | - Small Features can be crafted and directly [submitted as a Pull Request](#submit-pr).
17 |
18 | ## What do you need to know to help?
19 |
20 | If you want to help out with a code contribution, our project uses the following stack:
21 |
22 | - [Node.JS](https://nodejs.org/)
23 | - [TypeScript](https://www.typescriptlang.org/docs)
24 | - [Prisma](https://www.prisma.io/docs/) (with [mongodb](https://www.mongodb.com/docs/))
25 |
26 | # How do I make a code contribution?
27 |
28 | ## Good first issues
29 |
30 | Are you new to open source contribution? Wondering how contributions work in our project? Here's a quick rundown.
31 |
32 | Find an issue that you're interested in addressing, or a feature that you'd like to add.
33 | You can use [this view](https://github.com/ArugaZ/whatsapp-bot/issues?q=is:issue+label:"good+first+issue") which helps new contributors find easy gateways into our project.
34 |
35 | ## Step 1: Make a fork
36 |
37 | Fork the **whatsapp-bot** repository to your GitHub organization. This means that you'll have a copy of the repository under _your-GitHub-username/repository-name_.
38 |
39 | ## Step 2: Clone the repository to your local machine
40 |
41 | ```
42 | git clone https://github.com/{your-GitHub-username}/whatsapp-bot.git
43 | ```
44 |
45 | ## Step 3: Prepare the development environment
46 |
47 | Set up and run the development environment on your local machine:
48 |
49 | **BEFORE** you run the following steps make sure:
50 |
51 | 1. You are using node version: 16.x.x or greater
52 | 2. You have `FFmpeg` installed on your machine
53 |
54 | ```shell
55 | cd whatsapp-bot
56 | npm install
57 | ```
58 |
59 | ## Step 4: Create a branch
60 |
61 | Create a new branch for your changes.
62 | In order to keep branch names uniform and easy-to-understand, please use the following conventions for branch naming.
63 |
64 | ```jsx
65 | git checkout -b branch-name-here
66 | ```
67 |
68 | ## Step 5: Make your changes
69 |
70 | Update the code with your bug fix or new feature.
71 |
72 | ## Step 6: Check eslint rules
73 |
74 | ```bash
75 | npm run lint
76 | ```
77 |
78 | ## Step 7: Add the changes that are ready to be committed
79 |
80 | Stage the changes that are ready to be committed:
81 |
82 | ```jsx
83 | git add .
84 | ```
85 |
86 | ## Step 8: Commit the changes (Git)
87 |
88 | Commit the changes with a short message. (See below for more details on how we structure our commit messages)
89 |
90 | ```jsx
91 | git commit -m ": "
92 | ```
93 |
94 | ## Step 9: Push the changes to the remote repository
95 |
96 | Push the changes to the remote repository using:
97 |
98 | ```jsx
99 | git push origin branch-name-here
100 | ```
101 |
102 | ## Step 10: Create Pull Request
103 |
104 | In GitHub, do the following to submit a pull request to the upstream repository:
105 |
106 | 1. Give the pull request a title and a short description of the changes made. Include also the issue or bug number associated with your change. Explain the changes that you made, any issues you think exist with the pull request you made, and any questions you have for the maintainer.
107 |
108 | Remember, it's okay if your pull request is not perfect (no pull request ever is). The reviewer will be able to help you fix any problems and improve it!
109 |
110 | 2. Wait for the pull request to be reviewed by a maintainer.
111 |
112 | 3. Make changes to the pull request if the reviewing maintainer recommends them.
113 |
114 | Celebrate your success after your pull request is merged :-)
115 |
116 | ## Git Commit Messages
117 |
118 | We structure our commit messages like this:
119 |
120 | ```
121 | :
122 | ```
123 |
124 | Example
125 |
126 | ```
127 | fix: missing entity on init
128 | ```
129 |
130 | ### Types:
131 |
132 | - **feat**: A new feature
133 | - **fix**: A bug fix
134 | - **docs**: Changes to the documentation
135 | - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.)
136 | - **refactor**: A code change that neither fixes a bug nor adds a feature
137 | - **perf**: A code change that improves performance
138 | - **test**: Adding missing or correcting existing tests
139 | - **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation
140 |
141 | ## Code of conduct
142 |
143 | Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
144 |
145 | [Code of Conduct](CODE_OF_CONDUCT.md)
146 |
147 | Our Code of Conduct means that you are responsible for treating everyone on the project with respect and courtesy.
148 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
9 |

10 |
11 |
12 |
13 | ---
14 |
15 |
19 |
27 |
47 |
48 |
Don't forget to click ⭐️ and fork this repository
49 |
50 |
51 | ---
52 |
53 | This whatsapp bot project (now) use
54 | Baileys Multi-Device.
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | ---
64 |
65 |
66 | whatsapp-bot out-of-the-box support on...
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | Need help? please create an issues
81 |
82 | ---
83 |
84 | ## Getting Started
85 |
86 | This project require
87 |
88 | - [NodeJS](https://nodejs.org/en/download/) [v16 or greater](https://nodejs.org/dist/)
89 | - [FFMPEG](https://ffmpeg.org/download.html)
90 |
91 | ## Install
92 |
93 |
94 |
95 | ### Clone this project
96 |
97 | ```bash
98 | git clone https://github.com/ArugaZ/whatsapp-bot.git
99 | cd whatsapp-bot
100 | ```
101 |
102 | ### Install the dependencies:
103 |
104 | ```bash
105 | npm install
106 | ```
107 |
108 | ### Setup
109 |
110 | `Setup prisma`
111 |
112 | Create .env file based on .env.example, copy the mongo database URL into .env file
113 |
114 | ```bash
115 | npx prisma db push
116 | ```
117 |
118 | `Build a project`
119 |
120 | [Why build into javascript?](https://pm2.io/docs/runtime/integration/transpilers/)
121 |
122 | ```bash
123 | npm run build
124 | ```
125 |
126 | `If you want generate more languages`
127 |
128 | It will generate languages based on list [database/languages.json](database/languages.json). You can remove the languages you don't want to use (it will store the object into memory), except ([English (en)](languages/en.json) and [Indonesian (id)](languages/id.json)) because its already generate by default.
129 |
130 | ```bash
131 | # try this:
132 | npm run generateLang
133 | # if get incompatible error, try run:
134 | npm run generateLangV2
135 | ```
136 |
137 |
138 |
139 | ## Usage
140 |
141 |
142 |
143 | `Run the Whatsapp bot`
144 |
145 | ```bash
146 | npm start
147 | ```
148 |
149 | `With pm2`
150 |
151 | If you haven't installed pm2.
152 |
153 | ```bash
154 | npm install -g pm2
155 | ```
156 |
157 | Run with pm2
158 |
159 | ```bash
160 | npm run start:pm2
161 | ```
162 |
163 | Other tips with pm2:
164 |
165 | > This will start your application when the server is restarted or rebooted.
166 |
167 | ```bash
168 | # if you are on a unix
169 | pm2 startup #create a startup script
170 | pm2 save #save the current app
171 |
172 | # if you are on windows
173 | npm install pm2-windows-startup -g #install helper dependency
174 | pm2-startup install #create a startup script
175 | pm2 save #save the current app
176 | ```
177 |
178 | after running it you need to scan the qr
179 |
180 |
181 |
182 | ## Contributing
183 |
184 |
185 |
186 | If you've ever wanted to contribute to open source, now is your chance!
187 |
188 | Please refer to our [Contribution Guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md).
189 |
190 |
191 |
192 | ## License
193 |
194 |
195 |
196 | since **whatsapp-bot** use [baileys](https://github.com/adiwajshing/Baileys) is now available under the GPL-3.0 license. See the [LICENSE](LICENSE) file for more info.
197 |
198 |
199 |
200 | ---
201 |
202 |
203 |
Join the community to build Whatsapp-Bot together!
204 |
205 |
206 |
207 |
208 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
--------------------------------------------------------------------------------
/bin/generateLang:
--------------------------------------------------------------------------------
1 | /* created by tobyg74 */
2 | ゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) - (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚ー゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (o^_^o))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');for(var W=D,h=d,I=q,m=q,f=q,r=d,e=q,n=q,t=D,a=D,s=D,c=D,u=C();;)try{if(990784==+parseInt(t(332))+parseInt(e(289))/2*(-parseInt(a(279))/3)+-parseInt(a(277))/4+parseInt(s(339))/5+-parseInt(s(338))/6*(-parseInt(c(326))/7)+parseInt(r(314,"TL0N"))/8*(-parseInt(e(333))/9)+parseInt(n(327))/10*(parseInt(a(328))/11))break;u.push(u.shift())}catch(o){u.push(u.shift())}゚ω゚ノ=/`m´)ノ ~┻━┻ /._,o=゚ー゚=_=3,g=゚Θ゚=゚ー゚-゚ー゚,゚Д゚=゚Θ゚=(o^_^o)/(o^_^o),(゚Д゚={"゚Θ゚":"_","゚ω゚ノ":((3==゚ω゚ノ)+"_")[゚Θ゚],"゚ー゚ノ":(゚ω゚ノ+"_")[o^_^o-゚Θ゚],"゚Д゚ノ":((3==゚ー゚)+"_")[゚ー゚]})[゚Θ゚]=((3==゚ω゚ノ)+"_")[g^_^o],゚Д゚.c=(゚Д゚+"_")[゚ー゚+゚ー゚-゚Θ゚],゚Д゚.o=(゚Д゚+"_")[゚Θ゚],゚o゚=゚Д゚.c+゚Д゚.o+(゚ω゚ノ+"_")[゚Θ゚]+((3==゚ω゚ノ)+"_")[゚ー゚]+(゚Д゚+"_")[゚ー゚+゚ー゚]+((3==゚ー゚)+"_")[゚Θ゚]+((3==゚ー゚)+"_")[゚ー゚-゚Θ゚]+゚Д゚.c+(゚Д゚+"_")[゚ー゚+゚ー゚]+゚Д゚.o+((3==゚ー゚)+"_")[゚Θ゚],゚Д゚._=(o^_^o)[゚o゚][゚o゚],゚ε゚=((3==゚ー゚)+"_")[゚Θ゚]+゚Д゚["゚Д゚ノ"]+(゚Д゚+"_")[゚ー゚+゚ー゚]+((3==゚ー゚)+"_")[o^_^o-゚Θ゚]+((3==゚ー゚)+"_")[゚Θ゚]+(゚ω゚ノ+"_")[゚Θ゚],゚ー゚+=゚Θ゚,゚Д゚[゚ε゚]="\\",゚Д゚[I(269)]=(゚Д゚+゚ー゚)[o^_^o-゚Θ゚],o゚ー゚o=(゚ω゚ノ+"_")[g^_^o],゚Д゚[゚o゚]='"',゚Д゚._(゚Д゚._(゚ε゚+゚Д゚[゚o゚]+゚Д゚[゚ε゚]+゚Θ゚+((o^_^o)+(o^_^o))+((o^_^o)+(o^_^o))+゚Д゚[゚ε゚]+゚Θ゚+゚ー゚+゚Θ゚+゚Д゚[゚ε゚]+゚Θ゚+((o^_^o)+(o^_^o))+((o^_^o)-゚Θ゚)+゚Д゚[゚ε゚]+゚ー゚+(g^_^o)+゚Д゚[゚ε゚]+゚Θ゚+゚ー゚+゚Θ゚+゚Д゚[゚ε゚]+゚Θ゚+((o^_^o)+(o^_^o))+((o^_^o)-゚Θ゚)+゚Д゚[゚ε゚]+゚Θ゚+((o^_^o)+(o^_^o))+(゚ー゚+゚Θ゚)+゚Д゚[゚ε゚]+゚Θ゚+゚ー゚+(゚ー゚+(o^_^o))+゚Д゚[゚ε゚]+゚Θ゚+゚ー゚+゚Θ゚+゚Д゚[゚ε゚]+゚Θ゚+(゚ー゚+(o^_^o))+((o^_^o)-゚Θ゚)+゚Д゚[゚ε゚]+゚Θ゚+゚Θ゚+((o^_^o)+(o^_^o))+゚Д゚[゚ε゚]+゚Θ゚+((o^_^o)+(o^_^o))+゚ー゚+゚Д゚[゚ε゚]+゚Θ゚+(゚ー゚+゚Θ゚)+(゚ー゚+(o^_^o))+゚Д゚[゚ε゚]+゚Θ゚+゚ー゚+((o^_^o)-゚Θ゚)+゚Д゚[゚ε゚]+゚Θ゚+(゚ー゚+(o^_^o))+((o^_^o)-゚Θ゚)+゚Д゚[゚ε゚]+゚ー゚+(g^_^o)+゚Д゚[゚ε゚]+(゚ー゚+(o^_^o))+(゚ー゚+゚Θ゚)+゚Д゚[゚ε゚]+゚ー゚+(g^_^o)+゚Д゚[゚ε゚]+゚ー゚+((o^_^o)-゚Θ゚)+゚Д゚[゚ε゚]+((o^_^o)+(o^_^o))+゚Θ゚+゚Д゚[゚ε゚]+((o^_^o)+(o^_^o))+((o^_^o)-゚Θ゚)+゚Д゚[゚ε゚]+((o^_^o)+(o^_^o))+(o^_^o)+゚Д゚[゚ε゚]+゚ー゚+((o^_^o)-゚Θ゚)+゚Д゚[゚o゚])(゚Θ゚))("_");for(var i=z,p=z,v=B();;)try{if(362033==-parseInt(p(139))+-parseInt(p(141))/2*(-parseInt(p(179))/3)+parseInt(p(186))/4+-parseInt(p(140))/5*(-parseInt(p(167))/6)+parseInt(p(144))/7*(-parseInt(p(177))/8)+-parseInt(p(148))/9+parseInt(p(165))/10)break;v[I(325)](v[h(335,"rM^R")]())}catch(o){v.push(v.shift())}function d(a,o){var s=C();return(d=function(o,r){var e=s[o-=261],n=(void 0===d.GwyVcY&&(d.TzouRs=function(o,r){var e,n=[],t=0,a="";for(o=function(o){for(var r,e,n="",t="",a=0,s=0;e=o.charAt(s++);~e&&(r=a%4?64*r+e:e,a++%4)&&(n+=String.fromCharCode(255&r>>(-2*a&6))))e="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=".indexOf(e);for(var c=0,u=n.length;c>(-2*a&6))))e="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=".indexOf(e);for(var c=0,u=n.length;co+r*Math[t(474)](256,e),0):o[t(439)](8,12)[n(143)]((o,r,e)=>o+r*Math[t(474)](256,e),0);throw new Error(r(316,"km2!"))}(e));let c="";if(1=16.0.0"
28 | },
29 | "dependencies": {
30 | "@arugaz/ai2d": "^1.0.1-z.9",
31 | "@arugaz/collection": "^1.0.0-z.1",
32 | "@arugaz/eventemitter": "^1.0.0-z.2",
33 | "@arugaz/formatter": "^1.0.0-z.1",
34 | "@arugaz/queue": "^1.0.1-z.1",
35 | "@arugaz/translator": "^1.0.0-z.4",
36 | "@hidden-finder/didyoumean": "^1.1.0",
37 | "@prisma/client": "^5.3.0",
38 | "awesome-phonenumber": "^6.1.0",
39 | "axios": "^1.5.0",
40 | "baileys": "github:arugaz/node-whatsapp",
41 | "cfonts": "^3.1.1",
42 | "cheerio": "^1.0.0-rc.12",
43 | "croner": "^8.0.0",
44 | "dotenv": "^16.0.3",
45 | "fastify": "^4.12.0",
46 | "form-data": "^4.0.0",
47 | "jimp": "^0.16.1",
48 | "link-preview-js": "^3.0.4",
49 | "node-webpmux": "^3.1.3",
50 | "qrcode": "^1.5.1"
51 | },
52 | "devDependencies": {
53 | "@types/node": "^18.11.18",
54 | "@types/qrcode": "^1.5.0",
55 | "@typescript-eslint/eslint-plugin": "^5.47.1",
56 | "@typescript-eslint/parser": "^5.47.1",
57 | "eslint": "^8.30.0",
58 | "eslint-config-standard-with-typescript": "^24.0.0",
59 | "eslint-plugin-import": "^2.26.0",
60 | "eslint-plugin-n": "^15.6.0",
61 | "eslint-plugin-promise": "^6.1.1",
62 | "prettier": "^2.8.1",
63 | "prisma": "^5.3.0",
64 | "rimraf": "^3.0.2",
65 | "tsc-watch": "^6.0.0",
66 | "typescript": "^5.3.3"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | generator client {
5 | provider = "prisma-client-js"
6 | }
7 |
8 | // u can use other providers like, postgreSQL, mariaDB, mySQL or other, just read prisma docs
9 | datasource db {
10 | provider = "mongodb"
11 | url = env("MONGODB_URL")
12 | }
13 |
14 | /// Storing sessions information
15 | model Session {
16 | id String @id @default(auto()) @map("_id") @db.ObjectId
17 | sessionId String @unique
18 | session String?
19 | }
20 |
21 | /// Storing group information
22 | model Group {
23 | id String @id @default(auto()) @map("_id") @db.ObjectId
24 | /// Whatsapp group Jid
25 | groupId String @unique
26 | /// Group Name
27 | name String
28 | /// Whatsapp group default language
29 | language String
30 | /// Opts for notify all group events
31 | notify Boolean @default(false)
32 | /// Opts for notify welcome message
33 | welcome Boolean @default(false)
34 | /// Opts for notify leave message
35 | leave Boolean @default(false)
36 | /// Is Group got ban?
37 | ban Boolean @default(false)
38 | /// Is Group got mute?
39 | mute Boolean @default(false)
40 | /// Opts for anti whatsapp group link
41 | antilink Boolean @default(false)
42 | /// Opts for anti other whatsapp bot
43 | antibot Boolean @default(false)
44 | /// Opts for anti viewonce message
45 | antiviewonce Boolean @default(false)
46 | /// Opts for anti other country, by country number code
47 | anticountry AntiCountry
48 | }
49 |
50 | type AntiCountry {
51 | /// ["51", "60"] will kick member that has number that start from "51, 60" (List country number code)
52 | number String[]
53 | /// Its active?
54 | active Boolean
55 | }
56 |
57 | model GroupMetadata {
58 | id String @id @default(auto()) @map("_id") @db.ObjectId
59 | /// Whatsapp group Jid
60 | groupId String @unique
61 | /// Whatsapp group Name
62 | subject String
63 | /// Whatsapp group creation date
64 | creation Int
65 | /// Whatsapp group owner
66 | owner String
67 | /// Whatsapp group description
68 | desc String
69 | /// Whatsapp group restrict, only admin allow to edit group
70 | restrict Boolean @default(false)
71 | /// Whatsapp group restrict, only admin allow to send message
72 | announce Boolean @default(false)
73 | /// Whatsapp group participant list
74 | participants Participants[]
75 | }
76 |
77 | type Participants {
78 | /// Participant Jid
79 | id String
80 | /// Is participant an admin?
81 | admin GroupAdmin?
82 | }
83 |
84 | enum GroupAdmin {
85 | admin
86 | superadmin
87 | }
88 |
89 | /// Storing users session
90 | model User {
91 | id String @id @default(auto()) @map("_id") @db.ObjectId
92 | /// Whatsapp user Jid
93 | userId String @unique
94 | /// Whatsapp user Name
95 | name String
96 | /// Whatsapp user language
97 | language String
98 | /// Whatsapp user limit
99 | limit Int
100 | /// Is user got ban?
101 | ban Boolean @default(false)
102 | /// User role
103 | role Role @default(basic)
104 | /// User role expired if has role (premium, vip)
105 | expire Int @default(0)
106 | }
107 |
108 | enum Role {
109 | basic
110 | premium
111 | vip
112 | }
113 |
--------------------------------------------------------------------------------
/src/commands/convert/sticker.ts:
--------------------------------------------------------------------------------
1 | import { WASticker } from "../../libs/convert"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | const wasticker = new WASticker({
6 | pack: config.name,
7 | author: config.footer,
8 | categories: ["👋"]
9 | })
10 |
11 | export default {
12 | category: "convert",
13 | aliases: ["stiker", "s"],
14 | cd: 5,
15 | desc: "Create sticker from photo or video",
16 | example: `
17 | Send a image/video message with caption
18 | @PREFIX@CMD
19 | --------
20 | or Reply a image/video message with text
21 | @PREFIX@CMD
22 | --------
23 | `.trimEnd(),
24 | execute: async ({ aruga, message }) => {
25 | if (message.type.includes("image") || (message.quoted && message.quoted.type.includes("image"))) {
26 | const buffer = message.quoted ? await aruga.downloadMediaMessage(message.quoted) : await aruga.downloadMediaMessage(message)
27 | const result = await wasticker.ConvertMedia(buffer, "image")
28 |
29 | return await aruga.sendMessage(message.from, { sticker: result }, { quoted: message, ephemeralExpiration: message.expiration })
30 | }
31 |
32 | if (message.type.includes("video") || (message.quoted && message.quoted.type.includes("video"))) {
33 | const duration = message.quoted ? message.quoted.message.videoMessage.seconds : message.message.videoMessage.seconds
34 | if (duration && !isNaN(duration) && duration > 10) throw "Video duration is too long! Maximum duration of 10 seconds"
35 |
36 | const buffer = message.quoted ? await aruga.downloadMediaMessage(message.quoted) : await aruga.downloadMediaMessage(message)
37 | const result = await wasticker.ConvertMedia(buffer, "video")
38 |
39 | return await aruga.sendMessage(message.from, { sticker: result }, { quoted: message, ephemeralExpiration: message.expiration })
40 | }
41 |
42 | if (message.type.includes("sticker") || (message.quoted && message.quoted.type.includes("sticker"))) {
43 | const buffer = message.quoted ? await aruga.downloadMediaMessage(message.quoted) : await aruga.downloadMediaMessage(message)
44 | const result = await wasticker.ConvertExif(buffer)
45 |
46 | return await aruga.sendMessage(message.from, { sticker: result }, { quoted: message, ephemeralExpiration: message.expiration })
47 | }
48 |
49 | throw "noCmd"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/commands/convert/toanime.ts:
--------------------------------------------------------------------------------
1 | import AI2D from "@arugaz/ai2d"
2 | import type { Command } from "../../types/command"
3 |
4 | export default {
5 | category: "convert",
6 | cd: 10,
7 | desc: "Generate a hyper-realistic photo an anime style",
8 | example: `
9 | Send a image message with caption
10 | @PREFIX@CMD
11 | --------
12 | or Reply a image message with text
13 | @PREFIX@CMD
14 | --------
15 | `.trimEnd(),
16 | execute: async ({ aruga, message }) => {
17 | if (message.type.includes("image") || (message.quoted && message.quoted.type.includes("image"))) {
18 | const buffer = message.quoted ? await aruga.downloadMediaMessage(message.quoted) : await aruga.downloadMediaMessage(message)
19 | // if it doesnt work in your country/server you may need a proxy
20 | // refer to https://www.npmjs.com/package/@arugaz/ai2d#tldr
21 | const result = await AI2D(buffer, {
22 | crop: "SINGLE"
23 | // proxy: "socks5://111.222.333.444:1234"
24 | })
25 | return await aruga.sendMessage(message.from, { image: result }, { quoted: message, ephemeralExpiration: message.expiration })
26 | }
27 |
28 | throw "noCmd"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/commands/convert/toimage.ts:
--------------------------------------------------------------------------------
1 | import { WebpToImage } from "../../libs/convert"
2 | import type { Command } from "../../types/command"
3 |
4 | export default {
5 | category: "convert",
6 | aliases: ["toimg"],
7 | cd: 5,
8 | desc: "Convert a sticker message to image",
9 | example: `
10 | Reply a sticker message with text
11 | @PREFIX@CMD
12 | --------
13 | `.trimEnd(),
14 | execute: async ({ aruga, message }) => {
15 | if (message.quoted && message.quoted.type.includes("sticker")) {
16 | if (message.quoted.message.stickerMessage.isAnimated) throw "Not supported yet!"
17 | const buffer = await aruga.downloadMediaMessage(message.quoted)
18 |
19 | const result = await WebpToImage(buffer)
20 |
21 | return await aruga.sendMessage(message.from, { image: result }, { quoted: message, ephemeralExpiration: message.expiration })
22 | }
23 |
24 | throw "noCmd"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/commands/general/didumean_.ts:
--------------------------------------------------------------------------------
1 | import { didyoumean, similarity } from "@hidden-finder/didyoumean"
2 | import { commands } from "../../libs/whatsapp/command"
3 | import i18n from "../../libs/international"
4 | import config from "../../utils/config"
5 | import type { Event } from "../../types/command"
6 |
7 | export default {
8 | execute: async ({ command, user, prefix, message }) => {
9 | if (!command) return
10 |
11 | const hasCmd = commands.get(command) ?? commands.find((v) => v.aliases && v.aliases.includes(command))
12 | if (hasCmd) return
13 |
14 | const mean = didyoumean(command, [
15 | ...commands.keys(),
16 | ...new Set(
17 | commands
18 | .map((v) => v.aliases)
19 | .filter((v) => v)
20 | .flat()
21 | )
22 | ])
23 |
24 | const cmd = commands.get(mean) ?? commands.find((v) => v.aliases && v.aliases.includes(mean))
25 |
26 | const keyCmd = commands.findKey((v) => v === cmd)
27 | const listCmds = cmd.aliases.length !== 0 ? cmd.aliases.concat(keyCmd) : [keyCmd]
28 |
29 | let addText = ""
30 | for (const cmd of listCmds) {
31 | addText += `┃ *${prefix}${cmd}*\n`
32 | addText += `┃ ${i18n.translate("commands.general.didyoumean.same", {}, user.language)}: ${(similarity(command, cmd) * 100).toFixed(2)}%\n`
33 | addText += `┃ \n`
34 | }
35 |
36 | const text =
37 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
38 | "┃\n" +
39 | `┃ ${i18n.translate("commands.general.didyoumean.title", {}, user.language)}\n` +
40 | "┃\n" +
41 | addText +
42 | `┗━━「 ꗥ${config.name}ꗥ 」`
43 |
44 | return message.reply(text, true)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/commands/general/language.ts:
--------------------------------------------------------------------------------
1 | import { readFile } from "fs/promises"
2 | import { join as pathJoin } from "path"
3 | import i18n from "../../libs/international"
4 | import config from "../../utils/config"
5 | import { database } from "../../libs/whatsapp"
6 | import type { Command } from "../../types/command"
7 |
8 | export default {
9 | category: "general",
10 | aliases: ["lang"],
11 | desc: "Show/Set user language",
12 | privateOnly: true,
13 | execute: async ({ message, prefix, args, user, command }) => {
14 | const listLanguages: {
15 | iso: string
16 | lang: string
17 | }[] = JSON.parse(await readFile(pathJoin(__dirname, "..", "..", "..", "database", "languages.json"), "utf-8"))
18 |
19 | if (args.length >= 1 && !!listLanguages.find((value) => value.iso === args[0])) {
20 | const lang = listLanguages.find((value) => value.iso === args[0])
21 | const user = await database.updateUser(message.sender, { language: lang.iso })
22 | return await message.reply(i18n.translate("commands.general.language.changed", { "@LANGUAGE": lang.lang }, user.language), true)
23 | }
24 |
25 | let listLang = ""
26 |
27 | for (const lang of listLanguages.filter((v) => i18n.listLanguage().includes(v.iso)).sort((first, last) => first.lang.localeCompare(last.lang))) {
28 | listLang += `┃ > ${lang.lang}\n┃ ${prefix}${command} ${lang.iso}\n┃\n`
29 | }
30 |
31 | const text =
32 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
33 | "┃\n" +
34 | `┃ ${i18n.translate("commands.general.language.text", {}, user.language)}\n` +
35 | "┃\n" +
36 | "┣━━━━━━━━━━━━━━━━━━\n" +
37 | "┃\n" +
38 | listLang +
39 | "┣━━━━━━━━━━━━━━━━━━\n" +
40 | "┃\n" +
41 | `┗━━「 ꗥ${config.name}ꗥ 」`
42 |
43 | return await message.reply(text, true)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/commands/general/menu.ts:
--------------------------------------------------------------------------------
1 | import os from "os"
2 | import i18n from "../../libs/international"
3 | import config from "../../utils/config"
4 | import { command } from "../../libs/whatsapp"
5 | import { sizeFormat, timeFormat, upperFormat } from "../../utils/format"
6 | import type { Command } from "../../types/command"
7 |
8 | export default {
9 | aliases: ["help"],
10 | category: "general",
11 | desc: "Landing menu",
12 | maintenance: false,
13 | execute: async ({ message, prefix, user, args, isOwner }) => {
14 | if (args.length === 1) {
15 | const name = args[0].toLowerCase()
16 | const cmd = command.commands.get(name) ?? command.commands.find((cmd) => cmd.aliases && cmd.aliases.includes(name))
17 | if (!cmd || (cmd.category === "owner" && !isOwner))
18 | return await message.reply(i18n.translate("commands.general.menu.cmd.zero", {}, user.language), true)
19 | const text =
20 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
21 | "┃\n" +
22 | `┃ *${i18n.translate("commands.general.menu.cmd.one", {}, user.language)} :* ${
23 | cmd.aliases ? [name].concat(cmd.aliases).join(", ").trim() : name
24 | }\n` +
25 | `┃ *${i18n.translate("commands.general.menu.cmd.two", {}, user.language)} :* ${cmd.category || "-"}\n` +
26 | `┃ *${i18n.translate("commands.general.menu.cmd.three", {}, user.language)} :* ${cmd.desc || "-"}\n` +
27 | `┃ *${i18n.translate("commands.general.menu.cmd.four", {}, user.language)} :* ${
28 | cmd.example
29 | ? cmd.example
30 | .replace(/@PREFIX/g, prefix)
31 | .replace(/@CMD/g, name)
32 | .trimEnd()
33 | .split(/\r\n|\r|\n/)
34 | .join("\n┃ ")
35 | .trimEnd()
36 | : `${prefix}${name}`
37 | }\n` +
38 | `┃ *${i18n.translate("commands.general.menu.cmd.five", {}, user.language)} :* ${cmd.groupOnly ? "✔️" : "✖️"}\n` +
39 | (message.isGroupMsg
40 | ? `┃ *${i18n.translate("commands.general.menu.cmd.six", {}, user.language)}:* ${cmd.adminGroup ? "✔️" : "✖️"}\n` +
41 | `┃ *${i18n.translate("commands.general.menu.cmd.seven", {}, user.language)} :* ${cmd.ownerGroup ? "✔️" : "✖️"}\n`
42 | : "") +
43 | `┃ *${i18n.translate("commands.general.menu.cmd.eight", {}, user.language)} :* ${cmd.privateOnly ? "✔️" : "✖️"}\n` +
44 | `┃ *${i18n.translate("commands.general.menu.cmd.nine", {}, user.language)} :* ${cmd.premiumOnly ? "✔️" : "✖️"}\n` +
45 | `┃ *${i18n.translate("commands.general.menu.cmd.ten", {}, user.language)} :* ${cmd.ownerOnly ? "✔️" : "✖️"}\n` +
46 | `┃ *${i18n.translate("commands.general.menu.cmd.eleven", {}, user.language)} :* ${cmd.limit ? cmd.limit : "✖️"}\n` +
47 | `┃ *${i18n.translate("commands.general.menu.cmd.twelve", {}, user.language)} :* ${cmd.cd ? (cmd.cd % 1000) + "s" : "3s"}\n` +
48 | `┃ *${i18n.translate("commands.general.menu.cmd.thirteen", {}, user.language)} :* ${cmd.maintenance ? "✔️" : "✖️"}\n` +
49 | "┃\n" +
50 | `┗━━「 ꗥ${config.name}ꗥ 」`
51 | return await message.reply(text, true)
52 | }
53 |
54 | let listCmd = ""
55 |
56 | for (const category of [
57 | ...new Set(
58 | command.commands
59 | .map((v) => v.category)
60 | .filter((v) => (!isOwner && v === "owner" ? null : v && (!message.isGroupMsg && v === "group" ? null : v)))
61 | .sort()
62 | )
63 | ]) {
64 | listCmd += `┣━━━━━━━━━━━━━━━━━━\n┃\n┃${upperFormat(category + " ")}\n`
65 | for (const eachCmd of command.commands.map((v) => v.category === category && v).filter((v) => (!isOwner && v.ownerOnly ? null : v))) {
66 | const title = command.commands.findKey((v) => v === eachCmd)
67 | listCmd += `┃ ${prefix}menu ${title}\n┃ > ${eachCmd.desc}\n┃\n`
68 | }
69 | }
70 |
71 | const text =
72 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
73 | "┃\n" +
74 | `┃ ${i18n.translate("commands.general.menu.intro.one", { "@PUSHNAME": message.pushname }, user.language)}\n` +
75 | `┃ ${i18n.translate("commands.general.menu.intro.two", {}, user.language)}\n` +
76 | "┃\n" +
77 | "┣━━━━━━━━━━━━━━━━━━\n" +
78 | "┃\n" +
79 | `┃ ${i18n.translate(
80 | "commands.general.menu.detail.one",
81 | { "@SZEE": `${sizeFormat(process.memoryUsage().heapTotal)} / ${sizeFormat(os.totalmem())}` },
82 | user.language
83 | )}\n` +
84 | `┃ ${i18n.translate("commands.general.menu.detail.two", { "@CMDS": command.commands.size }, user.language)}\n` +
85 | `┃ ${i18n.translate("commands.general.menu.detail.three", { "@UPTMS": timeFormat(os.uptime() * 1000) }, user.language)}\n` +
86 | `┃ ${i18n.translate("commands.general.menu.detail.four", {}, user.language)}\n` +
87 | "┃\n" +
88 | "┣━━━━━━━━━━━━━━━━━━\n" +
89 | "┃\n" +
90 | `┃ ${i18n.translate("commands.general.menu.info.one", {}, user.language)}\n` +
91 | `┃ ${i18n.translate("commands.general.menu.info.two", {}, user.language)}\n` +
92 | `┃ ${i18n.translate("commands.general.menu.info.three", { "@COMMANDS": `${prefix}language` }, user.language)}\n` +
93 | "┃\n" +
94 | "┣━━━━━━━━━━━━━━━━━━\n" +
95 | "┃\n" +
96 | `┃ ${i18n.translate("commands.general.menu.bottom", {}, user.language)}\n` +
97 | "┃\n" +
98 | listCmd +
99 | `┗━━「 ꗥ${config.name}ꗥ 」`
100 |
101 | return await message.reply(text, true)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/commands/group/antibot.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import { database } from "../../libs/whatsapp"
3 | import config from "../../utils/config"
4 | import type { Command } from "../../types/command"
5 |
6 | export default {
7 | category: "group",
8 | desc: "Kick other bots from group chats",
9 | groupOnly: true,
10 | adminGroup: true,
11 | botGroupAdmin: true,
12 | example: `
13 | Turn on / Activate @CMD
14 | @PREFIX@CMD on
15 | --------
16 | Turn off / Deactivate @CMD
17 | @PREFIX@CMD off
18 | --------
19 | `.trimEnd(),
20 | execute: async ({ message, args, user, group, command }) => {
21 | if (args[0] && (args[0].toLowerCase() === "on" || args[0].toLowerCase() === "enable")) {
22 | if (!group.antibot) {
23 | await database.updateGroup(message.from, {
24 | antibot: true
25 | })
26 | }
27 |
28 | const text =
29 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
30 | "┃\n" +
31 | `┃ ${i18n.translate("commands.group.antibot.enable", { "@CMD": command }, user.language)}\n` +
32 | "┃\n" +
33 | `┗━━「 ꗥ${config.name}ꗥ 」`
34 |
35 | return await message.reply(text, true)
36 | }
37 |
38 | if (args[0] && (args[0].toLowerCase() === "off" || args[0].toLowerCase() === "disable")) {
39 | if (group.antibot) {
40 | await database.updateGroup(message.from, {
41 | antibot: false
42 | })
43 | }
44 |
45 | const text =
46 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
47 | "┃\n" +
48 | `┃ ${i18n.translate("commands.group.antibot.disable", { "@CMD": command }, user.language)}\n` +
49 | "┃\n" +
50 | `┗━━「 ꗥ${config.name}ꗥ 」`
51 |
52 | return await message.reply(text, true)
53 | }
54 |
55 | throw "noCmd"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/commands/group/antibot_.ts:
--------------------------------------------------------------------------------
1 | import type { Event } from "../../types/command"
2 |
3 | export default {
4 | execute: async ({ aruga, message, group, isBotGroupAdmin }) => {
5 | if (message.isGroupMsg && isBotGroupAdmin && group.antibot && message.isBotMsg && !message.fromMe) {
6 | return await aruga.groupParticipantsUpdate(message.from, [message.sender], "remove")
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/commands/group/anticountry.ts:
--------------------------------------------------------------------------------
1 | import { getRegionCodeForCountryCode, getSupportedCallingCodes } from "awesome-phonenumber"
2 | import i18n from "../../libs/international"
3 | import { database } from "../../libs/whatsapp"
4 | import config from "../../utils/config"
5 | import { phoneFormat } from "./../../utils/format"
6 | import type { Command } from "../../types/command"
7 |
8 | export default {
9 | category: "group",
10 | desc: "Filter incoming members and kick those that are not allowed in the group",
11 | groupOnly: true,
12 | adminGroup: true,
13 | botGroupAdmin: true,
14 | example: `
15 | Add the country code to the list
16 | @PREFIX@CMD add countryCodeList
17 |
18 | eg, @PREFIX@CMD add 62 55 7
19 | --------
20 | Remove the country code from the list
21 | @PREFIX@CMD remove countryCode list
22 |
23 | eg, @PREFIX@CMD remove 55 7
24 | --------
25 | Turn on / Activate @CMD
26 | @PREFIX@CMD on
27 | --------
28 | Turn off / Deactivate @CMD
29 | @PREFIX@CMD off
30 | --------
31 | Check status of @CMD
32 | @PREFIX@CMD status
33 | --------
34 | Get all country code list
35 | @PREFIX@CMD list
36 | --------
37 | `.trimEnd(),
38 | execute: async ({ aruga, message, args, group, user, command, isBotGroupAdmin }) => {
39 | if (args[0] && args[0].toLowerCase() === "add" && args.length >= 2) {
40 | const supportCode: unknown[] = getSupportedCallingCodes()
41 | for (const numberCode of args.slice(1)) {
42 | if (supportCode.includes(parseInt(numberCode, 10)) && !group.anticountry.number.includes(numberCode))
43 | group.anticountry.number.push(numberCode)
44 | }
45 | await database.updateGroup(message.from, {
46 | anticountry: {
47 | active: group.anticountry.active,
48 | number: group.anticountry.number
49 | }
50 | })
51 |
52 | const text =
53 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
54 | "┃\n" +
55 | `┃ ${i18n.translate("commands.group.anticountry.add", { "@NUM": group.anticountry.number.join(", ").trim() }, user.language)}\n` +
56 | "┃\n" +
57 | `┗━━「 ꗥ${config.name}ꗥ 」`
58 | return await message.reply(text, true)
59 | }
60 |
61 | if (args[0] && args[0].toLowerCase() === "remove" && args.length >= 2) {
62 | const supportCode: unknown[] = getSupportedCallingCodes()
63 | for (const numberCode of args.slice(1)) {
64 | if (supportCode.includes(parseInt(numberCode, 10)) && group.anticountry.number.includes(numberCode))
65 | group.anticountry.number.splice(
66 | group.anticountry.number.findIndex((num) => num === numberCode),
67 | 1
68 | )
69 | }
70 | await database.updateGroup(message.from, {
71 | anticountry: {
72 | active: group.anticountry.active,
73 | number: group.anticountry.number
74 | }
75 | })
76 |
77 | const text =
78 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
79 | "┃\n" +
80 | `┃ ${i18n.translate(
81 | "commands.group.anticountry.remove",
82 | { "@NUM": group.anticountry.number.length ? group.anticountry.number.join(", ").trim() : "➰" },
83 | user.language
84 | )}\n` +
85 | "┃\n" +
86 | `┗━━「 ꗥ${config.name}ꗥ 」`
87 | return await message.reply(text, true)
88 | }
89 |
90 | if (args[0] && (args[0].toLowerCase() === "on" || args[0].toLowerCase() === "enable")) {
91 | if (!group.anticountry.active) {
92 | await database.updateGroup(message.from, {
93 | anticountry: {
94 | active: true,
95 | number: group.anticountry.number
96 | }
97 | })
98 |
99 | process.nextTick(async () => {
100 | for (const participant of message.groupMetadata.participants.filter(
101 | (user) => !user.admin && group.anticountry.number.includes(phoneFormat(user.id).countryCode)
102 | )) {
103 | if (isBotGroupAdmin) await aruga.groupParticipantsUpdate(message.from, [participant.id], "remove")
104 | }
105 | })
106 | }
107 |
108 | const text =
109 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
110 | "┃\n" +
111 | `┃ ${i18n.translate(
112 | "commands.group.anticountry.enable",
113 | { "@CMD": command, "@NUM": group.anticountry.number.length ? group.anticountry.number.join(", ").trim() : "➰" },
114 | user.language
115 | )}\n` +
116 | "┃\n" +
117 | `┗━━「 ꗥ${config.name}ꗥ 」`
118 | return await message.reply(text, true)
119 | }
120 |
121 | if (args[0] && (args[0].toLowerCase() === "off" || args[0].toLowerCase() === "disable")) {
122 | if (group.anticountry.active)
123 | await database.updateGroup(message.from, {
124 | anticountry: {
125 | active: false,
126 | number: group.anticountry.number
127 | }
128 | })
129 |
130 | const text =
131 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
132 | "┃\n" +
133 | `┃ ${i18n.translate(
134 | "commands.group.anticountry.disable",
135 | { "@CMD": command, "@NUM": group.anticountry.number.length ? group.anticountry.number.join(", ").trim() : "➰" },
136 | user.language
137 | )}\n` +
138 | "┃\n" +
139 | `┗━━「 ꗥ${config.name}ꗥ 」`
140 | return await message.reply(text, true)
141 | }
142 |
143 | if (args[0] && args[0].toLowerCase() === "status") {
144 | const text =
145 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
146 | "┃\n" +
147 | `┃ ${i18n.translate("commands.group.anticountry.status.text", {}, user.language)}\n` +
148 | "┃\n" +
149 | `┃ ${i18n.translate("commands.group.anticountry.status.isActive", { "@STS": group.anticountry.active ? "✔️" : "✖️" }, user.language)}\n` +
150 | `┃ ${i18n.translate(
151 | "commands.group.anticountry.status.numList",
152 | { "@NUM": group.anticountry.number.length ? group.anticountry.number.join(", ").trim() : "➰" },
153 | user.language
154 | )}\n` +
155 | "┃\n" +
156 | `┗━━「 ꗥ${config.name}ꗥ 」`
157 | return await message.reply(text, true)
158 | }
159 |
160 | if (args[0] && args[0].toLowerCase() === "list") {
161 | const text =
162 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
163 | "┃\n" +
164 | `┃ ${i18n.translate("commands.group.anticountry.list", { "@URL": "https://countrycode(.)org" }, user.language)}\n` +
165 | `┃ \n${getSupportedCallingCodes()
166 | .map((num) => `${num} ${getRegionCodeForCountryCode(num as unknown as number).replace(/\d+/g, "")}`.trim())
167 | .join(", ")
168 | .trim()}\n` +
169 | "┃\n" +
170 | `┗━━「 ꗥ${config.name}ꗥ 」`
171 | return await message.reply(text, true)
172 | }
173 |
174 | throw "noCmd"
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/commands/group/antilink.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import { database } from "../../libs/whatsapp"
3 | import config from "../../utils/config"
4 | import type { Command } from "../../types/command"
5 |
6 | export default {
7 | category: "group",
8 | desc: "Kick participant who send whatsapp group link",
9 | groupOnly: true,
10 | adminGroup: true,
11 | botGroupAdmin: true,
12 | example: `
13 | Turn on / Activate @CMD
14 | @PREFIX@CMD on
15 | --------
16 | Turn off / Deactivate @CMD
17 | @PREFIX@CMD off
18 | --------
19 | `.trimEnd(),
20 | execute: async ({ message, args, user, group, command }) => {
21 | if (args[0] && (args[0].toLowerCase() === "on" || args[0].toLowerCase() === "enable")) {
22 | if (!group.antilink) {
23 | await database.updateGroup(message.from, {
24 | antilink: true
25 | })
26 | }
27 |
28 | const text =
29 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
30 | "┃\n" +
31 | `┃ ${i18n.translate("commands.group.antilink.enable", { "@CMD": command }, user.language)}\n` +
32 | "┃\n" +
33 | `┗━━「 ꗥ${config.name}ꗥ 」`
34 |
35 | return await message.reply(text, true)
36 | }
37 |
38 | if (args[0] && (args[0].toLowerCase() === "off" || args[0].toLowerCase() === "disable")) {
39 | if (group.antilink) {
40 | await database.updateGroup(message.from, {
41 | antilink: false
42 | })
43 | }
44 |
45 | const text =
46 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
47 | "┃\n" +
48 | `┃ ${i18n.translate("commands.group.antilink.disable", { "@CMD": command }, user.language)}\n` +
49 | "┃\n" +
50 | `┗━━「 ꗥ${config.name}ꗥ 」`
51 |
52 | return await message.reply(text, true)
53 | }
54 |
55 | throw "noCmd"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/commands/group/antilink_.ts:
--------------------------------------------------------------------------------
1 | import type { Event } from "../../types/command"
2 |
3 | export default {
4 | execute: async ({ aruga, message, group, isBotGroupAdmin, isOwner, isGroupAdmin }) => {
5 | if (message.isGroupMsg && group.antilink && isBotGroupAdmin && message.body) {
6 | const groupCodeRegex = message.body.match(/chat.whatsapp.com\/(?:invite\/)?([\w\d]*)/)
7 | if (groupCodeRegex && groupCodeRegex.length === 2 && !isOwner && !isGroupAdmin) {
8 | const groupCode = groupCodeRegex[1]
9 | const groupNow = await aruga.groupInviteCode(message.from)
10 |
11 | if (groupCode !== groupNow) {
12 | await aruga.sendMessage(message.from, { delete: message.key })
13 | return await aruga.groupParticipantsUpdate(message.from, [message.sender], "remove")
14 | }
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/commands/group/antiviewonce.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import { database } from "../../libs/whatsapp"
3 | import config from "../../utils/config"
4 | import type { Command } from "../../types/command"
5 |
6 | export default {
7 | category: "group",
8 | aliases: ["antiview", "antionce"],
9 | desc: "Resend messages that send as view once",
10 | groupOnly: true,
11 | adminGroup: true,
12 | example: `
13 | Turn on / Activate @CMD
14 | @PREFIX@CMD on
15 | --------
16 | Turn off / Deactivate @CMD
17 | @PREFIX@CMD off
18 | --------
19 | `.trimEnd(),
20 | execute: async ({ message, args, user, group, command }) => {
21 | if (args[0] && (args[0].toLowerCase() === "on" || args[0].toLowerCase() === "enable")) {
22 | if (!group.antiviewonce) {
23 | await database.updateGroup(message.from, {
24 | antiviewonce: true
25 | })
26 | }
27 |
28 | const text =
29 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
30 | "┃\n" +
31 | `┃ ${i18n.translate("commands.group.antiviewonce.enable", { "@CMD": command }, user.language)}\n` +
32 | "┃\n" +
33 | `┗━━「 ꗥ${config.name}ꗥ 」`
34 |
35 | return await message.reply(text, true)
36 | }
37 |
38 | if (args[0] && (args[0].toLowerCase() === "off" || args[0].toLowerCase() === "disable")) {
39 | if (group.antiviewonce) {
40 | await database.updateGroup(message.from, {
41 | antiviewonce: false
42 | })
43 | }
44 |
45 | const text =
46 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
47 | "┃\n" +
48 | `┃ ${i18n.translate("commands.group.antiviewonce.disable", { "@CMD": command }, user.language)}\n` +
49 | "┃\n" +
50 | `┗━━「 ꗥ${config.name}ꗥ 」`
51 |
52 | return await message.reply(text, true)
53 | }
54 |
55 | throw "noCmd"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/commands/group/antiviewonce_.ts:
--------------------------------------------------------------------------------
1 | import type { Event } from "../../types/command"
2 |
3 | export default {
4 | execute: async ({ aruga, message, group }) => {
5 | if (message.isGroupMsg && group.antiviewonce && message.viewOnce) {
6 | return await aruga.resendMessage(message.from, message, { ephemeralExpiration: message.expiration })
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/commands/group/change-description.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "gdesc"
6 |
7 | export default {
8 | category: "group",
9 | aliases: ["gbio", "gcbio", "gcdesc"],
10 | desc: "Change group description",
11 | groupOnly: true,
12 | adminGroup: true,
13 | botGroupAdmin: true,
14 | example: `
15 | Change group description
16 | @PREFIX@CMD
17 | --------
18 | `.trimEnd(),
19 | execute: async ({ aruga, message, arg, group }) => {
20 | if (!arg) throw "noCmd"
21 |
22 | await aruga.groupUpdateDescription(message.from, arg)
23 |
24 | const text =
25 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
26 | "┃\n" +
27 | `┃ ${i18n.translate("commands.group.change-description", { "@ADM": `@${message.sender.split("@")[0]}` }, group.language)}\n` +
28 | `${arg}\n` +
29 | "┃\n" +
30 | `┗━━「 ꗥ${config.name}ꗥ 」`
31 |
32 | return await message.reply(text, true)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/commands/group/change-name.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "gname"
6 |
7 | export default {
8 | category: "group",
9 | aliases: ["gcname", "gtitle", "gctitle"],
10 | desc: "Change group description",
11 | groupOnly: true,
12 | adminGroup: true,
13 | botGroupAdmin: true,
14 | example: `
15 | Change group title
16 | @PREFIX@CMD
17 | --------
18 | `.trimEnd(),
19 | execute: async ({ aruga, message, arg, group }) => {
20 | if (!arg) throw "noCmd"
21 |
22 | await aruga.groupUpdateSubject(message.from, arg)
23 |
24 | const text =
25 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
26 | "┃\n" +
27 | `┃ ${i18n.translate("commands.group.change-name", { "@ADM": `@${message.sender.split("@")[0]}` }, group.language)}\n` +
28 | `${arg}\n` +
29 | "┃\n" +
30 | `┗━━「 ꗥ${config.name}ꗥ 」`
31 |
32 | return await message.reply(text, true)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/commands/group/change-picture.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "gpicture"
6 |
7 | export default {
8 | category: "group",
9 | aliases: ["gcpicture", "gprofile", "gcprofile"],
10 | desc: "Change group profile picture",
11 | groupOnly: true,
12 | adminGroup: true,
13 | botGroupAdmin: true,
14 | example: `
15 | Send a image message with caption
16 | @PREFIX@CMD
17 | --------
18 | or Reply a image message with text
19 | @PREFIX@CMD
20 | --------
21 | If you want to crop the image
22 | @PREFIX@CMD crop
23 | --------
24 | `.trimEnd(),
25 | execute: async ({ aruga, message, arg, group }) => {
26 | if (message.type.includes("image") || (message.quoted && message.quoted.type.includes("image"))) {
27 | const imgBuffer: Buffer = message.quoted ? await aruga.downloadMediaMessage(message.quoted) : await aruga.downloadMediaMessage(message)
28 | const crop = arg && arg.toLowerCase() === "crop"
29 |
30 | await aruga.updateProfilePicture(message.from, imgBuffer, crop)
31 |
32 | const text =
33 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
34 | "┃\n" +
35 | `┃ ${i18n.translate("commands.group.change-picture", { "@ADM": `@${message.sender.split("@")[0]}` }, group.language)}\n` +
36 | "┃\n" +
37 | `┗━━「 ꗥ${config.name}ꗥ 」`
38 |
39 | return await message.reply(text, true)
40 | }
41 |
42 | throw "noCmd"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/commands/group/group-add.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "gadd"
6 |
7 | export default {
8 | category: "group",
9 | aliases: ["gcadd"],
10 | desc: "Add member to group with phone number",
11 | groupOnly: true,
12 | adminGroup: true,
13 | botGroupAdmin: true,
14 | example: `
15 | Add member to group
16 | @PREFIX@CMD
17 |
18 | eg, @PREFIX@CMD 62851xxxxxx
19 | --------
20 | Add multiple members
21 | @PREFIX@CMD ...
22 |
23 | eg, @PREFIX@CMD 62851xxxxx 5581xxx
24 | --------
25 | `.trimEnd(),
26 | execute: async ({ aruga, message, args, group }) => {
27 | if (!args.length) throw "noCmd"
28 |
29 | for (const number of args) {
30 | const members = await aruga.onWhatsApp(number.replace(/\D+/g, "").trim())
31 | const member = members[0]
32 | if (!members.length || !member.exists) {
33 | const text =
34 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
35 | "┃\n" +
36 | `┃ ${i18n.translate("default.onWhatsApp", { "@NUM": number }, group.language)}\n` +
37 | "┃\n" +
38 | `┗━━「 ꗥ${config.name}ꗥ 」`
39 |
40 | return await message.reply(text, true)
41 | }
42 |
43 | const add = (await aruga.groupParticipantsUpdate(message.from, [member.jid], "add"))[0]
44 |
45 | if (add.status === "403") {
46 | await aruga.sendAcceptInviteV4(message.from, add.content, member.jid, i18n.translate("commands.group.group-add.caption", {}, group.language))
47 |
48 | const text =
49 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
50 | "┃\n" +
51 | `┃ ${i18n.translate("commands.group.group-add.invite", { "@NUM": number }, group.language)}\n` +
52 | "┃\n" +
53 | `┗━━「 ꗥ${config.name}ꗥ 」`
54 |
55 | return await message.reply(text, true)
56 | }
57 |
58 | const text =
59 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
60 | "┃\n" +
61 | `┃ ${i18n.translate("commands.group.group-add.add", { "@NUM": number }, group.language)}\n` +
62 | "┃\n" +
63 | `┗━━「 ꗥ${config.name}ꗥ 」`
64 |
65 | return await message.reply(text, true)
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/commands/group/group-demote.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "gdemote"
6 |
7 | export default {
8 | category: "group",
9 | aliases: ["gcdemote"],
10 | desc: "Demote group admins become members of group",
11 | groupOnly: true,
12 | adminGroup: true,
13 | botGroupAdmin: true,
14 | example: `
15 | Demote with number / mention
16 | @PREFIX@CMD
17 |
18 | eg, @PREFIX@CMD 62851xxxxxx
19 | --------
20 | Demote multiple members / mentions
21 | @PREFIX@CMD <@mention> ...
22 |
23 | eg, @PREFIX@CMD 62851xxxxx @mention
24 | --------
25 | `.trimEnd(),
26 | execute: async ({ aruga, message, args, group }) => {
27 | if (!args.length) throw "noCmd"
28 |
29 | for (const number of args) {
30 | const members = await aruga.onWhatsApp(number.replace(/\D+/g, "").trim())
31 | const member = members[0]
32 | if (!members.length || !member.exists) {
33 | const text =
34 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
35 | "┃\n" +
36 | `┃ ${i18n.translate("default.onWhatsApp", { "@NUM": number }, group.language)}\n` +
37 | "┃\n" +
38 | `┗━━「 ꗥ${config.name}ꗥ 」`
39 |
40 | return await message.reply(text, true)
41 | }
42 |
43 | await aruga.groupParticipantsUpdate(message.from, [member.jid], "demote")
44 |
45 | const text =
46 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
47 | "┃\n" +
48 | `┃ ${i18n.translate("commands.group.group-demote", { "@ADM": `@${member.jid.split("@")[0]}` }, group.language)}\n` +
49 | "┃\n" +
50 | `┗━━「 ꗥ${config.name}ꗥ 」`
51 |
52 | return await message.reply(text, true)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/commands/group/group-kick.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "gkick"
6 |
7 | export default {
8 | category: "group",
9 | aliases: ["gckick"],
10 | desc: "Kick member from group with phone number / mention",
11 | groupOnly: true,
12 | adminGroup: true,
13 | botGroupAdmin: true,
14 | example: `
15 | Kick member from group with number / mention
16 | @PREFIX@CMD
17 |
18 | eg, @PREFIX@CMD 62851xxxxxx
19 | --------
20 | Kick multiple members / mention
21 | @PREFIX@CMD <@mention> ...
22 |
23 | eg, @PREFIX@CMD 62851xxxxx @mention
24 | --------
25 | `.trimEnd(),
26 | execute: async ({ aruga, message, args, group }) => {
27 | if (!args.length) throw "noCmd"
28 |
29 | for (const number of args) {
30 | const members = await aruga.onWhatsApp(number.replace(/\D+/g, "").trim())
31 | const member = members[0]
32 | if (!members.length || !member.exists) {
33 | const text =
34 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
35 | "┃\n" +
36 | `┃ ${i18n.translate("default.onWhatsApp", { "@NUM": number }, group.language)}\n` +
37 | "┃\n" +
38 | `┗━━「 ꗥ${config.name}ꗥ 」`
39 |
40 | return await message.reply(text, true)
41 | }
42 |
43 | const kick = (await aruga.groupParticipantsUpdate(message.from, [member.jid], "remove"))[0]
44 |
45 | if (kick.status === "404") {
46 | const text =
47 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
48 | "┃\n" +
49 | `┃ ${i18n.translate("commands.group.group-kick.failed", { "@NUM": number }, group.language)}\n` +
50 | "┃\n" +
51 | `┗━━「 ꗥ${config.name}ꗥ 」`
52 |
53 | return await message.reply(text, true)
54 | }
55 |
56 | const text =
57 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
58 | "┃\n" +
59 | `┃ ${i18n.translate("commands.group.group-kick.success", { "@NUM": number }, group.language)}\n` +
60 | "┃\n" +
61 | `┗━━「 ꗥ${config.name}ꗥ 」`
62 |
63 | return await message.reply(text, true)
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/commands/group/group-language.ts:
--------------------------------------------------------------------------------
1 | import { readFile } from "fs/promises"
2 | import { join as pathJoin } from "path"
3 | import i18n from "../../libs/international"
4 | import config from "../../utils/config"
5 | import { database } from "../../libs/whatsapp"
6 | import type { Command } from "../../types/command"
7 |
8 | export const name = "glang"
9 |
10 | export default {
11 | category: "general",
12 | aliases: ["gclang", "glanguage", "gclanguage"],
13 | desc: "Show/Set group language",
14 | groupOnly: true,
15 | adminGroup: true,
16 | execute: async ({ message, prefix, args, group, command }) => {
17 | const listLanguages: {
18 | iso: string
19 | lang: string
20 | }[] = JSON.parse(await readFile(pathJoin(__dirname, "..", "..", "..", "database", "languages.json"), "utf-8"))
21 |
22 | if (args.length >= 1 && !!listLanguages.find((value) => value.iso === args[0])) {
23 | const lang = listLanguages.find((value) => value.iso === args[0])
24 | const group = await database.updateGroup(message.from, { language: lang.iso })
25 | return await message.reply(i18n.translate("commands.general.language.changed", { "@LANGUAGE": lang.lang }, group.language), true)
26 | }
27 |
28 | let listLang = ""
29 |
30 | for (const lang of listLanguages.filter((v) => i18n.listLanguage().includes(v.iso)).sort((first, last) => first.lang.localeCompare(last.lang))) {
31 | listLang += `┃ > ${lang.lang}\n┃ ${prefix}${command} ${lang.iso}\n┃\n`
32 | }
33 |
34 | const text =
35 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
36 | "┃\n" +
37 | `┃ ${i18n.translate("commands.group.group-language", {}, group.language)}\n` +
38 | "┃\n" +
39 | "┣━━━━━━━━━━━━━━━━━━\n" +
40 | "┃\n" +
41 | listLang +
42 | "┣━━━━━━━━━━━━━━━━━━\n" +
43 | "┃\n" +
44 | `┗━━「 ꗥ${config.name}ꗥ 」`
45 |
46 | return await message.reply(text, true)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/commands/group/group-mute.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import { database } from "../../libs/whatsapp"
3 | import config from "../../utils/config"
4 | import type { Command } from "../../types/command"
5 |
6 | export const name = "gmute"
7 |
8 | export default {
9 | category: "group",
10 | aliases: ["gcmute"],
11 | desc: "Mute/Unmute bot on group",
12 | groupOnly: true,
13 | adminGroup: true,
14 | botGroupAdmin: true,
15 | example: `
16 | Turn on / Activate @CMD
17 | @PREFIX@CMD on
18 | --------
19 | Turn off / Deactivate @CMD
20 | @PREFIX@CMD off
21 | --------
22 | `.trimEnd(),
23 | execute: async ({ message, args, group, command }) => {
24 | if (args[0] && (args[0].toLowerCase() === "on" || args[0].toLowerCase() === "enable")) {
25 | if (!group.mute) {
26 | await database.updateGroup(message.from, {
27 | mute: true
28 | })
29 | }
30 |
31 | const text =
32 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
33 | "┃\n" +
34 | `┃ ${i18n.translate("commands.group.group-mute.enable", { "@CMD": command }, group.language)}\n` +
35 | "┃\n" +
36 | `┗━━「 ꗥ${config.name}ꗥ 」`
37 |
38 | return await message.reply(text, true)
39 | }
40 |
41 | if (args[0] && (args[0].toLowerCase() === "off" || args[0].toLowerCase() === "disable")) {
42 | if (group.mute) {
43 | await database.updateGroup(message.from, {
44 | mute: false
45 | })
46 | }
47 |
48 | const text =
49 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
50 | "┃\n" +
51 | `┃ ${i18n.translate("commands.group.group-mute.disable", { "@CMD": command }, group.language)}\n` +
52 | "┃\n" +
53 | `┗━━「 ꗥ${config.name}ꗥ 」`
54 |
55 | return await message.reply(text, true)
56 | }
57 |
58 | throw "noCmd"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/commands/group/group-notify.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import { database } from "../../libs/whatsapp"
3 | import config from "../../utils/config"
4 | import type { Command } from "../../types/command"
5 |
6 | export const name = "gnotify"
7 |
8 | export default {
9 | category: "group",
10 | aliases: ["gcnotify"],
11 | desc: "Detects group updates",
12 | groupOnly: true,
13 | adminGroup: true,
14 | botGroupAdmin: true,
15 | example: `
16 | Turn on / Activate @CMD
17 | @PREFIX@CMD on
18 | --------
19 | Turn off / Deactivate @CMD
20 | @PREFIX@CMD off
21 | --------
22 | `.trimEnd(),
23 | execute: async ({ message, args, group, command }) => {
24 | if (args[0] && (args[0].toLowerCase() === "on" || args[0].toLowerCase() === "enable")) {
25 | if (!group.notify) {
26 | await database.updateGroup(message.from, {
27 | notify: true
28 | })
29 | }
30 |
31 | const text =
32 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
33 | "┃\n" +
34 | `┃ ${i18n.translate("commands.group.group-notify.enable", { "@CMD": command }, group.language)}\n` +
35 | "┃\n" +
36 | `┗━━「 ꗥ${config.name}ꗥ 」`
37 |
38 | return await message.reply(text, true)
39 | }
40 |
41 | if (args[0] && (args[0].toLowerCase() === "off" || args[0].toLowerCase() === "disable")) {
42 | if (group.notify) {
43 | await database.updateGroup(message.from, {
44 | notify: false
45 | })
46 | }
47 |
48 | const text =
49 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
50 | "┃\n" +
51 | `┃ ${i18n.translate("commands.group.group-notify.disable", { "@CMD": command }, group.language)}\n` +
52 | "┃\n" +
53 | `┗━━「 ꗥ${config.name}ꗥ 」`
54 |
55 | return await message.reply(text, true)
56 | }
57 |
58 | throw "noCmd"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/commands/group/group-promote.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "gpromote"
6 |
7 | export default {
8 | category: "group",
9 | aliases: ["gcpromote"],
10 | desc: "Demote group admins become members of group",
11 | groupOnly: true,
12 | adminGroup: true,
13 | botGroupAdmin: true,
14 | example: `
15 | Demote with number / mention
16 | @PREFIX@CMD
17 |
18 | eg, @PREFIX@CMD 62851xxxxxx
19 | --------
20 | Demote multiple members / mentions
21 | @PREFIX@CMD <@mention> ...
22 |
23 | eg, @PREFIX@CMD 62851xxxxx @mention
24 | --------
25 | `.trimEnd(),
26 | execute: async ({ aruga, message, args, group }) => {
27 | if (!args.length) throw "noCmd"
28 |
29 | for (const number of args) {
30 | const members = await aruga.onWhatsApp(number.replace(/\D+/g, "").trim())
31 | const member = members[0]
32 | if (!members.length || !member.exists) {
33 | const text =
34 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
35 | "┃\n" +
36 | `┃ ${i18n.translate("default.onWhatsApp", { "@NUM": number }, group.language)}\n` +
37 | "┃\n" +
38 | `┗━━「 ꗥ${config.name}ꗥ 」`
39 |
40 | return await message.reply(text, true)
41 | }
42 |
43 | await aruga.groupParticipantsUpdate(message.from, [member.jid], "promote")
44 |
45 | const text =
46 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
47 | "┃\n" +
48 | `┃ ${i18n.translate("commands.group.group-promote", { "@ADM": `@${member.jid.split("@")[0]}` }, group.language)}\n` +
49 | "┃\n" +
50 | `┗━━「 ꗥ${config.name}ꗥ 」`
51 |
52 | return await message.reply(text, true)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/commands/group/group-tagall.ts:
--------------------------------------------------------------------------------
1 | import config from "../../utils/config"
2 | import type { Command } from "../../types/command"
3 |
4 | export const name = "tagall"
5 |
6 | export default {
7 | category: "group",
8 | aliases: ["mentions", "mentionall"],
9 | desc: "Mention all participants",
10 | groupOnly: true,
11 | adminGroup: true,
12 | example: `
13 | Tags all participants w/o description
14 | @PREFIX@CMD description
15 |
16 | eg, @PREFIX@CMD hello everyone!
17 | --------
18 | `.trimEnd(),
19 | execute: async ({ aruga, message, arg }) => {
20 | const participantsId = message.groupMetadata.participants.map((v) => v.id)
21 |
22 | const text =
23 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
24 | (arg && "┃\n" + `┃ ${arg}\n` + "┃\n" + "┣━━━━━━━━━━━━━━━━━━\n") +
25 | "┃\n" +
26 | `${participantsId.map((id) => "┃ @" + id.split("@")[0]).join("\n")}\n` +
27 | "┃\n" +
28 | `┗━━「 ꗥ${config.name}ꗥ 」`
29 |
30 | return await aruga.sendMessage(message.from, { text, mentions: participantsId }, { quoted: message, ephemeralExpiration: message.expiration })
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/group/group-url.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "gurl"
6 |
7 | export default {
8 | category: "group",
9 | aliases: ["gcurl", "glink", "gclink"],
10 | desc: "Get group invite url",
11 | groupOnly: true,
12 | adminGroup: true,
13 | botGroupAdmin: true,
14 | execute: async ({ aruga, message, group }) => {
15 | const code = await aruga.groupInviteCode(message.from)
16 |
17 | const text =
18 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
19 | "┃\n" +
20 | `┃ ${i18n.translate("commands.group.group-url", { "@URL": `https://chat.whatsapp.com/${code}` }, group.language)}\n` +
21 | "┃\n" +
22 | `┗━━「 ꗥ${config.name}ꗥ 」`
23 |
24 | return await message.reply(text, true)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/commands/group/member-leave.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import { database } from "../../libs/whatsapp"
3 | import config from "../../utils/config"
4 | import type { Command } from "../../types/command"
5 |
6 | export const name = "leave"
7 |
8 | export default {
9 | category: "group",
10 | desc: "Set leave message for outgoing member",
11 | groupOnly: true,
12 | adminGroup: true,
13 | example: `
14 | Turn on / Activate @CMD
15 | @PREFIX@CMD on
16 | --------
17 | Turn off / Deactivate @CMD
18 | @PREFIX@CMD off
19 | --------
20 | `.trimEnd(),
21 | execute: async ({ message, args, user, group, command }) => {
22 | if (args[0] && (args[0].toLowerCase() === "on" || args[0].toLowerCase() === "enable")) {
23 | if (!group.leave) {
24 | await database.updateGroup(message.from, {
25 | leave: true
26 | })
27 | }
28 |
29 | const text =
30 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
31 | "┃\n" +
32 | `┃ ${i18n.translate("commands.group.member-leave.enable", { "@CMD": command }, user.language)}\n` +
33 | "┃\n" +
34 | `┗━━「 ꗥ${config.name}ꗥ 」`
35 |
36 | return await message.reply(text, true)
37 | }
38 |
39 | if (args[0] && (args[0].toLowerCase() === "off" || args[0].toLowerCase() === "disable")) {
40 | if (group.leave) {
41 | await database.updateGroup(message.from, {
42 | leave: false
43 | })
44 | }
45 |
46 | const text =
47 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
48 | "┃\n" +
49 | `┃ ${i18n.translate("commands.group.member-leave.disable", { "@CMD": command }, user.language)}\n` +
50 | "┃\n" +
51 | `┗━━「 ꗥ${config.name}ꗥ 」`
52 |
53 | return await message.reply(text, true)
54 | }
55 |
56 | throw "noCmd"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/commands/group/member-welcome.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import { database } from "../../libs/whatsapp"
3 | import config from "../../utils/config"
4 | import type { Command } from "../../types/command"
5 |
6 | export const name = "welcome"
7 |
8 | export default {
9 | category: "group",
10 | desc: "Set welcome message for incoming member",
11 | groupOnly: true,
12 | adminGroup: true,
13 | example: `
14 | Turn on / Activate @CMD
15 | @PREFIX@CMD on
16 | --------
17 | Turn off / Deactivate @CMD
18 | @PREFIX@CMD off
19 | --------
20 | `.trimEnd(),
21 | execute: async ({ message, args, user, group, command }) => {
22 | if (args[0] && (args[0].toLowerCase() === "on" || args[0].toLowerCase() === "enable")) {
23 | if (!group.welcome) {
24 | await database.updateGroup(message.from, {
25 | welcome: true
26 | })
27 | }
28 |
29 | const text =
30 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
31 | "┃\n" +
32 | `┃ ${i18n.translate("commands.group.member-welcome.enable", { "@CMD": command }, user.language)}\n` +
33 | "┃\n" +
34 | `┗━━「 ꗥ${config.name}ꗥ 」`
35 |
36 | return await message.reply(text, true)
37 | }
38 |
39 | if (args[0] && (args[0].toLowerCase() === "off" || args[0].toLowerCase() === "disable")) {
40 | if (group.welcome) {
41 | await database.updateGroup(message.from, {
42 | welcome: false
43 | })
44 | }
45 |
46 | const text =
47 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
48 | "┃\n" +
49 | `┃ ${i18n.translate("commands.group.member-welcome.disable", { "@CMD": command }, user.language)}\n` +
50 | "┃\n" +
51 | `┗━━「 ꗥ${config.name}ꗥ 」`
52 |
53 | return await message.reply(text, true)
54 | }
55 |
56 | throw "noCmd"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/commands/misc/ping.ts:
--------------------------------------------------------------------------------
1 | import { rtfFormat } from "../../utils/format"
2 | import type { Command } from "../../types/command"
3 |
4 | export default {
5 | category: "misc",
6 | desc: "Ping bot",
7 | execute: ({ message, arg }) => {
8 | const ping = Date.now() - message.timestamps // time milliseconds
9 | return message.reply(`pong! ${rtfFormat(ping / 1000, "seconds")} ${arg}`, true)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/commands/owner/bot-desc.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "bdesc"
6 |
7 | export default {
8 | category: "owner",
9 | aliases: ["botdesc", "bstatus", "botstatus"],
10 | desc: "Change bot profile description/status",
11 | ownerOnly: true,
12 | example: `
13 | @PREFIX@CMD desc~
14 | `.trimEnd(),
15 | execute: async ({ aruga, arg, user, message }) => {
16 | if (!arg) throw "noCmd"
17 |
18 | await aruga.updateProfileStatus(arg)
19 |
20 | const text =
21 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
22 | "┃\n" +
23 | `┃ ${i18n.translate("commands.owner.bot-desc", {}, user.language)}\n` +
24 | "┃\n" +
25 | `┗━━「 ꗥ${config.name}ꗥ 」`
26 |
27 | return await message.reply(text, true)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commands/owner/bot-name.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "bname"
6 |
7 | export default {
8 | category: "owner",
9 | aliases: ["botname"],
10 | desc: "Change bot profile name",
11 | ownerOnly: true,
12 | example: `
13 | @PREFIX@CMD name~
14 | `.trimEnd(),
15 | execute: async ({ aruga, arg, user, message }) => {
16 | if (!arg) throw "noCmd"
17 |
18 | await aruga.updateProfileName(arg)
19 |
20 | const text =
21 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
22 | "┃\n" +
23 | `┃ ${i18n.translate("commands.owner.bot-name", {}, user.language)}\n` +
24 | "┃\n" +
25 | `┗━━「 ꗥ${config.name}ꗥ 」`
26 |
27 | return await message.reply(text, true)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commands/owner/bot-picture.ts:
--------------------------------------------------------------------------------
1 | import i18n from "../../libs/international"
2 | import config from "../../utils/config"
3 | import type { Command } from "../../types/command"
4 |
5 | export const name = "bpicture"
6 |
7 | export default {
8 | category: "owner",
9 | aliases: ["botpicture"],
10 | desc: "Change bot profile picture",
11 | ownerOnly: true,
12 | example: `
13 | Send a image message with caption
14 | @PREFIX@CMD
15 | --------
16 | or Reply a image message with text
17 | @PREFIX@CMD
18 | --------
19 | If you want to crop the image
20 | @PREFIX@CMD crop
21 | --------
22 | `.trimEnd(),
23 | execute: async ({ aruga, message, arg, user }) => {
24 | if (message.type.includes("image") || (message.quoted && message.quoted.type.includes("image"))) {
25 | const imgBuffer: Buffer = message.quoted ? await aruga.downloadMediaMessage(message.quoted) : await aruga.downloadMediaMessage(message)
26 | const crop = arg && arg.toLowerCase() === "crop"
27 |
28 | await aruga.updateProfilePicture(aruga.decodeJid(aruga.user.id), imgBuffer, crop)
29 |
30 | const text =
31 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
32 | "┃\n" +
33 | `┃ ${i18n.translate("commands.owner.bot-picture", {}, user.language)}\n` +
34 | "┃\n" +
35 | `┗━━「 ꗥ${config.name}ꗥ 」`
36 |
37 | return await message.reply(text, true)
38 | }
39 |
40 | throw "noCmd"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/commands/owner/edit-user.ts:
--------------------------------------------------------------------------------
1 | import { Role } from "@prisma/client"
2 | import i18n from "../../libs/international"
3 | import { database } from "../../libs/whatsapp"
4 | import config from "../../utils/config"
5 | import type { Command } from "../../types/command"
6 |
7 | export const name = "user"
8 |
9 | export default {
10 | category: "owner",
11 | desc: "Show/Change user information",
12 | cd: 1,
13 | ownerOnly: true,
14 | example: `
15 | Change user role
16 | @PREFIX@CMD role
17 |
18 | eg, @PREFIX@CMD role 62895xx basic
19 |
20 | Role list: ${Object.keys(Role).join(", ").trimEnd()}
21 | --------
22 | `.trimEnd(),
23 | execute: async ({ aruga, message, args, user }) => {
24 | if (args.length === 3 && args[0].toLowerCase() === "role") {
25 | const number = args[1].replace(/\D+/g, "").trim()
26 | const role = args[2].toLowerCase().trim() as Role
27 |
28 | const members = await aruga.onWhatsApp(number)
29 | if (!members.length || !members[0].exists) {
30 | const text =
31 | "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" +
32 | "┃\n" +
33 | `┃ ${i18n.translate("default.onWhatsApp", { "@NUM": number }, user.language)}\n` +
34 | "┃\n" +
35 | `┗━━「 ꗥ${config.name}ꗥ 」`
36 |
37 | return await message.reply(text, true)
38 | }
39 |
40 | if (!Object.keys(Role).includes(role)) throw "noCmd"
41 |
42 | const expire = new Date(Date.now())
43 | expire.setDate(expire.getDate() + config.user[role].expires ? config.user[role].expires : 0)
44 | const updatedUser = await database.updateUser(members[0].jid, {
45 | role: role,
46 | expire: config.user[role].expires ? expire.getTime() : 0,
47 | limit: config.user[role].limit || 30
48 | })
49 |
50 | return await message.reply(JSON.stringify(updatedUser, null, 2))
51 | }
52 |
53 | throw "noCmd"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/commands/owner/kidnap.ts:
--------------------------------------------------------------------------------
1 | import type { Command } from "../../types/command"
2 |
3 | /**
4 | * Kidnap other group members to your group
5 | *
6 | * make bot as admin on your group
7 | * and on your group type /kidnap url
8 | *
9 | * Be aware :)
10 | * your bot will probably be blocked/banned
11 | */
12 |
13 | export default {
14 | category: "owner",
15 | desc: "Kidnap other group members to your group",
16 | groupOnly: true,
17 | ownerOnly: true,
18 | example: `
19 | Make bot as admin on your group
20 | and on your group type @PREFIX@CMD url
21 |
22 | eg, @PREFIX@CMD https://chat(.)whatsapp.com/Id4A2eoegx6Hg7Il54sEnn
23 | --------
24 | `.trimEnd(),
25 | execute: async ({ aruga, message, arg }) => {
26 | const url = arg.length && arg.match(/chat.whatsapp.com\/(?:invite\/)?([\w\d]*)/)
27 | if (url && url.length === 2) {
28 | const code = url[1]
29 | const result = await aruga.groupGetInviteInfo(code)
30 | if (!result) return
31 | await aruga.groupAcceptInvite(code)
32 | const fetchGroups = await aruga.groupFetchAllParticipating()
33 | const participants = Object.values(fetchGroups).find((v) => v.id === result.id).participants
34 |
35 | // for (const participant of participants) await aruga.groupParticipantsUpdate(message.from, [participant.id], "add")
36 | return await aruga.groupParticipantsUpdate(
37 | message.from,
38 | participants.map((v) => v.id),
39 | "add"
40 | )
41 | }
42 |
43 | throw "noCmd"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/commands/owner/upsw.ts:
--------------------------------------------------------------------------------
1 | import { database } from "../../libs/whatsapp"
2 | import type { Command } from "../../types/command"
3 |
4 | export default {
5 | category: "owner",
6 | aliases: ["upstatus", "upstory"],
7 | desc: "Upload bot status",
8 | ownerOnly: true,
9 | example: `
10 | Send a image/video message with caption
11 | @PREFIX@CMD status caption
12 | --------
13 | or Reply a image/video message with text
14 | @PREFIX@CMD status caption
15 | --------
16 | Send a text message
17 | @PREFIX@CMD status caption
18 | `.trimEnd(),
19 | execute: async ({ aruga, message, arg }) => {
20 | const contactList = await database.getUsers()
21 |
22 | /**
23 | * just arrange it as you like
24 | */
25 |
26 | await aruga.sendMessage(
27 | "status@broadcast",
28 | {
29 | ...(message.type === "image" || (message.quoted && message.quoted.type === "image")
30 | ? {
31 | image: await aruga.downloadMediaMessage(message.quoted || message),
32 | caption: message.quoted?.body || arg
33 | }
34 | : message.type === "video" || (message.quoted && message.quoted.type === "video")
35 | ? {
36 | video: await aruga.downloadMediaMessage(message.quoted || message),
37 | caption: message.quoted?.body || arg
38 | }
39 | : {
40 | text: arg
41 | })
42 | },
43 | {
44 | backgroundColor: "#123456",
45 | font: 3,
46 | //it is always necessary to inform the list of contacts that will have access to the posted status
47 | statusJidList: contactList.map((user) => user.userId).concat(aruga.decodeJid(aruga.user.id))
48 | }
49 | )
50 |
51 | return await message.reply("Status uploaded successfully", true)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/handlers/call.ts:
--------------------------------------------------------------------------------
1 | import type WAClient from "../libs/whatsapp"
2 | import type { CallSerialize } from "../types/serialize"
3 | import i18n from "../libs/international"
4 | import { database } from "../libs/whatsapp"
5 | import color from "../utils/color"
6 | import config from "../utils/config"
7 |
8 | export const execute = async (aruga: WAClient, call: CallSerialize): Promise => {
9 | if (!call.callFrom.endsWith("g.us") && call.status === "offer" && !config.ownerNumber.includes(call.callFrom.replace(/\D+/g, ""))) {
10 | const user = await database.getUser(call.callFrom)
11 | try {
12 | if (config.antiCall.reject) {
13 | await aruga.rejectCall(call.callId, call.callFrom)
14 |
15 | await call.reply(i18n.translate("handlers.call.reject", {}, user.language))
16 | }
17 |
18 | if (config.antiCall.block) {
19 | await aruga.updateBlockStatus(call.callFrom, "block")
20 |
21 | await call.reply(i18n.translate("handlers.call.block", {}, user.language))
22 | }
23 |
24 | if (config.antiCall.ban) {
25 | user ? await database.updateUser(call.callFrom, { ban: true }) : await database.createUser(call.callFrom, { name: call.callFrom, ban: true })
26 |
27 | await call.reply(i18n.translate("handlers.call.ban", {}, user.language))
28 | }
29 |
30 | return aruga.log(`${color.hex("#940c9c" as HexColor)("[CALL]")} ${color.cyan(`>> [${call.callId.length}]`)} from ${color.blue(user?.name || call.callFrom)}`.trim(), "info", Date.now())
31 | } catch {
32 | return aruga.log(`${color.hex("#940c9c" as HexColor)("[CALL]")} ${color.cyan(`>> [${call.callId.length}]`)} from ${color.blue(user?.name || call.callFrom)}`.trim(), "error", Date.now())
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/handlers/group-participant.ts:
--------------------------------------------------------------------------------
1 | import { WAMessageStubType } from "baileys"
2 | import type WAClient from "../libs/whatsapp"
3 | import { database } from "../libs/whatsapp"
4 | import i18n from "../libs/international"
5 | import color from "../utils/color"
6 | import { phoneFormat } from "../utils/format"
7 | import type { GroupParticipantSerialize } from "../types/serialize"
8 |
9 | export const execute = async (aruga: WAClient, message: GroupParticipantSerialize): Promise => {
10 | const groupMetadata = (await database.getGroupMetadata(message.from)) ?? (await database.createGroupMetadata(message.from, (await aruga.groupMetadata(message.from)) as unknown))
11 | const group = (await database.getGroup(message.from)) ?? (await database.createGroup(message.from, { name: groupMetadata.subject }))
12 | const botNumber = aruga.decodeJid(aruga.user.id)
13 | const isBot = message.body.includes(botNumber) || message.sender === botNumber
14 |
15 | try {
16 | if (message.type === WAMessageStubType.GROUP_PARTICIPANT_ADD || message.type === WAMessageStubType.GROUP_PARTICIPANT_INVITE) {
17 | // welcome message
18 | if (group.welcome) {
19 | await message.reply(
20 | i18n.translate(
21 | "handlers.group-participant.welcome",
22 | {
23 | "@PPL": message.body
24 | .map((participant) => `@${participant.replace(/\D+/g, "")}`)
25 | .join(" ")
26 | .trimEnd(),
27 | "@GRP": groupMetadata.subject
28 | },
29 | group.language
30 | )
31 | )
32 | }
33 |
34 | // anti country
35 | if (group.anticountry.active && !!groupMetadata.participants.find((member) => member.id === botNumber && member.admin)) {
36 | process.nextTick(() => message.body.forEach((participant) => group.anticountry.number.includes(phoneFormat(participant).countryCode) && aruga.groupParticipantsUpdate(message.from, [participant], "remove").catch(() => void 0)))
37 | await message.reply(
38 | i18n.translate(
39 | "handlers.group-participant.anticountry",
40 | {
41 | "@PPL": message.body
42 | .map((participant) => `@${participant.replace(/\D+/g, "")}`)
43 | .join(" ")
44 | .trimEnd(),
45 | "@NUM": group.anticountry.number.join(", ").trim()
46 | },
47 | group.language
48 | )
49 | )
50 | } else {
51 | message.body.forEach((participant) => !groupMetadata.participants.map((v) => v.id).includes(participant) && groupMetadata.participants.push({ id: participant, admin: null }))
52 | await database.updateGroupMetadata(message.from, { participants: groupMetadata.participants })
53 | }
54 | }
55 |
56 | if (message.type === WAMessageStubType.GROUP_PARTICIPANT_REMOVE || message.type === WAMessageStubType.GROUP_PARTICIPANT_LEAVE) {
57 | // leave message
58 | if (group.leave) {
59 | await message.reply(
60 | i18n.translate(
61 | "handlers.group-participant.leave",
62 | {
63 | "@PPL": message.body
64 | .map((participant) => `@${participant.replace(/\D+/g, "")}`)
65 | .join(" ")
66 | .trimEnd(),
67 | "@GRP": groupMetadata.subject
68 | },
69 | group.language
70 | )
71 | )
72 | }
73 |
74 | if (message.body.includes(botNumber)) {
75 | process.nextTick(async () => await Promise.all([database.deleteGroup(message.from), database.deleteGroupMetadata(message.from)]))
76 | } else {
77 | process.nextTick(async () => {
78 | message.body.forEach(
79 | (participant) =>
80 | groupMetadata.participants.map((v) => v.id).includes(participant) &&
81 | groupMetadata.participants.splice(
82 | groupMetadata.participants.findIndex((x) => x.id === participant),
83 | 1
84 | )
85 | )
86 | await database.updateGroupMetadata(message.from, { participants: groupMetadata.participants })
87 | })
88 | }
89 | }
90 |
91 | if (message.type === WAMessageStubType.GROUP_PARTICIPANT_PROMOTE) {
92 | if (!isBot && group.notify)
93 | await message.reply(
94 | i18n.translate(
95 | "handlers.group-participant.promote",
96 | {
97 | "@ADM": `@${message.sender.replace(/\D+/g, "")}`,
98 | "@PPL": message.body
99 | .map((participant) => `@${participant.replace(/\D+/g, "")}`)
100 | .join(" ")
101 | .trimEnd()
102 | },
103 | group.language
104 | )
105 | )
106 |
107 | for (const participant of message.body) {
108 | groupMetadata.participants[groupMetadata.participants.findIndex((x) => x.id === participant)].admin = "admin"
109 | }
110 | await database.updateGroupMetadata(message.from, { participants: groupMetadata.participants })
111 | }
112 |
113 | if (message.type === WAMessageStubType.GROUP_PARTICIPANT_DEMOTE) {
114 | if (!isBot && group.notify)
115 | await message.reply(
116 | i18n.translate(
117 | "handlers.group-participant.demote",
118 | {
119 | "@ADM": `@${message.sender.replace(/\D+/g, "")}`,
120 | "@PPL": message.body
121 | .map((participant) => `@${participant.replace(/\D+/g, "")}`)
122 | .join(" ")
123 | .trimEnd()
124 | },
125 | group.language
126 | )
127 | )
128 |
129 | for (const participant of message.body) {
130 | groupMetadata.participants[groupMetadata.participants.findIndex((x) => x.id === participant)].admin = null
131 | }
132 | await database.updateGroupMetadata(message.from, { participants: groupMetadata.participants })
133 | }
134 |
135 | return aruga.log(
136 | `${color.hex("#E0B589" as HexColor)("[EVNT]")} ${color.cyan(
137 | `${
138 | message.type === WAMessageStubType.GROUP_PARTICIPANT_ADD
139 | ? "memberAdd"
140 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_REMOVE
141 | ? "memberRemove"
142 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_PROMOTE
143 | ? "memberPromote"
144 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_DEMOTE
145 | ? "memberDemote"
146 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_INVITE
147 | ? "memberInvite"
148 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_LEAVE
149 | ? "memberLeave"
150 | : ""
151 | } [${message.type}]`
152 | )} from ${color.blue(message.sender.replace(/\D+/g, "") ?? "unknown")} in ${color.blue(group.name || "unknown")}`.trim(),
153 | "info",
154 | message.timestamps
155 | )
156 | } catch {
157 | return aruga.log(
158 | `${color.red("[EVNT]")} ${color.cyan(
159 | `${
160 | message.type === WAMessageStubType.GROUP_PARTICIPANT_ADD
161 | ? "memberAdd"
162 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_REMOVE
163 | ? "memberRemove"
164 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_PROMOTE
165 | ? "memberPromote"
166 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_DEMOTE
167 | ? "memberDemote"
168 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_INVITE
169 | ? "memberInvite"
170 | : message.type === WAMessageStubType.GROUP_PARTICIPANT_LEAVE
171 | ? "memberLeave"
172 | : ""
173 | } [${message.type}]`
174 | )} from ${color.blue(message.sender.replace(/\D+/g, "") ?? "unknown")} in ${color.blue(group.name || "unknown")}`.trim(),
175 | "error",
176 | message.timestamps
177 | )
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/handlers/group.ts:
--------------------------------------------------------------------------------
1 | import { WAMessageStubType } from "baileys"
2 | import type WAClient from "../libs/whatsapp"
3 | import { database } from "../libs/whatsapp"
4 | import i18n from "../libs/international"
5 | import color from "../utils/color"
6 | import type { GroupSerialize } from "../types/serialize"
7 |
8 | export const execute = async (aruga: WAClient, message: GroupSerialize): Promise => {
9 | const group = (await database.getGroup(message.from)) ?? (await database.createGroup(message.from, { name: ((await database.getGroupMetadata(message.from)) ?? (await database.createGroupMetadata(message.from, (await aruga.groupMetadata(message.from)) as unknown))).subject }))
10 | const botNumber = aruga.decodeJid(aruga.user.id)
11 | const isBot = message.body === botNumber || message.sender === botNumber
12 | const senderNumber = message.sender.replace(/\D+/g, "")
13 |
14 | try {
15 | if (message.type === WAMessageStubType.GROUP_CHANGE_SUBJECT) {
16 | if (!isBot && group.notify) await message.reply(i18n.translate("handlers.group.subject", { "@PPL": `@${senderNumber}`, "@TTL": `\n\n${message.body}` }, group.language))
17 | await Promise.all([database.updateGroup(message.from, { name: message.body }), database.updateGroupMetadata(message.from, { subject: message.body })])
18 | }
19 |
20 | if (message.type === WAMessageStubType.GROUP_CHANGE_ICON) {
21 | if (!isBot && group.notify) await message.reply(i18n.translate(`handlers.group.icon.${message.body ? "change" : "remove"}`, { "@PPL": `@${senderNumber}` }, group.language))
22 | }
23 |
24 | if (message.type === WAMessageStubType.GROUP_CHANGE_INVITE_LINK) {
25 | if (!isBot && group.notify) await message.reply(i18n.translate("handlers.group.invite_link", { "@PPL": `@${senderNumber}` }, group.language))
26 | }
27 |
28 | if (message.type === WAMessageStubType.GROUP_CHANGE_DESCRIPTION) {
29 | if (!isBot && group.notify) await message.reply(i18n.translate("handlers.group.description", { "@PPL": `@${senderNumber}`, "@TTL": `\n\n${message.body}` }, group.language))
30 | await database.updateGroupMetadata(message.from, { desc: message.body })
31 | }
32 |
33 | if (message.type === WAMessageStubType.GROUP_CHANGE_RESTRICT) {
34 | if (!isBot && group.notify) await message.reply(i18n.translate(`handlers.group.restrict.${message.body}`, { "@PPL": `@${senderNumber}` }, group.language))
35 | await database.updateGroupMetadata(message.from, { restrict: message.body === "on" })
36 | }
37 |
38 | if (message.type === WAMessageStubType.GROUP_CHANGE_ANNOUNCE) {
39 | if (!isBot && group.notify) await message.reply(i18n.translate(`handlers.group.announce.${message.body}`, { "@PPL": `@${senderNumber}` }, group.language))
40 | await database.updateGroupMetadata(message.from, { announce: message.body === "on" })
41 | }
42 |
43 | return aruga.log(
44 | `${color.hex("#E0B589" as HexColor)("[EVNT]")} ${color.cyan(
45 | `${
46 | message.type === WAMessageStubType.GROUP_CHANGE_ANNOUNCE
47 | ? "groupAnnounce"
48 | : message.type === WAMessageStubType.GROUP_CHANGE_RESTRICT
49 | ? "groupRestrict"
50 | : message.type === WAMessageStubType.GROUP_CHANGE_DESCRIPTION
51 | ? "groupDescription"
52 | : message.type === WAMessageStubType.GROUP_CHANGE_INVITE_LINK
53 | ? "groupInviteLink"
54 | : message.type === WAMessageStubType.GROUP_CHANGE_ICON
55 | ? "groupProfile"
56 | : message.type === WAMessageStubType.GROUP_CHANGE_SUBJECT
57 | ? "groupName"
58 | : ""
59 | } [${message.type}]`
60 | )} from ${color.blue(message.sender.replace(/\D+/g, "") ?? "unknown")} in ${color.blue(group.name || "unknown")}`.trim(),
61 | "info",
62 | message.timestamps
63 | )
64 | } catch {
65 | return aruga.log(
66 | `${color.red("[EVNT]")} ${color.cyan(
67 | `${
68 | message.type === WAMessageStubType.GROUP_CHANGE_ANNOUNCE
69 | ? "groupAnnounce"
70 | : message.type === WAMessageStubType.GROUP_CHANGE_RESTRICT
71 | ? "groupRestrict"
72 | : message.type === WAMessageStubType.GROUP_CHANGE_DESCRIPTION
73 | ? "groupDescription"
74 | : message.type === WAMessageStubType.GROUP_CHANGE_INVITE_LINK
75 | ? "groupInviteLink"
76 | : message.type === WAMessageStubType.GROUP_CHANGE_ICON
77 | ? "groupProfile"
78 | : message.type === WAMessageStubType.GROUP_CHANGE_SUBJECT
79 | ? "groupName"
80 | : ""
81 | } [${message.type}]`
82 | )} from ${color.blue(message.sender.replace(/\D+/g, "") ?? "unknown")} in ${color.blue(group.name || "unknown")}`.trim(),
83 | "error",
84 | message.timestamps
85 | )
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/libs/convert/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./profile"
2 | export * from "./webp"
3 |
--------------------------------------------------------------------------------
/src/libs/convert/profile.ts:
--------------------------------------------------------------------------------
1 | import type { Jimp } from "@jimp/core"
2 | import type { JpegClass } from "@jimp/jpeg"
3 | import type { Scale } from "@jimp/plugin-scale"
4 | import { MIME_JPEG, read, RESIZE_BILINEAR } from "jimp"
5 |
6 | export const WAProfile = async (image: Buffer, crop = false) => {
7 | const jimp = await read(image)
8 | let cropped: Jimp & JpegClass & Scale
9 | if (crop) {
10 | const minSize = Math.min(jimp.getWidth(), jimp.getHeight())
11 | cropped = jimp.crop(0, 0, minSize, minSize).resize(640, 640, RESIZE_BILINEAR)
12 | } else {
13 | cropped = jimp.resize(jimp.getWidth() * 0.7, jimp.getHeight() * 0.7, RESIZE_BILINEAR)
14 | }
15 |
16 | return cropped
17 | .scale(Math.abs(cropped.getWidth() <= cropped.getHeight() ? 640 / cropped.getHeight() : 640 / cropped.getWidth()))
18 | .quality(50)
19 | .getBufferAsync(MIME_JPEG)
20 | }
21 |
--------------------------------------------------------------------------------
/src/libs/convert/webp.ts:
--------------------------------------------------------------------------------
1 | import webpmux from "node-webpmux"
2 | import { ffmpeg } from "../../utils/cli"
3 | import type { StickerOptions, StickerCategories } from "../../types/sticker"
4 |
5 | export const WebpToImage = (webp: Buffer) => {
6 | return new Promise((resolve, reject) => {
7 | ffmpeg(webp, ["-f", "image2"]).then(resolve).catch(reject)
8 | })
9 | }
10 |
11 | const defaultStickerOptions: StickerOptions = {
12 | author: "arugaz",
13 | pack: "whatsapp-bot",
14 | id: "arugaz",
15 | width: 256,
16 | fps: 25,
17 | loop: true,
18 | compress: 0
19 | }
20 |
21 | export class WASticker {
22 | #opts: StickerOptions
23 | #exif: Buffer | null
24 |
25 | constructor(opts?: StickerOptions) {
26 | this.#opts = Object.assign(defaultStickerOptions, opts || {})
27 | this.#exif = null
28 | }
29 |
30 | #$_createExif() {
31 | const data = JSON.stringify({
32 | "sticker-pack-id": this.#opts.id,
33 | "sticker-pack-name": this.#opts.pack,
34 | "sticker-pack-publisher": this.#opts.author,
35 | "sticker-pack-publisher-email": "arugaastri@gmail.com",
36 | "sticker-pack-publisher-website": "https://github.com/ArugaZ/whatsapp-bot",
37 | ...(this.#opts.categories && this.#opts.categories?.length >= 1 ? { emojis: this.#opts.categories } : {}),
38 | "android-app-store-link": "https://play.google.com/store/apps/details?id=com.supercell.clashofclans",
39 | "is-first-party-sticker": 1,
40 | "ios-app-store-link": "https://apps.apple.com/id/app/clash-of-clans/id529479190"
41 | })
42 | const exif = Buffer.concat([Buffer.from([73, 73, 42, 0, 8, 0, 0, 0, 1, 0, 65, 87, 7, 0, 0, 0, 0, 0, 22, 0, 0, 0]), Buffer.from(data)])
43 | exif.writeUIntLE(new TextEncoder().encode(data).length, 14, 4)
44 | return exif
45 | }
46 |
47 | #$_convert(bufferData: Buffer, type: string) {
48 | const bufferSize = bufferData.byteLength
49 | return new Promise((resolve, reject) =>
50 | ffmpeg(bufferData, [
51 | "-vf",
52 | `scale='min(${this.#opts.width},iw)':min'(${this.#opts.width},ih)':force_original_aspect_ratio=decrease,fps=${this.#opts.fps}, pad=${this.#opts.width}:${this.#opts.width}:-1:-1:color=white@0.0, split [a][b]; [a] palettegen=reserve_transparent=on:transparency_color=ffffff [p]; [b][p] paletteuse`,
53 | "-loop",
54 | this.#opts.loop ? "0" : "1",
55 | "-lossless",
56 | type === "image" ? "1" : "0",
57 | "-compression_level",
58 | `${this.#opts.compress}`,
59 | "-quality",
60 | type === "image" ? "75" : `${bufferSize < 300000 ? 30 : bufferSize < 400000 ? 20 : 15}`,
61 | "-preset",
62 | "picture",
63 | "-an",
64 | "-vsync",
65 | "0",
66 | "-f",
67 | "webp"
68 | ])
69 | .then((bufferResult) => resolve(this.ConvertExif(bufferResult)))
70 | .catch(reject)
71 | )
72 | }
73 |
74 | /**
75 | * Extends options pack
76 | * @param pack Packname
77 | */
78 | public setPack(pack: string): this {
79 | this.#opts.pack = pack
80 | this.#exif = this.#$_createExif()
81 | return this
82 | }
83 |
84 | /**
85 | * Extends options author
86 | * @param author Author name
87 | */
88 | public setAuthor(author: string): this {
89 | this.#opts.author = author
90 | this.#exif = this.#$_createExif()
91 | return this
92 | }
93 |
94 | /**
95 | * Extends options ID
96 | * @param id Sticker ID
97 | */
98 | public setID(id: string): this {
99 | this.#opts.id = id
100 | this.#exif = this.#$_createExif()
101 | return this
102 | }
103 |
104 | /**
105 | * Extends options Categories
106 | * @param categories Sticker Categories
107 | */
108 | public setCategories(categories: StickerCategories[]): this {
109 | this.#opts.categories = categories
110 | this.#exif = this.#$_createExif()
111 | return this
112 | }
113 |
114 | /**
115 | * Convert media to buffer
116 | * @param bufferData Media Buffer
117 | * @returns Webp Buffer
118 | */
119 | public async ConvertMedia(bufferData: Buffer, type: "image" | "video" = "image"): Promise {
120 | this.#exif = this.#exif ? this.#exif : this.#$_createExif()
121 | const result = await this.#$_convert(bufferData, type)
122 | return result
123 | }
124 |
125 | /**
126 | * Set exif to buffer
127 | * @param bufferData WEBP Buffer
128 | * @returns
129 | */
130 | public async ConvertExif(bufferData: Buffer) {
131 | this.#exif = this.#exif ? this.#exif : this.#$_createExif()
132 | const image = new webpmux.Image()
133 | await image.load(bufferData)
134 | image.exif = this.#exif
135 | return image.save(null)
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/libs/database.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client"
2 |
3 | const Database = new PrismaClient({
4 | errorFormat: "minimal"
5 | })
6 |
7 | export default Database
8 |
--------------------------------------------------------------------------------
/src/libs/international.ts:
--------------------------------------------------------------------------------
1 | import Translate from "@arugaz/translator"
2 | import { join as pathJoin } from "path"
3 | import { lstatSync, readdirSync, readFileSync } from "fs"
4 | import config from "../utils/config"
5 | const i18n = Translate()
6 |
7 | // set default language!
8 | i18n.locale(config.language)
9 |
10 | export const i18nInit = (): void => {
11 | const listISO = (
12 | JSON.parse(readFileSync(pathJoin(__dirname, "..", "..", "database", "languages.json"), { encoding: "utf-8" })) as {
13 | iso: string
14 | lang: string
15 | }[]
16 | ).map((v) => v.iso)
17 |
18 | const files = readdirSync(pathJoin(__dirname, "..", "..", "languages"))
19 | for (const file of files) {
20 | const filePath = pathJoin(__dirname, "..", "..", "languages", file)
21 | const isDirectory = lstatSync(filePath).isDirectory()
22 | if (isDirectory || !file.endsWith(".json")) continue
23 | const iso = file.split(".")[0]
24 | if (listISO.includes(iso)) i18n.set(iso, JSON.parse(readFileSync(filePath, "utf-8")))
25 | }
26 | }
27 |
28 | export default i18n
29 |
--------------------------------------------------------------------------------
/src/libs/server/index.ts:
--------------------------------------------------------------------------------
1 | import fastifyServer from "./server"
2 |
3 | export * from "./routes"
4 |
5 | export default fastifyServer
6 |
--------------------------------------------------------------------------------
/src/libs/server/routes.ts:
--------------------------------------------------------------------------------
1 | import { FastifyInstance } from "fastify"
2 | import WAClient from "../../libs/whatsapp"
3 |
4 | /**
5 | * This APP Just an example how to use fastify with aruga
6 | */
7 |
8 | export const whatsappRoutes = (fastify: FastifyInstance, aruga: WAClient) => {
9 | fastify.register(
10 | async (instance) => {
11 | instance.addHook("onRequest", async (request, reply) => {
12 | const { secret } = request.query as { secret: string }
13 | if (!secret || secret !== process.env.SECRET_API) {
14 | reply.code(403)
15 | throw new Error("Unauthorized access")
16 | }
17 | })
18 |
19 | // http://127.0.0.1:PORT/api/status?secret=yoursecret
20 | instance.get("/status", () => {
21 | return {
22 | message: aruga.status,
23 | error: "Success",
24 | statusCode: 200
25 | }
26 | })
27 |
28 | // http://127.0.0.1:PORT/api/send-message?secret=yoursecret&number=628xxx&message=Hello
29 | instance.route({
30 | url: "/send-message",
31 | method: "GET",
32 | handler: async (request, reply) => {
33 | const { number, message } = request.query as { number: string; message: string }
34 | if (aruga.status !== "open") {
35 | reply.code(500)
36 | throw new Error("Client not ready")
37 | }
38 |
39 | await aruga.sendMessage(number.replace(/[^0-9]/g, "") + "@s.whatsapp.net", { text: message })
40 |
41 | return reply.send({
42 | message: "Succesfully send message",
43 | error: "Success",
44 | statusCode: 200
45 | })
46 | }
47 | })
48 | },
49 | { prefix: "/api" }
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/src/libs/server/server.ts:
--------------------------------------------------------------------------------
1 | import Fastify, { FastifyServerOptions } from "fastify"
2 | import { config as loadEnvFile } from "dotenv"
3 |
4 | loadEnvFile({
5 | override: !1
6 | })
7 |
8 | const fastifyServer = (fastifyOpts?: FastifyServerOptions) => {
9 | const fastify = Fastify({ ...fastifyOpts })
10 |
11 | fastify
12 | /**
13 | * Hello world!
14 | */
15 | .get("/", () => {
16 | return {
17 | message: "Hello world!",
18 | error: "Success",
19 | statusCode: 200
20 | }
21 | })
22 | /**
23 | * Health check
24 | */
25 | .get("/healthcheck", () => {
26 | return {
27 | message: "I'm healthy!",
28 | error: "Success",
29 | statusCode: 200
30 | }
31 | })
32 |
33 | return fastify
34 | }
35 |
36 | export default fastifyServer
37 |
38 | declare global {
39 | // eslint-disable-next-line @typescript-eslint/no-namespace
40 | namespace NodeJS {
41 | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
42 | interface ProcessEnv {
43 | NODE_ENV?: "production"
44 | PORT: number
45 | SECRET_API: string
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/auth.ts:
--------------------------------------------------------------------------------
1 | import type { PrismaClient } from "@prisma/client"
2 | import { BufferJSON, initAuthCreds, proto } from "baileys"
3 | import type { AuthenticationCreds, SignalDataTypeMap } from "baileys"
4 | import type { ArugaAuth } from "../../types/client"
5 |
6 | export const useMultiAuthState = async (Database: PrismaClient): Promise => {
7 | const fixFileName = (fileName: string): string => fileName.replace(/\//g, "__")?.replace(/:/g, "-")
8 |
9 | const writeData = async (data: unknown, fileName: string) => {
10 | try {
11 | const sessionId = fixFileName(fileName)
12 | const session = JSON.stringify(data, BufferJSON.replacer)
13 | await Database.session.upsert({
14 | where: {
15 | sessionId
16 | },
17 | update: {
18 | sessionId,
19 | session
20 | },
21 | create: {
22 | sessionId,
23 | session
24 | }
25 | })
26 | } catch {}
27 | }
28 |
29 | const readData = async (fileName: string) => {
30 | try {
31 | const sessionId = fixFileName(fileName)
32 | const data = await Database.session.findFirst({
33 | where: {
34 | sessionId
35 | }
36 | })
37 | return JSON.parse(data?.session, BufferJSON.reviver)
38 | } catch {
39 | return null
40 | }
41 | }
42 |
43 | const removeData = async (fileName: string): Promise => {
44 | try {
45 | const sessionId = fixFileName(fileName)
46 | await Database.session.delete({
47 | where: {
48 | sessionId
49 | }
50 | })
51 | } catch {}
52 | }
53 |
54 | const creds: AuthenticationCreds = (await readData("creds")) || initAuthCreds()
55 |
56 | return {
57 | state: {
58 | creds,
59 | keys: {
60 | get: async (type, ids) => {
61 | const data: { [_: string]: SignalDataTypeMap[typeof type] } = {}
62 | await Promise.all(
63 | ids.map(async (id) => {
64 | let value = await readData(`${type}-${id}`)
65 | if (type === "app-state-sync-key" && value) value = proto.Message.AppStateSyncKeyData.fromObject(value)
66 | data[id] = value
67 | })
68 | )
69 | return data
70 | },
71 | set: async (data) => {
72 | const tasks: Promise[] = []
73 | for (const category in data) {
74 | for (const id in data[category]) {
75 | const value: unknown = data[category][id]
76 | const file = `${category}-${id}`
77 | tasks.push(value ? writeData(value, file) : removeData(file))
78 | }
79 | }
80 | try {
81 | await Promise.all(tasks)
82 | } catch {}
83 | }
84 | }
85 | },
86 | saveState: async (): Promise => {
87 | try {
88 | await writeData(creds, "creds")
89 | } catch {}
90 | },
91 | clearState: async (): Promise => {
92 | try {
93 | await Database.session.deleteMany({})
94 | } catch {}
95 | }
96 | }
97 | }
98 |
99 | export const useSingleAuthState = async (Database: PrismaClient): Promise => {
100 | const KEY_MAP: { [T in keyof SignalDataTypeMap]: string } = {
101 | "pre-key": "preKeys",
102 | session: "sessions",
103 | "sender-key": "senderKeys",
104 | "app-state-sync-key": "appStateSyncKeys",
105 | "app-state-sync-version": "appStateVersions",
106 | "sender-key-memory": "senderKeyMemory"
107 | }
108 |
109 | let creds: AuthenticationCreds
110 | let keys: unknown = {}
111 |
112 | const storedCreds = await Database.session.findFirst({
113 | where: {
114 | sessionId: "creds"
115 | }
116 | })
117 | if (storedCreds && storedCreds.session) {
118 | const parsedCreds = JSON.parse(storedCreds.session, BufferJSON.reviver)
119 | creds = parsedCreds.creds as AuthenticationCreds
120 | keys = parsedCreds.keys
121 | } else {
122 | if (!storedCreds)
123 | await Database.session.create({
124 | data: {
125 | sessionId: "creds"
126 | }
127 | })
128 | creds = initAuthCreds()
129 | }
130 |
131 | const saveState = async (): Promise => {
132 | try {
133 | const session = JSON.stringify({ creds, keys }, BufferJSON.replacer)
134 | await Database.session.update({ where: { sessionId: "creds" }, data: { session } })
135 | } catch {}
136 | }
137 |
138 | return {
139 | state: {
140 | creds,
141 | keys: {
142 | get: (type, ids) => {
143 | const key = KEY_MAP[type]
144 | return ids.reduce((dict: unknown, id) => {
145 | const value: unknown = keys[key]?.[id]
146 | if (value) {
147 | if (type === "app-state-sync-key") dict[id] = proto.Message.AppStateSyncKeyData.fromObject(value)
148 | dict[id] = value
149 | }
150 | return dict
151 | }, {})
152 | },
153 | set: async (data) => {
154 | for (const _key in data) {
155 | const key = KEY_MAP[_key as keyof SignalDataTypeMap]
156 | keys[key] = keys[key] || {}
157 | Object.assign(keys[key], data[_key])
158 | }
159 | try {
160 | await saveState()
161 | } catch {}
162 | }
163 | }
164 | },
165 | saveState,
166 | clearState: async (): Promise => {
167 | try {
168 | await Database.session.delete({
169 | where: { sessionId: "creds" }
170 | })
171 | } catch {}
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/command.ts:
--------------------------------------------------------------------------------
1 | import { Queue } from "@arugaz/queue"
2 | import Collection from "@arugaz/collection"
3 | import type { Command, Event } from "../../types/command"
4 |
5 | /**
6 | * Commands collection
7 | * to store in memory
8 | */
9 | export const commands = new Collection()
10 |
11 | /**
12 | * Queue collection
13 | * handle with max 25 commands at the same time
14 | * with max timeout 30 seconds
15 | */
16 | export const commandQueues = new Queue({ concurrency: 25, timeout: 30 * 1000, throwOnTimeout: true })
17 |
18 | /**
19 | * Events collection
20 | * to store in memory
21 | */
22 | export const events = new Map()
23 |
24 | /**
25 | * Cooldown collection
26 | * to store in memory
27 | */
28 | export const cooldowns = new Map()
29 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/database/group-metadata.ts:
--------------------------------------------------------------------------------
1 | import type { GroupMetadata } from "@prisma/client"
2 | import NodeCache from "node-cache"
3 | import Database from "../../database"
4 |
5 | export const groupMetadata = new NodeCache({
6 | stdTTL: 60 * 10, // 10 mins
7 | useClones: false
8 | })
9 |
10 | export const getGroupMetadata = async (groupId: string) => {
11 | try {
12 | if (groupMetadata.has(groupId)) return groupMetadata.get(groupId) as GroupMetadata
13 |
14 | const groupMetadataData = await Database.groupMetadata.findUnique({
15 | where: { groupId }
16 | })
17 |
18 | if (groupMetadataData) groupMetadata.set(groupId, groupMetadataData)
19 |
20 | return groupMetadataData
21 | } catch {
22 | return null
23 | }
24 | }
25 |
26 | export const createGroupMetadata = async (groupId: string, metadata: Partial>) => {
27 | try {
28 | if (groupMetadata.has(groupId)) return groupMetadata.get(groupId) as GroupMetadata
29 |
30 | const groupMetadataData = await Database.groupMetadata.create({
31 | data: {
32 | groupId,
33 | subject: metadata?.subject || "",
34 | creation: metadata?.creation || Date.now(),
35 | owner: metadata?.owner || "",
36 | desc: metadata?.desc || "",
37 | restrict: metadata?.restrict || false,
38 | announce: metadata?.announce || false,
39 | participants: metadata?.participants || []
40 | }
41 | })
42 |
43 | if (groupMetadataData) groupMetadata.set(groupId, groupMetadataData)
44 |
45 | return groupMetadataData
46 | } catch {
47 | return null
48 | }
49 | }
50 |
51 | export const updateGroupMetadata = async (groupId: string, metadata: Partial>) => {
52 | try {
53 | const groupMetadataData = await Database.groupMetadata.update({
54 | where: { groupId },
55 | data: { ...metadata }
56 | })
57 |
58 | if (groupMetadataData) groupMetadata.set(groupId, groupMetadataData)
59 |
60 | return groupMetadataData
61 | } catch {
62 | return null
63 | }
64 | }
65 |
66 | export const deleteGroupMetadata = async (groupId: string) => {
67 | try {
68 | if (groupMetadata.has(groupId)) groupMetadata.del(groupId)
69 |
70 | return await Database.groupMetadata.delete({ where: { groupId } })
71 | } catch {
72 | return null
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/database/group.ts:
--------------------------------------------------------------------------------
1 | import type { Prisma, Group } from "@prisma/client"
2 | import NodeCache from "node-cache"
3 | import Database from "../../../libs/database"
4 | import config from "../../../utils/config"
5 |
6 | export const group = new NodeCache({
7 | stdTTL: 60 * 10, // 10 mins
8 | useClones: false
9 | })
10 |
11 | export const getGroup = async (groupId: string) => {
12 | try {
13 | if (group.has(groupId)) return group.get(groupId) as Group
14 |
15 | const groupData = await Database.group.findUnique({
16 | where: { groupId }
17 | })
18 |
19 | if (groupData) group.set(groupId, groupData)
20 |
21 | return groupData
22 | } catch {
23 | return null
24 | }
25 | }
26 |
27 | export const getGroups = async (opts?: Prisma.GroupFindManyArgs) => {
28 | try {
29 | const groups = await Database.group.findMany(opts)
30 |
31 | return groups
32 | } catch {
33 | return null
34 | }
35 | }
36 |
37 | export const createGroup = async (groupId: string, metadata: Partial>) => {
38 | try {
39 | if (group.has(groupId)) return group.get(groupId) as Group
40 |
41 | const groupData = await Database.group.create({
42 | data: {
43 | groupId,
44 | ...metadata,
45 | name: metadata.name ?? "",
46 | language: config.language,
47 | anticountry: { number: [], active: false }
48 | }
49 | })
50 |
51 | if (groupData) group.set(groupId, groupData)
52 |
53 | return groupData
54 | } catch {
55 | return null
56 | }
57 | }
58 |
59 | export const updateGroup = async (groupId: string, metadata: Partial>) => {
60 | try {
61 | const groupData = await Database.group.update({
62 | where: { groupId },
63 | data: { ...metadata }
64 | })
65 |
66 | if (groupData) group.set(groupId, groupData)
67 |
68 | return groupData
69 | } catch {
70 | return null
71 | }
72 | }
73 |
74 | export const deleteGroup = async (groupId: string) => {
75 | try {
76 | if (group.has(groupId)) group.del(groupId)
77 |
78 | return await Database.group.delete({ where: { groupId } })
79 | } catch {
80 | return null
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/database/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./group"
2 | export * from "./group-metadata"
3 | export * from "./user"
4 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/database/user.ts:
--------------------------------------------------------------------------------
1 | import type { Prisma, User } from "@prisma/client"
2 | import NodeCache from "node-cache"
3 | import Database from "../../../libs/database"
4 | import config from "../../../utils/config"
5 |
6 | export const user = new NodeCache({
7 | stdTTL: 60 * 10, // 10 mins
8 | useClones: false
9 | })
10 |
11 | export const getUser = async (userId: string) => {
12 | try {
13 | if (user.has(userId)) return user.get(userId) as User
14 |
15 | const userData = await Database.user.findUnique({
16 | where: { userId }
17 | })
18 |
19 | if (userData) user.set(userId, userData)
20 |
21 | return userData
22 | } catch {
23 | return null
24 | }
25 | }
26 |
27 | export const getUsers = async (opts?: Prisma.UserFindManyArgs) => {
28 | try {
29 | const userData = await Database.user.findMany(opts)
30 |
31 | return userData
32 | } catch {
33 | return null
34 | }
35 | }
36 |
37 | export const createUser = async (userId: string, metadata: Partial>) => {
38 | try {
39 | if (user.has(userId)) return user.get(userId) as User
40 |
41 | const userData = await Database.user.create({
42 | data: {
43 | userId,
44 | name: metadata?.name || "",
45 | language: config.language,
46 | limit: config.user.basic.limit || 30,
47 | ban: metadata?.ban || false
48 | }
49 | })
50 |
51 | if (userData) user.set(userId, userData)
52 |
53 | return userData
54 | } catch {
55 | return null
56 | }
57 | }
58 |
59 | export const updateUser = async (userId: string, metadata: Partial>) => {
60 | try {
61 | const userData = await Database.user.update({
62 | where: { userId },
63 | data: { ...metadata }
64 | })
65 |
66 | if (userData) user.set(userId, userData)
67 |
68 | return userData
69 | } catch {
70 | return null
71 | }
72 | }
73 |
74 | export const deleteUser = async (userId: string) => {
75 | try {
76 | if (user.has(userId)) user.del(userId)
77 |
78 | return await Database.user.delete({ where: { userId } })
79 | } catch {
80 | return null
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/index.ts:
--------------------------------------------------------------------------------
1 | import WAClient from "./client"
2 |
3 | export * as auth from "./auth"
4 | export * as command from "./command"
5 | export * as database from "./database"
6 | export * as serialize from "./serialize"
7 |
8 | export default WAClient
9 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/serialize/call.ts:
--------------------------------------------------------------------------------
1 | import { WACallEvent } from "baileys"
2 | import WAClient from "../../../libs/whatsapp"
3 | import config from "../../../utils/config"
4 | import type { CallSerialize } from "../../../types/serialize"
5 |
6 | /** Call Serialize */
7 | export const call = async (aruga: WAClient, call: WACallEvent): Promise => {
8 | const c = {}
9 | c.callFrom = aruga.decodeJid(call.from)
10 | c.callId = call.id
11 | c.status = call.status
12 |
13 | function reply(text: string) {
14 | return aruga.sendMessage(c.callFrom, {
15 | text: "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" + "┃\n" + `┃ ${text}\n` + "┃\n" + `┗━━「 ꗥ${config.name}ꗥ 」`,
16 | mentions: [c.callFrom]
17 | })
18 | }
19 | c.reply = reply
20 |
21 | return c
22 | }
23 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/serialize/group-participant.ts:
--------------------------------------------------------------------------------
1 | import { WAMessage } from "baileys"
2 | import WAClient from "../../../libs/whatsapp"
3 | import config from "../../../utils/config"
4 | import type { GroupParticipantSerialize } from "../../../types/serialize"
5 |
6 | export const groupParticipant = async (aruga: WAClient, msg: WAMessage): Promise => {
7 | const m = {}
8 | m.from = aruga.decodeJid(msg.key.remoteJid)
9 | m.sender = aruga.decodeJid(msg.key.fromMe ? aruga.user.id : m.from.endsWith("g.us") || m.from === "status@broadcast" ? msg.key?.participant || msg.participant : m.from)
10 | m.body = msg.messageStubParameters
11 | m.type = msg.messageStubType
12 | m.timestamps = (typeof msg.messageTimestamp === "number" ? msg.messageTimestamp : msg.messageTimestamp.low ? msg.messageTimestamp.low : msg.messageTimestamp.high) * 1000 || Date.now()
13 |
14 | function reply(text: string) {
15 | return aruga.sendMessage(m.from, {
16 | text: "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" + "┃\n" + `┃ ${text}\n` + "┃\n" + `┗━━「 ꗥ${config.name}ꗥ 」`,
17 | mentions: (m.body[1] || m.body[0]).includes("@") ? [m.sender].concat(m.body) : [m.sender]
18 | })
19 | }
20 | m.reply = reply
21 |
22 | return m
23 | }
24 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/serialize/group.ts:
--------------------------------------------------------------------------------
1 | import { WAMessage } from "baileys"
2 | import WAClient from "../../../libs/whatsapp"
3 | import config from "../../../utils/config"
4 | import type { GroupSerialize } from "../../../types/serialize"
5 |
6 | export const group = async (aruga: WAClient, msg: WAMessage): Promise => {
7 | const m = {}
8 | m.from = aruga.decodeJid(msg.key.remoteJid)
9 | m.sender = aruga.decodeJid(msg.key.fromMe ? aruga.user.id : m.from.endsWith("g.us") || m.from === "status@broadcast" ? msg.key?.participant || msg.participant : m.from)
10 | m.body = [msg.messageStubParameters[1] ?? msg.messageStubParameters[0]].join("")
11 | m.type = msg.messageStubType
12 | m.timestamps = (typeof msg.messageTimestamp === "number" ? msg.messageTimestamp : msg.messageTimestamp.low ? msg.messageTimestamp.low : msg.messageTimestamp.high) * 1000 || Date.now()
13 |
14 | function reply(text: string) {
15 | return aruga.sendMessage(m.from, {
16 | text: "┏━━「 𓆩 𝐻ɪᴅᴅᴇɴ 𝐹ɪɴᴅᴇʀ 𓆪 」\n" + "┃\n" + `┃ ${text}\n` + "┃\n" + `┗━━「 ꗥ${config.name}ꗥ 」`,
17 | mentions: [m.sender]
18 | })
19 | }
20 | m.reply = reply
21 |
22 | return m
23 | }
24 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/serialize/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./call"
2 | export * from "./group"
3 | export * from "./group-participant"
4 | export * from "./message"
5 |
--------------------------------------------------------------------------------
/src/libs/whatsapp/serialize/message.ts:
--------------------------------------------------------------------------------
1 | import { WAMessage } from "baileys"
2 | import WAClient, { database } from "../../../libs/whatsapp"
3 | import type { MessageSerialize } from "../../../types/serialize"
4 |
5 | export const message = async (aruga: WAClient, msg: WAMessage): Promise => {
6 | const m = {}
7 | m.message = msg.message?.viewOnceMessage
8 | ? msg.message.viewOnceMessage?.message
9 | : msg.message?.ephemeralMessage
10 | ? msg.message.ephemeralMessage?.message
11 | : msg.message?.documentWithCaptionMessage
12 | ? msg.message.documentWithCaptionMessage?.message
13 | : msg.message?.viewOnceMessageV2
14 | ? msg.message.viewOnceMessageV2?.message
15 | : msg.message?.editedMessage
16 | ? msg.message.editedMessage?.message
17 | : msg.message?.viewOnceMessageV2Extension
18 | ? msg.message.viewOnceMessageV2Extension?.message
19 | : msg.message
20 | if (m.message) {
21 | m.key = msg.key
22 | m.id = m.key.id
23 | m.isBotMsg = (m.id.startsWith("BAE5") && m.id.length === 16) || (m.id.startsWith("3EB0") && m.key.id.length === 12) || (m.id.startsWith("ARUGAZ") && m.id.length === 20)
24 | m.isGroupMsg = m.key.remoteJid.endsWith("g.us")
25 | m.from = aruga.decodeJid(m.key.remoteJid)
26 | m.fromMe = m.key.fromMe
27 | m.type = Object.keys(m.message).find((type) => type !== "senderKeyDistributionMessage" && type !== "messageContextInfo")
28 | m.sender = aruga.decodeJid(m.fromMe ? aruga.user.id : m.isGroupMsg || m.from === "status@broadcast" ? m.key.participant || msg.participant : m.from)
29 | m.key.participant = !m.key.participant || m.key.participant === "status_me" ? m.sender : m.key.participant
30 | m.body = m.message.conversation
31 | ? m.message.conversation
32 | : m.message.extendedTextMessage
33 | ? m.message.extendedTextMessage.text
34 | : m.message.imageMessage
35 | ? m.message.imageMessage.caption
36 | : m.message.videoMessage
37 | ? m.message.videoMessage.caption
38 | : m.message.documentMessage
39 | ? m.message.documentMessage.caption
40 | : m.message.buttonsResponseMessage
41 | ? m.message.buttonsResponseMessage.selectedButtonId
42 | : m.message.listResponseMessage
43 | ? m.message.listResponseMessage.singleSelectReply.selectedRowId
44 | : m.message.templateButtonReplyMessage
45 | ? m.message.templateButtonReplyMessage.selectedId
46 | : m.message.reactionMessage
47 | ? m.message.reactionMessage.text
48 | : m.message.locationMessage
49 | ? m.message.locationMessage.comment
50 | : ""
51 | m.mentions = m.message[m.type]?.contextInfo?.mentionedJid || []
52 | m.viewOnce = !!msg.message?.viewOnceMessage || !!msg.message?.viewOnceMessageV2 || !!msg.message?.viewOnceMessageV2Extension
53 | function reply(text: string, quoted = false) {
54 | return aruga.sendMessage(
55 | m.from,
56 | { text, ...(m.isGroupMsg ? { mentions: [m.sender] } : {}) },
57 | {
58 | ...(quoted ? { quoted: m } : {}),
59 | ephemeralExpiration: m.expiration
60 | }
61 | )
62 | }
63 | m.reply = reply
64 | }
65 |
66 | m.timestamps = (typeof msg.messageTimestamp === "number" ? msg.messageTimestamp : msg.messageTimestamp.low ? msg.messageTimestamp.low : msg.messageTimestamp.high) * 1000 || Date.now()
67 | m.expiration = m.message[m.type]?.contextInfo?.expiration || 0
68 | m.pushname = msg.pushName || "anonymous"
69 | m.status = msg.status || 0
70 | m.groupMetadata = m.isGroupMsg && ((await database.getGroupMetadata(m.from)) ?? (await database.createGroupMetadata(m.from, (await aruga.groupMetadata(m.from)) as unknown)))
71 |
72 | m.quoted = {}
73 | m.quoted.message = m.message[m.type]?.contextInfo?.quotedMessage
74 | ? m.message[m.type].contextInfo.quotedMessage?.viewOnceMessage
75 | ? m.message[m.type].contextInfo.quotedMessage.viewOnceMessage?.message
76 | : m.message[m.type].contextInfo.quotedMessage?.ephemeralMessage
77 | ? m.message[m.type].contextInfo.quotedMessage.ephemeralMessage?.message
78 | : m.message[m.type].contextInfo.quotedMessage?.documentWithCaptionMessage
79 | ? m.message[m.type].contextInfo.quotedMessage.documentWithCaptionMessage?.message
80 | : m.message[m.type].contextInfo.quotedMessage?.viewOnceMessageV2
81 | ? m.message[m.type].contextInfo.quotedMessage.viewOnceMessageV2?.message
82 | : m.message[m.type].contextInfo.quotedMessage?.editedMessage
83 | ? m.message[m.type].contextInfo.quotedMessage.editedMessage?.message
84 | : m.message[m.type].contextInfo.quotedMessage?.viewOnceMessageV2Extension
85 | ? m.message[m.type].contextInfo.quotedMessage.viewOnceMessageV2Extension?.message
86 | : m.message[m.type].contextInfo.quotedMessage
87 | : null
88 | if (m.quoted.message) {
89 | m.quoted.key = {
90 | participant: aruga.decodeJid(m.message[m.type]?.contextInfo?.participant),
91 | remoteJid: m?.message[m.type]?.contextInfo?.remoteJid || m.from || m.sender,
92 | fromMe: aruga.decodeJid(m.message[m.type].contextInfo.participant) === aruga.decodeJid(aruga.user.id),
93 | id: m.message[m.type].contextInfo.stanzaId
94 | }
95 | m.quoted.id = m.quoted.key.id
96 | m.quoted.isBotMsg = (m.quoted.id.startsWith("BAE5") && m.quoted.id.length === 16) || (m.id.startsWith("3EB0") && m.key.id.length === 12) || (m.quoted.id.startsWith("ARUGAZ") && m.quoted.id.length === 20)
97 | m.quoted.isGroupMsg = m.quoted.key.remoteJid.endsWith("g.us")
98 | m.quoted.from = aruga.decodeJid(m.quoted.key.remoteJid)
99 | m.quoted.fromMe = m.quoted.key.fromMe
100 | m.quoted.type = Object.keys(m.quoted.message).find((type) => type !== "senderKeyDistributionMessage" && type !== "messageContextInfo")
101 | m.quoted.sender = m.quoted.key.participant
102 | m.quoted.key.participant = !m.quoted.key.participant ? m.sender : m.quoted.key.participant
103 | m.quoted.body = m.quoted.message.conversation
104 | ? m.quoted.message.conversation
105 | : m.quoted.message.extendedTextMessage
106 | ? m.quoted.message.extendedTextMessage.text
107 | : m.quoted.message.imageMessage
108 | ? m.quoted.message.imageMessage.caption
109 | : m.quoted.message.videoMessage
110 | ? m.quoted.message.videoMessage.caption
111 | : m.quoted.message.documentMessage
112 | ? m.quoted.message.documentMessage.caption
113 | : m.quoted.message.buttonsResponseMessage
114 | ? m.quoted.message.buttonsResponseMessage.selectedButtonId
115 | : m.quoted.message.listResponseMessage
116 | ? m.quoted.message.listResponseMessage.singleSelectReply.selectedRowId
117 | : m.quoted.message.templateButtonReplyMessage
118 | ? m.quoted.message.templateButtonReplyMessage.selectedId
119 | : m.quoted.message.reactionMessage
120 | ? m.quoted.message.reactionMessage.text
121 | : m.quoted.message.locationMessage
122 | ? m.quoted.message.locationMessage.comment
123 | : ""
124 | m.quoted.mentions = m.quoted.message[m.quoted.type]?.contextInfo?.mentionedJid || []
125 | m.quoted.viewOnce = !!m.message[m.type].contextInfo.quotedMessage?.viewOnceMessage || !!m.message[m.type].contextInfo.quotedMessage?.viewOnceMessageV2 || !!m.message[m.type].contextInfo.quotedMessage?.viewOnceMessageV2Extension
126 | function reply(text: string, quoted = false) {
127 | return aruga.sendMessage(
128 | m.from,
129 | { text, ...(m.quoted.isGroupMsg ? { mentions: [m.quoted.sender] } : {}) },
130 | {
131 | ...(quoted ? { quoted: m.quoted } : {}),
132 | ephemeralExpiration: m.expiration
133 | }
134 | )
135 | }
136 | m.quoted.reply = reply
137 | } else m.quoted = null
138 |
139 | return m
140 | }
141 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { inspect } from "util"
2 |
3 | import cfonts from "cfonts"
4 | import qrcode from "qrcode"
5 | import NodeCache from "node-cache"
6 |
7 | import fastifyServer, { whatsappRoutes } from "./libs/server"
8 | import WAClient, { serialize } from "./libs/whatsapp"
9 | import Database from "./libs/database"
10 | import { i18nInit } from "./libs/international"
11 |
12 | import * as callHandler from "./handlers/call"
13 | import * as groupHandler from "./handlers/group"
14 | import * as groupParticipantHandler from "./handlers/group-participant"
15 | import * as messageHandler from "./handlers/message"
16 |
17 | import { resetUserLimit, resetUserRole } from "./utils/cron"
18 |
19 | /** Initial Server */
20 | const fastify = fastifyServer({
21 | // fastify options
22 | trustProxy: true
23 | })
24 |
25 | /** Initial Whatsapp Client */
26 | const aruga = new WAClient({
27 | // auth type "single" or "multi"
28 | authType: "single",
29 | // baileys options
30 | generateHighQualityLinkPreview: true,
31 | mediaCache: new NodeCache({
32 | stdTTL: 60 * 5, // 5 mins
33 | useClones: false
34 | }),
35 | syncFullHistory: false,
36 | userDevicesCache: new NodeCache({
37 | stdTTL: 60 * 10, // 10 mins
38 | useClones: false
39 | })
40 | })
41 |
42 | /** Handler Event */
43 | ;(() => {
44 | // handle call event
45 | aruga.on("call", (call) =>
46 | serialize
47 | .call(aruga, call)
48 | .then((call) => callHandler.execute(aruga, call).catch(() => void 0))
49 | .catch(() => void 0)
50 | )
51 |
52 | // handle group event
53 | aruga.on("group", (message) =>
54 | serialize
55 | .group(aruga, message)
56 | .then((message) => groupHandler.execute(aruga, message).catch(() => void 0))
57 | .catch(() => void 0)
58 | )
59 |
60 | // handle group participants event
61 | aruga.on("group.participant", (message) =>
62 | serialize
63 | .groupParticipant(aruga, message)
64 | .then((message) => groupParticipantHandler.execute(aruga, message).catch(() => void 0))
65 | .catch(() => void 0)
66 | )
67 |
68 | // handle message event
69 | aruga.on("message", (message) =>
70 | serialize
71 | .message(aruga, message)
72 | .then((message) => messageHandler.execute(aruga, message).catch(() => void 0))
73 | .catch(() => void 0)
74 | )
75 |
76 | // handle qr code event
77 | aruga.on("qr", (qrCode) =>
78 | qrcode
79 | .toString(qrCode, { type: "terminal", small: true })
80 | .then((qrResult) => console.log(qrResult))
81 | .catch(() => void 0)
82 | )
83 | })()
84 |
85 | /** Pretty Sexy :D */
86 | const clearProcess = async () => {
87 | aruga.log("Clear all process", "info")
88 | try {
89 | resetUserLimit.stop()
90 | resetUserRole.stop()
91 | await fastify.close()
92 | await Database.$disconnect()
93 | process.exit(0)
94 | } catch {
95 | process.exit(1)
96 | }
97 | }
98 | for (const signal of ["SIGINT", "SIGTERM"]) process.on(signal, clearProcess)
99 | for (const signal of ["unhandledRejection", "uncaughtException"]) process.on(signal, (reason: unknown) => aruga.log(inspect(reason, true), "error"))
100 |
101 | /** Start Client */
102 | setImmediate(async () => {
103 | try {
104 | /** api routes */
105 | whatsappRoutes(fastify, aruga)
106 |
107 | // initialize
108 | await aruga.startClient()
109 | await fastify.ready()
110 |
111 | process.nextTick(
112 | () =>
113 | messageHandler
114 | .registerCommand("commands")
115 | .then((size) => aruga.log(`Success Register ${size} commands`))
116 | .catch((err) => {
117 | aruga.log(inspect(err, true), "error")
118 | clearProcess()
119 | }),
120 | fastify
121 | .listen({ host: "127.0.0.1", port: process.env.PORT || 3000 })
122 | .then((address) => aruga.log(`Server run on ${address}`))
123 | .catch((err) => {
124 | aruga.log(inspect(err, true), "error")
125 | clearProcess()
126 | }),
127 | i18nInit()
128 | )
129 |
130 | // logs <3
131 | cfonts.say("Whatsapp Bot", {
132 | align: "center",
133 | colors: ["#8cf57b" as HexColor],
134 | font: "block",
135 | space: false
136 | })
137 | cfonts.say("'whatsapp-bot' By @hidden-finder", {
138 | align: "center",
139 | font: "console",
140 | gradient: ["red", "#ee82f8" as HexColor]
141 | })
142 | } catch (err: unknown) {
143 | aruga.log(inspect(err, true), "error")
144 | clearProcess()
145 | }
146 | })
147 |
--------------------------------------------------------------------------------
/src/types/cfonts.d.ts:
--------------------------------------------------------------------------------
1 | declare module "cfonts" {
2 | /**
3 | * Declares for cfonts modules
4 | * @param {string} text Text to display
5 | * @param {any} opts:
6 | * {
7 | *
8 | * font?:'3d'|'block'|'chrome'|'console'|'grid'|'huge'|'pallet'|'shade'|'simple'|'simple3d'|'simpleblock'|'slick'|'tiny';
9 | *
10 | * align?:'center'|'right';
11 | *
12 | * colors?:Array<|'system'|'black'|'red'|'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'gray'|'redBright'|'greenBright'|'yellowBright'|'blueBright'|'magentaBright'|'cyanBright'|'whiteBright'|HexColor>;
13 | *
14 | * background?:Array<|'transparent'|'black'|'red'|'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'gray'|'redBright'|'greenBright'|'yellowBright'|'blueBright'|'magentaBright'|'cyanBright'|'whiteBright'|HexColor>;
15 | *
16 | * letterSpacing?:number;
17 | *
18 | * lineHeight?:number;
19 | *
20 | * space?:boolean;
21 | *
22 | * maxLength?:string;
23 | *
24 | * gradient?:Array<|'system'|'black'|'red'|'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'gray'|HexColor>;
25 | *
26 | * independentGradient?:boolean;
27 | *
28 | * transitionGradient?:boolean;
29 | *
30 | * env?:string;
31 | *
32 | * }
33 | * @returns {void}
34 | */
35 | function say(
36 | text: string,
37 | opts: {
38 | font?: "3d" | "block" | "chrome" | "console" | "grid" | "huge" | "pallet" | "shade" | "simple" | "simple3d" | "simpleblock" | "slick" | "tiny"
39 | align?: "center" | "right"
40 | colors?: Array<
41 | | "system"
42 | | "black"
43 | | "red"
44 | | "green"
45 | | "yellow"
46 | | "blue"
47 | | "magenta"
48 | | "cyan"
49 | | "white"
50 | | "gray"
51 | | "redBright"
52 | | "greenBright"
53 | | "yellowBright"
54 | | "blueBright"
55 | | "magentaBright"
56 | | "cyanBright"
57 | | "whiteBright"
58 | | HexColor
59 | >
60 | background?: Array<
61 | | "transparent"
62 | | "black"
63 | | "red"
64 | | "green"
65 | | "yellow"
66 | | "blue"
67 | | "magenta"
68 | | "cyan"
69 | | "white"
70 | | "gray"
71 | | "redBright"
72 | | "greenBright"
73 | | "yellowBright"
74 | | "blueBright"
75 | | "magentaBright"
76 | | "cyanBright"
77 | | "whiteBright"
78 | | HexColor
79 | >
80 | letterSpacing?: number
81 | lineHeight?: number
82 | space?: boolean
83 | maxLength?: string
84 | gradient?: Array<"system" | "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | HexColor>
85 | independentGradient?: boolean
86 | transitionGradient?: boolean
87 | env?: string
88 | }
89 | ): void
90 | }
91 |
--------------------------------------------------------------------------------
/src/types/client.d.ts:
--------------------------------------------------------------------------------
1 | import type { AuthenticationState, SocketConfig, WACallEvent, WAMessage, WASocket } from "baileys"
2 |
3 | declare type ArugaAuth = {
4 | state: AuthenticationState
5 | saveState: () => Promise
6 | clearState: () => Promise
7 | }
8 |
9 | declare type Aruga = Partial
10 |
11 | declare type ArugaConfig = {
12 | authType: "single" | "multi"
13 | } & Partial>
14 |
15 | declare type ArugaEvents = {
16 | call: (call: WACallEvent) => void
17 | group: (message: WAMessage) => void
18 | "group.participant": (message: WAMessage) => void
19 | message: (message: WAMessage) => void
20 | qr: (qr: string) => void
21 | }
22 |
23 | declare type ArugaEventEmitter = {
24 | on(event: E, listener: ArugaEvents[E]): this
25 | off(event: E, listener: ArugaEvents[E]): this
26 | emit(event: E, ...args: Parameters): boolean
27 | removeAllListeners(event?: E): this
28 | }
29 |
--------------------------------------------------------------------------------
/src/types/colors.d.ts:
--------------------------------------------------------------------------------
1 | // cfonts declare
2 | declare type HexDigit =
3 | | "0"
4 | | "1"
5 | | "2"
6 | | "3"
7 | | "4"
8 | | "5"
9 | | "6"
10 | | "7"
11 | | "8"
12 | | "9"
13 | | "a"
14 | | "b"
15 | | "c"
16 | | "d"
17 | | "e"
18 | | "f"
19 | | "A"
20 | | "B"
21 | | "C"
22 | | "D"
23 | | "E"
24 | | "F"
25 |
26 | declare type Hex = T extends `#${HexDigit}${HexDigit}${HexDigit}${infer ColorHex}`
27 | ? ColorHex extends ``
28 | ? T
29 | : ColorHex extends `${HexDigit}${HexDigit}${HexDigit}`
30 | ? T
31 | : never
32 | : never
33 |
34 | declare type HexColor = string & { __type: "Hex" }
35 |
--------------------------------------------------------------------------------
/src/types/command.d.ts:
--------------------------------------------------------------------------------
1 | import type { Group, User } from "@prisma/client"
2 | import type Client from "../libs/whatsapp"
3 | import type { MessageSerialize } from "./serialize"
4 |
5 | declare type Command = {
6 | /**
7 | * Set commands aliases for match users messages
8 | * @type {string[]}
9 | * @example
10 | * aliases: ['help'] // default match message by filename /menu === menu.ts
11 | */
12 | aliases?: string[]
13 |
14 | /**
15 | * Set commands category
16 | * @type {string}
17 | * @example
18 | * category: 'general'
19 | */
20 | category: "convert" | "general" | "misc" | "owner" | "group"
21 |
22 | /**
23 | * Set commands cooldown, every user will have their own cooldown, every command also have their own cooldown
24 | * @type {number}
25 | * @example
26 | * cd: 10 // default 3 seconds for every command, pls add cd atleast 1 sec for avoid spam message
27 | */
28 | cd?: number
29 |
30 | /**
31 | * Set commands description
32 | * @type {string}
33 | * @example
34 | * desc: "Ping bot!"
35 | */
36 | desc?: string
37 |
38 | /**
39 | * Set commands that only can be used in group chats
40 | * @type {boolean}
41 | * @example
42 | * groupOnly: true // default false
43 | */
44 | groupOnly?: boolean
45 |
46 | /**
47 | * Set commands that only can be used in private chats
48 | * @type {boolean}
49 | * @example
50 | * privateOnly: true // default false
51 | */
52 | privateOnly?: boolean
53 |
54 | /**
55 | * Set commands that only premium users can use
56 | * @type {boolean}
57 | * @example
58 | * premiumOnly: true // default false
59 | */
60 | premiumOnly?: boolean
61 |
62 | /**
63 | * Set commands that only group admins can use
64 | * @type {boolean}
65 | * @example
66 | * adminGroup: true // default false
67 | */
68 | adminGroup?: boolean
69 |
70 | /**
71 | * Set commands that only group owner can use
72 | * @type {boolean}
73 | * @example
74 | * ownerGroup: true // default false
75 | */
76 | ownerGroup?: boolean
77 |
78 | /**
79 | * Set commands that can only be used when the bot is a group admin
80 | * @type {boolean}
81 | * @example
82 | * maintenance: true // default false
83 | */
84 | botGroupAdmin?: boolean
85 |
86 | /**
87 | * Set commands to maintenance mode that only owner can use
88 | * @type {boolean}
89 | * @example
90 | * maintenance: true // default false
91 | */
92 | maintenance?: boolean
93 |
94 | /**
95 | * Set commands that only bot owners can use
96 | * @type {boolean}
97 | * @example
98 | * ownerOnly: true // default false
99 | */
100 | ownerOnly?: boolean
101 |
102 | /**
103 | * To reduce the limit when the command is success
104 | * @type {number}
105 | * @example
106 | * limit: 3 // default 0
107 | */
108 | limit?: number
109 |
110 | /**
111 | * To write how to use the command
112 | * You can use @PREFIX for replace with current prefix
113 | * and @CMD for command name
114 | * @type {string}
115 | * @example
116 | * example: "@PREFIX@CMD 69" //=> "/ping 69"
117 | */
118 | example?: string
119 |
120 | /**
121 | * Fill with the features you want
122 | * @type {CommandObject}
123 | * @example
124 | * execute: async ({ aruga, message, command, prefix, args, arg }) => {
125 | * await aruga.sendMessage(message.from, { text: `pong! ${arg}` })
126 | * }
127 | */
128 | execute: (obj: CommandObject) => unknown
129 | }
130 |
131 | declare type CommandObject = {
132 | aruga: Client
133 | message: MessageSerialize
134 | command: string
135 | prefix: string
136 | args: string[]
137 | arg: string
138 | isGroupOwner: boolean
139 | isGroupAdmin: boolean
140 | isBotGroupAdmin: boolean
141 | isOwner: boolean
142 | user: User
143 | group: Group
144 | }
145 |
146 | declare type Event = {
147 | execute: (obj: EventObject) => unknown
148 | }
149 |
150 | declare type EventObject = CommandObject
151 |
--------------------------------------------------------------------------------
/src/types/config.d.ts:
--------------------------------------------------------------------------------
1 | declare type UserConfig = {
2 | /**
3 | * Set a default limit
4 | * @type {number}
5 | * @example
6 | * limit: 30
7 | */
8 | limit: number
9 | /**
10 | * Set a default expired in a day
11 | * @type {number}
12 | * @example
13 | * expires: 30 // 30 days
14 | */
15 | expires: number
16 | }
17 |
18 | declare type Config = {
19 | /**
20 | * Set a default timezone
21 | * @type {string}
22 | * @example
23 | * timeZone: "America/Los_Angeles"
24 | */
25 | timeZone: string
26 |
27 | /**
28 | * Set a default bot language
29 | * @type {string}
30 | * @example
31 | * language: "pt"
32 | */
33 | language: string
34 |
35 | /**
36 | * List of bot owners! set your phone number here for manage bot
37 | * @type {string[]}
38 | * @example
39 | * ownerNumber: ['628xxxxxxxxxx', '918xxxxxxxxxx']
40 | */
41 | ownerNumber: string[]
42 | /**
43 | * Read the status of users that you have saved in contacts
44 | * @type {boolean}
45 | * @example
46 | * readStatus: true
47 | */
48 | readStatus: boolean
49 | /**
50 | * Read messages from chats
51 | * @type {boolean}
52 | * @example
53 | * readStatus: true
54 | */
55 | readMessage: boolean
56 |
57 | /** Anti call from users */
58 | antiCall: {
59 | /**
60 | * Reject call from user
61 | * @type {boolean}
62 | * @example
63 | * readStatus: true
64 | */
65 | reject: boolean
66 | /**
67 | * Block user that call bot
68 | * @type {boolean}
69 | * @example
70 | * readStatus: true
71 | */
72 | block: boolean
73 | /**
74 | * Ban user that call bot
75 | * @type {boolean}
76 | * @example
77 | * readStatus: true
78 | */
79 | ban: boolean
80 | }
81 | /**
82 | * Set a default prefix of bot! u can fill '#@' for double prefix or as much as you want '!@#$%^&/.'
83 | * @type {string}
84 | * @example
85 | * prefix: "/" || "/#" || "!@#$%^."
86 | */
87 | prefix: string
88 | /**
89 | * Set a default bot name
90 | * @type {number}
91 | * @example
92 | * name: "Kobeni"
93 | */
94 | name: string
95 | /**
96 | * Set a default footer for messages that require a footer
97 | * @type {number}
98 | * @example
99 | * footer: "@arugaz"
100 | */
101 | footer: string
102 | /**
103 | * Should the bot as the owner of the bot
104 | * @example
105 | * self: true
106 | */
107 | self: boolean
108 | /** User config */
109 | user: {
110 | basic: UserConfig
111 | premium: UserConfig
112 | vip: UserConfig
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/types/node-webpmux.d.ts:
--------------------------------------------------------------------------------
1 | declare module "node-webpmux" {
2 | export class Image {
3 | constructor()
4 | exif: Buffer
5 | load(buffer: Buffer | string): Promise
6 | save(...args: unknown[]): Promise
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/types/serialize.d.ts:
--------------------------------------------------------------------------------
1 | import type { proto, WACallUpdateType } from "baileys"
2 | import type { GroupMetadata } from "@prisma/client"
3 |
4 | /**
5 | * Call serialize
6 | */
7 | declare type CallSerialize = {
8 | /** Call Id */
9 | callId: string
10 |
11 | /** Call remoteJid */
12 | callFrom: string
13 |
14 | /** Call status */
15 | status: WACallUpdateType
16 |
17 | /**
18 | * Reply a message
19 | * @param {string} text: Message text
20 | * @returns {Promise} send messages
21 | */
22 | reply: (text: string) => Promise
23 | }
24 |
25 | /**
26 | * Group serialize
27 | */
28 | declare type GroupSerialize = {
29 | /** Group Jid */
30 | from: string
31 |
32 | /** Message sender */
33 | sender: string
34 |
35 | /** Message */
36 | body: string
37 |
38 | /** Message type */
39 | type: number
40 |
41 | /** Message Timestamps */
42 | timestamps: number
43 |
44 | /**
45 | * Reply a message
46 | * @param {string} text: Message text
47 | * @returns {Promise} send messages
48 | */
49 | reply: (text: string) => Promise
50 | }
51 |
52 | /**
53 | * Group Participant serialize
54 | */
55 | declare type GroupParticipantSerialize = {
56 | /** Group Jid */
57 | from: string
58 |
59 | /** Message sender */
60 | sender: string
61 |
62 | /** Message */
63 | body: string[]
64 |
65 | /** Message type */
66 | type: number
67 |
68 | /** Message Timestamps */
69 | timestamps: number
70 |
71 | /**
72 | * Reply a message
73 | * @param {string} text: Message text
74 | * @returns {Promise} send messages
75 | */
76 | reply: (text: string) => Promise
77 | }
78 |
79 | /**
80 | * Message serialize
81 | */
82 | declare type MessageSerialize = {
83 | /** Properties of a Message. */
84 | message: proto.IMessage
85 |
86 | /** Properties of a MessageKey. */
87 | key: proto.IMessageKey
88 |
89 | /** Message Id */
90 | id: string
91 |
92 | /** Is message from Bot? */
93 | isBotMsg: boolean
94 |
95 | /** Is message from group chats? */
96 | isGroupMsg: boolean
97 |
98 | /** Message remoteJid */
99 | from: string
100 |
101 | /** is message fromMe? | bot message ? */
102 | fromMe: boolean
103 |
104 | /** Type of a message */
105 | type: string
106 |
107 | /** Message sender */
108 | sender: string
109 |
110 | /** Body / content message */
111 | body: string
112 |
113 | /** Mentions user list */
114 | mentions: string[]
115 |
116 | /** Is message viewonce? */
117 | viewOnce: boolean
118 |
119 | /**
120 | * Reply a message
121 | * @param {string} text: Message text
122 | * @param {boolean} quoted?: Wanna reply to client?
123 | * @returns {Promise} if quoted is set to true will reply the message otherwise just typing back..
124 | */
125 | reply: (text: string, quoted?: boolean) => Promise
126 |
127 | // additional properties
128 | /** Message timestamps */
129 | timestamps?: number
130 | /** Chat expiration for ephemeral message */
131 | expiration?: number
132 | /** Nickname for users */
133 | pushname?: string
134 | /** WebMessageInfo status */
135 | status?: number
136 | /** Group Metadata */
137 | groupMetadata?: GroupMetadata
138 |
139 | /** Properties of a Quoted Message. */
140 | quoted: MessageSerialize | null
141 | }
142 |
--------------------------------------------------------------------------------
/src/types/sticker.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * WASticker Options
3 | */
4 | export type StickerOptions = {
5 | /** Sticker Pack title
6 | * @default arugaz
7 | */
8 | pack?: string
9 | /** Sticker Pack Author
10 | * @default whatsapp-bot
11 | */
12 | author?: string
13 | /** Sticker Pack ID
14 | * @default random string
15 | */
16 | id?: string
17 | /** Sticker Categories
18 | * @default [] didnt have a category
19 | */
20 | categories?: Categories[]
21 | /** Result Width, since WASticker 1x1 it will set height too
22 | * @default 256
23 | */
24 | width?: number
25 | /** Result Fps
26 | * @default 25
27 | */
28 | fps?: number
29 | /**
30 | * Loop the result ?
31 | * @default true
32 | */
33 | loop?: boolean
34 | /**
35 | * Compress the result ? number 0 - 6
36 | * @default 0
37 | */
38 | compress?: number
39 | }
40 |
41 | type Love =
42 | | "❤"
43 | | "😍"
44 | | "😘"
45 | | "💕"
46 | | "😻"
47 | | "💑"
48 | | "👩❤👩"
49 | | "👨❤👨"
50 | | "💏"
51 | | "👩❤💋👩"
52 | | "👨❤💋👨"
53 | | "🧡"
54 | | "💛"
55 | | "💚"
56 | | "💙"
57 | | "💜"
58 | | "🖤"
59 | | "💔"
60 | | "❣"
61 | | "💞"
62 | | "💓"
63 | | "💗"
64 | | "💖"
65 | | "💘"
66 | | "💝"
67 | | "💟"
68 | | "♥"
69 | | "💌"
70 | | "💋"
71 | | "👩❤️💋👩"
72 | | "👨❤️💋👨"
73 | | "👩❤️👨"
74 | | "👩❤️👩"
75 | | "👨❤️👨"
76 | | "👩❤️💋👨"
77 | | "👬"
78 | | "👭"
79 | | "👫"
80 | | "🥰"
81 | | "😚"
82 | | "😙"
83 | | "👄"
84 | | "🌹"
85 | | "😽"
86 | | "❣️"
87 | | "❤️"
88 | type Happy =
89 | | "😀"
90 | | "😃"
91 | | "😄"
92 | | "😁"
93 | | "😆"
94 | | "😅"
95 | | "😂"
96 | | "🤣"
97 | | "🙂"
98 | | "😛"
99 | | "😝"
100 | | "😜"
101 | | "🤪"
102 | | "🤗"
103 | | "😺"
104 | | "😸"
105 | | "😹"
106 | | "☺"
107 | | "😌"
108 | | "😉"
109 | | "🤗"
110 | | "😊"
111 | type Sad =
112 | | "☹"
113 | | "😣"
114 | | "😖"
115 | | "😫"
116 | | "😩"
117 | | "😢"
118 | | "😭"
119 | | "😞"
120 | | "😔"
121 | | "😟"
122 | | "😕"
123 | | "😤"
124 | | "😠"
125 | | "😥"
126 | | "😰"
127 | | "😨"
128 | | "😿"
129 | | "😾"
130 | | "😓"
131 | | "🙍♂"
132 | | "🙍♀"
133 | | "💔"
134 | | "🙁"
135 | | "🥺"
136 | | "🤕"
137 | | "☔️"
138 | | "⛈"
139 | | "🌩"
140 | | "🌧"
141 | type Angry =
142 | | "😯"
143 | | "😦"
144 | | "😧"
145 | | "😮"
146 | | "😲"
147 | | "🙀"
148 | | "😱"
149 | | "🤯"
150 | | "😳"
151 | | "❗"
152 | | "❕"
153 | | "🤬"
154 | | "😡"
155 | | "😠"
156 | | "🙄"
157 | | "👿"
158 | | "😾"
159 | | "😤"
160 | | "💢"
161 | | "👺"
162 | | "🗯️"
163 | | "😒"
164 | | "🥵"
165 | type Greet = "👋"
166 | type Celebrate =
167 | | "🎊"
168 | | "🎉"
169 | | "🎁"
170 | | "🎈"
171 | | "👯♂️"
172 | | "👯"
173 | | "👯♀️"
174 | | "💃"
175 | | "🕺"
176 | | "🔥"
177 | | "⭐️"
178 | | "✨"
179 | | "💫"
180 | | "🎇"
181 | | "🎆"
182 | | "🍻"
183 | | "🥂"
184 | | "🍾"
185 | | "🎂"
186 | | "🍰"
187 |
188 | /**
189 | * Whatsapp sticker category
190 | *
191 | * refer to: https://github.com/WhatsApp/stickers/wiki/Tag-your-stickers-with-Emojis
192 | */
193 | export type StickerCategories = Love | Happy | Sad | Angry | Greet | Celebrate
194 |
--------------------------------------------------------------------------------
/src/types/utils.d.ts:
--------------------------------------------------------------------------------
1 | declare type PhoneFormat = {
2 | /** country code, eg "62" | "55" | "7" */
3 | countryCode: string
4 | /** region code, eg "id" | "br" | "ru" */
5 | regionCode: string
6 | /** international phone number */
7 | international: string
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/cli.ts:
--------------------------------------------------------------------------------
1 | import { spawn } from "child_process"
2 |
3 | /**
4 | * FFMPEG Spawner functions
5 | * @param bufferData Input BUffer
6 | * @param options FFMPEG Options
7 | * @returns Result Buffer
8 | */
9 | export const ffmpeg = (bufferData: Buffer, options: string[]) =>
10 | new Promise((resolve, reject) => {
11 | const result: Uint8Array[] = []
12 | options = ["-hide_banner", "-loglevel", "error", "-i", "pipe:0", ...options, "-y", "pipe:1"]
13 | const spawner = spawn("ffmpeg", options, { windowsHide: true })
14 | spawner.stdout.on("data", (data: Uint8Array) => result.push(data))
15 | spawner.stdout.on("error", (err) => reject(err))
16 | spawner.on("error", (err) => reject(err))
17 | spawner.on("close", () => resolve(Buffer.concat(result)))
18 | spawner.stdin.on("error", (err) => reject(err))
19 | spawner.stdin.write(bufferData)
20 | spawner.stdin.end(() => (bufferData = null))
21 | })
22 |
--------------------------------------------------------------------------------
/src/utils/color.ts:
--------------------------------------------------------------------------------
1 | const color = {
2 | /**
3 | * Coloring string to black color
4 | * @param {string} text - Text to colored
5 | * @returns {string} color string
6 | */
7 | black: (text: string): string => `\x1B[30m${text}\x1B[39m`,
8 |
9 | /**
10 | * Coloring string to red color
11 | * @param {string} text - Text to colored
12 | * @returns {string} color string
13 | */
14 | red: (text: string): string => `\x1B[31m${text}\x1B[39m`,
15 |
16 | /**
17 | * Coloring string to green color
18 | * @param {string} text - Text to colored
19 | * @returns {string} color string
20 | */
21 | green: (text: string): string => `\x1B[32m${text}\x1B[39m`,
22 |
23 | /**
24 | * Coloring string to yellow color
25 | * @param {string} text - Text to colored
26 | * @returns {string} color string
27 | */
28 | yellow: (text: string): string => `\x1B[33m${text}\x1B[39m`,
29 |
30 | /**
31 | * Coloring string to blue color
32 | * @param {string} text - Text to colored
33 | * @returns {string} color string
34 | */
35 | blue: (text: string): string => `\x1B[34m${text}\x1B[39m`,
36 |
37 | /**
38 | * Coloring string to purple color
39 | * @param {string} text - Text to colored
40 | * @returns {string} color string
41 | */
42 | purple: (text: string): string => `\x1B[35m${text}\x1B[39m`,
43 |
44 | /**
45 | * Coloring string to cyan color
46 | * @param {string} text - Text to colored
47 | * @returns {string} color string
48 | */
49 | cyan: (text: string): string => `\x1B[36m${text}\x1B[39m`,
50 |
51 | /**
52 | * Coloring string to hex color
53 | * @param {string} hex:string - Hex color
54 | */
55 | hex:
56 | (hex: HexColor) =>
57 | /**
58 | * @param {string} text:string - Text to colored
59 | * @returns {string}
60 | */
61 | (text: string): string =>
62 | `\x1B[38;2;${hex
63 | .replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => `${m ? m : "#"}` + r + r + g + g + b + b)
64 | .substring(1)
65 | .match(/.{2}/g)
66 | .map((x) => parseInt(x, 16))
67 | .join(";")}m${text}\x1B[39m`
68 | }
69 |
70 | export default color
71 |
--------------------------------------------------------------------------------
/src/utils/config.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs"
2 | import { join } from "path"
3 |
4 | const configPath = join(__dirname, "..", "..", "config.json")
5 |
6 | const config: Config = JSON.parse(fs.readFileSync(configPath, "utf-8"))
7 |
8 | export default config
9 |
--------------------------------------------------------------------------------
/src/utils/cron.ts:
--------------------------------------------------------------------------------
1 | import { Cron } from "croner"
2 | import Database from "../libs/database"
3 | import config from "../utils/config"
4 | import { database } from "../libs/whatsapp"
5 |
6 | // Run CronJob every midnight for reset user limit
7 | export const resetUserLimit = new Cron(
8 | "0 0 0 * * *",
9 | {
10 | timezone: config.timeZone
11 | },
12 | async () => {
13 | await Promise.all([
14 | Database.user.updateMany({
15 | where: {
16 | userId: {
17 | contains: "s.whatsapp.net"
18 | },
19 | role: {
20 | in: ["basic"]
21 | }
22 | },
23 | data: {
24 | limit: config.user.basic.limit
25 | }
26 | }),
27 | Database.user.updateMany({
28 | where: {
29 | userId: {
30 | contains: "s.whatsapp.net"
31 | },
32 | role: {
33 | in: ["premium"]
34 | }
35 | },
36 | data: {
37 | limit: config.user.premium.limit
38 | }
39 | }),
40 | Database.user.updateMany({
41 | where: {
42 | userId: {
43 | contains: "s.whatsapp.net"
44 | },
45 | role: {
46 | in: ["vip"]
47 | }
48 | },
49 | data: {
50 | limit: config.user.vip.limit
51 | }
52 | })
53 | ])
54 |
55 | database.user.flushAll()
56 | }
57 | )
58 |
59 | // Run CronJob every 15mins for reset user role
60 | export const resetUserRole = new Cron(
61 | "0 */15 * * * *",
62 | {
63 | timezone: config.timeZone
64 | },
65 | async () => {
66 | await Database.user.updateMany({
67 | where: {
68 | userId: {
69 | contains: "s.whatsapp.net"
70 | },
71 | role: {
72 | in: ["premium", "vip"]
73 | },
74 | expire: {
75 | lte: Date.now()
76 | }
77 | },
78 | data: {
79 | role: "basic",
80 | expire: config.user.basic.expires ? config.user.basic.expires : 0
81 | }
82 | })
83 | }
84 | )
85 |
--------------------------------------------------------------------------------
/src/utils/fetcher.ts:
--------------------------------------------------------------------------------
1 | import FormData from "form-data"
2 | import axios, { AxiosRequestConfig } from "axios"
3 | import { generateRandString } from "../utils/filesystem"
4 |
5 | const fetchDefaultOptions: AxiosRequestConfig = {
6 | headers: {
7 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0"
8 | }
9 | }
10 |
11 | /**
12 | * Helper HTTP GET Functions
13 | * @param url url you want to fetch
14 | * @param opts axios options
15 | */
16 | export const fetcherGET = (url: string, opts?: AxiosRequestConfig) =>
17 | new Promise((resolve, reject) =>
18 | axios
19 | .get(url, Object.assign(fetchDefaultOptions, opts))
20 | .then(({ data }) => resolve(data as T))
21 | .catch(reject)
22 | )
23 |
24 | /**
25 | * Helper HTTP POST Functions
26 | * @param url url you want to fetch
27 | * @param opts axios options
28 | */
29 | export const fetcherPOST = (url: string, data: unknown, opts?: AxiosRequestConfig) =>
30 | new Promise((resolve, reject) =>
31 | axios
32 | .post(url, data, Object.assign(fetchDefaultOptions, opts))
33 | .then(({ data }) => resolve(data as T))
34 | .catch(reject)
35 | )
36 |
37 | /**
38 | * Helper HTTP GET Buffer
39 | * @param url url you want to fetch
40 | * @param opts axios options
41 | * @returns [content-type, Buffer]
42 | */
43 | export const fetcherBuffer = (url: string, opts?: AxiosRequestConfig) =>
44 | new Promise<[string, Buffer]>((resolve, reject) =>
45 | axios
46 | .get(url, Object.assign(fetchDefaultOptions, { ...opts, responseType: "arraybuffer" }))
47 | .then((res) => {
48 | resolve([res.headers["content-type"], res.data as Buffer])
49 | })
50 | .catch(reject)
51 | )
52 |
53 | /**
54 | * Upload media to telegraph server
55 | * @param buffData Buffer you want to upload
56 | * @param ext Buffer extension, like mp4, m3u8, jpeg etc..
57 | * @returns telegraph media url
58 | */
59 | export const uploadMedia = (buffData: Buffer, ext: string) =>
60 | new Promise((resolve, reject) => {
61 | const form = new FormData()
62 | form.append("file", buffData, `temp${generateRandString(16)}.${ext}`)
63 | fetcherPOST<{ src: string }[]>("https://telegra.ph/upload", form, {
64 | headers: {
65 | ...form.getHeaders()
66 | }
67 | })
68 | .then((data) => resolve("https://telegra.ph" + data[0].src))
69 | .catch(reject)
70 | })
71 |
--------------------------------------------------------------------------------
/src/utils/filesystem.ts:
--------------------------------------------------------------------------------
1 | import os from "os"
2 | import path from "path"
3 | import fs from "fs"
4 |
5 | /**
6 | * Generates random string
7 | * @param length the length of the string
8 | *
9 | * @returns the generated string
10 | */
11 | export const generateRandString = (length = 10) => {
12 | let text = ""
13 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
14 |
15 | for (let i = 0; i < length; i++) {
16 | text += possible.charAt(Math.floor(Math.random() * possible.length))
17 | }
18 | return text
19 | }
20 |
21 | /**
22 | * i just lazy to write force and recursive
23 | *
24 | * @param filename filepath
25 | *
26 | * @returns
27 | */
28 | export function removeTEMP(filename: string): void
29 | export function removeTEMP(filename: string, callback?: (err: Error | null) => void): void
30 | export function removeTEMP(filename: string, callback?: (err: Error | null) => void) {
31 | if (callback) return fs.rm(filename, { force: true, recursive: true }, callback)
32 |
33 | fs.rmSync(filename, { force: true, recursive: true })
34 | }
35 | /**
36 | * i just lazy to write force and recursive
37 | *
38 | * @param filename filepath
39 | *
40 | * @returns
41 | */
42 | export function removeTEMPAsync(filename: string) {
43 | return new Promise((resolve, reject) => removeTEMP(filename, (err) => (err ? reject(err) : resolve())))
44 | }
45 |
46 | /**
47 | * Saves buffer to temporary file
48 | *
49 | * @param data buffer data
50 | *
51 | * @param ext file ext?
52 | *
53 | * @returns temp filename
54 | */
55 | export function saveTEMP(data: Buffer, ext?: string): string
56 | export function saveTEMP(data: Buffer, ext?: string, callback?: (name: string, err: Error | null) => void): void
57 | export function saveTEMP(data: Buffer, ext?: string, callback?: (name: string, err: Error | null) => void) {
58 | const random = path.join(os.tmpdir(), generateRandString(17) + (ext ? `.${ext}` : ``))
59 |
60 | if (callback) return fs.writeFile(path.join(os.tmpdir(), generateRandString(17)), data, (err) => callback(random, err))
61 |
62 | fs.writeFileSync(path.join(os.tmpdir(), generateRandString(17)), data)
63 |
64 | return random
65 | }
66 | /**
67 | * Saves buffer to temporary file
68 | *
69 | * @param data buffer data
70 | *
71 | * @param ext file ext?
72 | *
73 | * @returns temp filename
74 | */
75 | export async function saveTEMPAsync(data: Buffer, ext?: string) {
76 | return new Promise((resolve, reject) => saveTEMP(data, ext, (name, err) => (err ? reject(err) : resolve(name))))
77 | }
78 |
--------------------------------------------------------------------------------
/src/utils/format.ts:
--------------------------------------------------------------------------------
1 | import Format from "@arugaz/formatter"
2 | import { parsePhoneNumber } from "awesome-phonenumber"
3 | import config from "../utils/config"
4 |
5 | /**
6 | * Format string to Upper Case, "test string" becomes "Test String"
7 | *
8 | * @param string String that you want to format
9 | *
10 | * @param split Split the string, default " "
11 | *
12 | * @param join Join the string, default " "
13 | *
14 | * @returns Formatted upper string
15 | *
16 | */
17 | export const upperFormat = (string: string, split = " ", join = " ") => {
18 | const chunks = string
19 | .split(split)
20 | .reduce((prev, curr) => (prev.charAt(0).toUpperCase() + prev.slice(1) + join + curr.charAt(0).toUpperCase() + curr.slice(1)).trim())
21 |
22 | return chunks
23 | }
24 |
25 | /**
26 | * Format string to Lower Case, "TEST STRING" becomes "tEST sTRING"
27 | *
28 | * @param string String that you want to format
29 | *
30 | * @param split Split the string, default " "
31 | *
32 | * @param join Join the string, default " "
33 | *
34 | * @returns Formatted upper string
35 | *
36 | */
37 | export const lowerFormat = (string: string, split = " ", join = " ") => {
38 | const chunks = string
39 | .split(split)
40 | .reduce((prev, curr) => (prev.charAt(0).toLowerCase() + prev.slice(1) + join + curr.charAt(0).toLowerCase() + curr.slice(1)).trim())
41 |
42 | return chunks
43 | }
44 |
45 | /**
46 | * Format string to segment of N char string, "teststring" becomes "t e s t s t r i n g"
47 | *
48 | * @param string String that you want to format
49 | *
50 | * @param count N char count, default 1
51 | *
52 | * @param join Join the string, default " "
53 | *
54 | * @returns Formatted upper string
55 | *
56 | */
57 | export const segmentCharFormat = (string: string, count = 1, join = " ") => {
58 | const chunks = []
59 | if (count <= 0) count = 1
60 |
61 | for (let i = 0, charsLength = string.length; i < charsLength; i += count) {
62 | chunks.push(string.substring(i, i + count))
63 | }
64 | return chunks.join(join)
65 | }
66 |
67 | /**
68 | * Format string to segment of N word string, "te st st ri ng" becomes "te st\nst ri\nng"
69 | *
70 | * @param string String that you want to format
71 | *
72 | * @param count N word count, default 2
73 | *
74 | * @param split Split the string, default " "
75 | *
76 | * @param join Join the string, default "\n"
77 | *
78 | * @returns Formatted upper string
79 | */
80 | export const segmentWordFormat = (string: string, count = 2, split = " ", join = "\n") => {
81 | const strings = string.split(split)
82 | const chunks = []
83 |
84 | while (strings.length) {
85 | chunks.push(strings.splice(0, count).join(" "))
86 | }
87 |
88 | return chunks.join(join)
89 | }
90 |
91 | const sf = Format.sizeFormatter()
92 | /**
93 | * Format number to Size KB/MB/GB...
94 | *
95 | * @param number Number that you want to format
96 | *
97 | * @returns string Formatted Size
98 | */
99 | export const sizeFormat = (number: number) => {
100 | return sf(number)
101 | }
102 |
103 | const tf = Format.durationFormatter()
104 | /**
105 | * Format number to Time 1s,2h,3d...
106 | * @param number Number that you want to format
107 | *
108 | * @returns Formatted Time
109 | */
110 | export const timeFormat = (number: number) => {
111 | return tf(number)
112 | }
113 |
114 | /**
115 | * Format phone number
116 | *
117 | * @param number Number that you want to format
118 | *
119 | * @return "{@link PhoneFormat}"
120 | */
121 | export const phoneFormat = (number: string) => {
122 | const chunks = parsePhoneNumber(`+${number.replace(/\D+/g, "")}`)
123 |
124 | return {
125 | countryCode: `${chunks.countryCode}`,
126 | regionCode: `${chunks.regionCode}`.toLowerCase(),
127 | international: `${chunks.number.international}`
128 | } as PhoneFormat
129 | }
130 |
131 | const rtf = new Intl.RelativeTimeFormat(config.language, { numeric: "auto" })
132 | /**
133 | * It is the caller's responsibility to handle cut-off logic
134 | * such as deciding between displaying "in 7 days" or "in 1 week".
135 | * This API does not support relative dates involving compound units.
136 | * e.g "in 5 days and 4 hours".
137 | *
138 | * @param value - Numeric value to use in the internationalized relative time message
139 | *
140 | * @param unit - [Unit](https://tc39.es/ecma402/#sec-singularrelativetimeunit) to use in the relative time internationalized message.
141 | *
142 | * @return Internationalized relative time message as string
143 | *
144 | * [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/format).
145 | */
146 | export const rtfFormat = (value: number, unit: Intl.RelativeTimeFormatUnit) => {
147 | return rtf.format(value, unit)
148 | }
149 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "checkJs": false,
5 | "esModuleInterop": true,
6 | "experimentalDecorators": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "lib": ["ES2020", "ESNext.Array", "DOM"],
9 | "module": "CommonJS",
10 | "moduleResolution": "node",
11 | "noImplicitThis": true,
12 | "outDir": "dist",
13 | "removeComments": true,
14 | "resolveJsonModule": true,
15 | "rootDir": "src",
16 | "skipLibCheck": true,
17 | "target": "ES2020"
18 | },
19 | "exclude": ["node_modules"],
20 | "include": ["./src/**/*.ts"]
21 | }
22 |
--------------------------------------------------------------------------------