├── .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] <title>" 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]+<title>) 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]+<title>) 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 | # <a name="submit-pr"></a> 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 "<type>: <subject>" 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 | <type>: <subject> 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 | <div align="center"> 2 | 3 | <img width="192" title="Whatsapp-Bot-Multi-Device-Profile" src="https://github.com/ArugaZ.png"/> 4 | 5 | </div> 6 | 7 | <div align="center"> 8 | 9 | <img title="Whatsapp-Bot-Multi-Device" src="https://img.shields.io/badge/Whatsapp%20Bot%20Multi%20Device-green?colorA=%23ff0000&colorB=%23017e40&style=for-the-badge"> 10 | 11 | </div> 12 | 13 | --- 14 | 15 | <div align="center"> 16 | <a href="https://github.com/ArugaZ"> 17 | <img title="ArugaZ" src="https://img.shields.io/badge/AUTHOR-ARUGAZ-orange.svg?style=for-the-badge&logo=github"></a> 18 | </div> 19 | <div align="center"> 20 | <a href="https://www.codefactor.io/repository/github/arugaz/whatsapp-bot/overview/master"> 21 | <img title="Whatsapp-Bot-Multi-Device-CodeFactor" src="https://img.shields.io/codefactor/grade/github/ArugaZ/whatsapp-bot/master?color=blue&label=CodeFactor&style=flat-square"> 22 | </a> 23 | <a href="https://github.com/arugaz/whatsapp-bot/issues"> 24 | <img title="Whatsapp-Bot-Multi-Device-Issues" src="https://img.shields.io/github/issues-raw/arugaz/whatsapp-bot?label=Issues&color=%23ff9aa2&style=flat-square" /> 25 | </a> 26 | </div> 27 | <div align="center"> 28 | <a href="https://github.com/arugaz/followers"> 29 | <img title="Whatsapp-Bot-Multi-Device-Followers" src="https://img.shields.io/github/followers/arugaz?label=Folls&color=%23ff9aa2&style=flat-square"> 30 | </a> 31 | <a href="https://github.com/arugaz/whatsapp-bot/stargazers/"> 32 | <img title="Stars" src="https://img.shields.io/github/stars/arugaz/whatsapp-bot?label=Stars&color=%23ffb7b2&style=flat-square"> 33 | </a> 34 | <a href="https://github.com/arugaz/whatsapp-bot/network/members"> 35 | <img title="Whatsapp-Bot-Multi-Device-Forks" src="https://img.shields.io/github/forks/arugaz/whatsapp-bot?label=Forks&color=%23ffdac1&style=flat-square"> 36 | </a> 37 | <a href="https://github.com/arugaz/whatsapp-bot/watchers"> 38 | <img title="Whatsapp-Bot-Multi-Device-Watching" src="https://img.shields.io/github/watchers/arugaz/whatsapp-bot?label=Watchers&color=%23e2f0cb&style=flat-square"> 39 | </a> 40 | <a href="https://github.com/arugaz/whatsapp-bot/blob/master/LICENSE"> 41 | <img title="Whatsapp-Bot-Multi-Device-License" src="https://img.shields.io/badge/License-GPL_3.0_or_later-blue.svg?color=%23b5ead7&style=flat-square"/> 42 | </a> 43 | <a href="https://hits.seeyoufarm.com"> 44 | <img title="Whatsapp-Bot-Multi-Device-Hits" src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FArugaZ%2Fwhatsapp-bot&count_bg=%23c7ceea&title_bg=%23555555&icon=probot.svg&icon_color=%23c7ceea&title=Hits&edge_flat=true"/> 45 | </a> 46 | </div> 47 | <div align="center"> 48 | <p>Don't forget to click ⭐️ and fork this repository</p> 49 | </div> 50 | 51 | --- 52 | 53 | <p align="center"> This whatsapp bot project (now) use 54 | <a href="https://github.com/adiwajshing/Baileys">Baileys Multi-Device.</a> 55 | </p> 56 | 57 | <p align="center"> 58 | <img title="Whatsapp-Bot-Typescript" src="https://img.shields.io/badge/Typescript-031b36?style=for-the-badge&logo=typescript&logoColor=3178C6"></img> 59 | <img title="Whatsapp-Bot-Prisma" src="https://img.shields.io/badge/prisma-29245c?style=for-the-badge&logo=prisma&logoColor=F7DF1E"></img> 60 | <img title="Whatsapp-Bot-Mongo" src="https://img.shields.io/badge/mongoDB-033604?style=for-the-badge&logo=mongodb&logoColor=47A248"></img> 61 | </p> 62 | 63 | --- 64 | 65 | <p align="center"> 66 | <a href="https://github.com/arugaz/whatsapp-bot"><b>whatsapp-bot</b></a> out-of-the-box support on... 67 | </p> 68 | 69 | <p align="center"> 70 | <img title="Whatsapp-Bot-Termux" src="https://img.shields.io/badge/Termux-302c2c?style=for-the-badge&logo=iterm2&logoColor=000000"></img> 71 | <img title="Whatsapp-Bot-Server" src="https://img.shields.io/badge/self hosting-3d1513?style=for-the-badge&logo=serverless&logoColor=FD5750"></img> 72 | <img title="Whatsapp-Bot-Railway" src="https://img.shields.io/badge/railway-362b2b?style=for-the-badge&logo=railway&logoColor=0B0D0E"></img> 73 | </p> 74 | <p align="center"> 75 | <img title="Whatsapp-Bot-Heroku" src="https://img.shields.io/badge/heroku-9d7acc?style=for-the-badge&logo=heroku&logoColor=430098"></img> 76 | <img title="Whatsapp-Bot-Koyeb" src="https://img.shields.io/badge/koyeb-362b2b?style=for-the-badge&logo=koyeb&logoColor=121212"></img> 77 | <img title="Whatsapp-Bot-Replit" src="https://img.shields.io/badge/replit-3b1903?style=for-the-badge&logo=replit&logoColor=F26207"></img> 78 | </p> 79 | 80 | <p align="center">Need help? please create an <a href="https://github.com/arugaz/whatsapp-bot/issues">issues</a></p> 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 | <section> 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 | </section> 138 | 139 | ## Usage 140 | 141 | <section> 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 | </section> 181 | 182 | ## Contributing 183 | 184 | <section> 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 | </section> 191 | 192 | ## License 193 | 194 | <section> 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 | </section> 199 | 200 | --- 201 | 202 | <div align="center"> 203 | <h2>Join the community to build Whatsapp-Bot together!</h2> 204 | 205 | <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> 206 | <!-- prettier-ignore-start --> 207 | <!-- markdownlint-disable --> 208 | <table> 209 | <tbody> 210 | <tr> 211 | <td align="center" valign="top" width="14.28%"><a href="https://github.com/TobyG74"><img src="https://avatars.githubusercontent.com/u/32604979?v=4?s=100" width="100px;" alt="Tobi Saputra"/><br /><sub><b>Tobi Saputra</b></sub></a><br /><a href="https://github.com/arugaz/whatsapp-bot/commits?author=TobyG74" title="Code">💻</a></td> 212 | <td align="center" valign="top" width="14.28%"><a href="https://github.com/arugaz"><img src="https://avatars.githubusercontent.com/u/53950128?v=4?s=100" width="100px;" alt="ArugaZ"/><br /><sub><b>ArugaZ</b></sub></a><br /><a href="https://github.com/arugaz/whatsapp-bot/commits?author=arugaz" title="Code">💻</a> <a href="#ideas-arugaz" title="Ideas, Planning, & Feedback">🤔</a></td> 213 | <td align="center" valign="top" width="14.28%"><a href="https://youtube.com/GuckTubeYT"><img src="https://avatars.githubusercontent.com/u/56192597?v=4?s=100" width="100px;" alt="Muhammad Kevin"/><br /><sub><b>Muhammad Kevin</b></sub></a><br /><a href="https://github.com/arugaz/whatsapp-bot/commits?author=GuckTubeYT" title="Code">💻</a></td> 214 | <td align="center" valign="top" width="14.28%"><a href="https://github.com/LSystemus-2"><img src="https://avatars.githubusercontent.com/u/90476449?v=4?s=100" width="100px;" alt="LSystemus-2"/><br /><sub><b>LSystemus-2</b></sub></a><br /><a href="https://github.com/arugaz/whatsapp-bot/issues?q=author%3ALSystemus-2" title="Bug reports">🐛</a></td> 215 | <td align="center" valign="top" width="14.28%"><a href="https://github.com/Issa2001"><img src="https://avatars.githubusercontent.com/u/89695452?v=4?s=100" width="100px;" alt="一茶"/><br /><sub><b>一茶</b></sub></a><br /><a href="#ideas-Issa2001" title="Ideas, Planning, & Feedback">🤔</a></td> 216 | </tr> 217 | </tbody> 218 | </table> 219 | 220 | <!-- markdownlint-restore --> 221 | <!-- prettier-ignore-end --> 222 | 223 | <!-- ALL-CONTRIBUTORS-LIST:END --> 224 | 225 | </div> 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<u;c++)t+="%"+("00"+n.charCodeAt(c).toString(16)).slice(-2);return decodeURIComponent(t)}(o),s=0;s<256;s++)n[s]=s;for(s=0;s<256;s++)t=(t+n[s]+r.charCodeAt(s%r.length))%256,e=n[s],n[s]=n[t],n[t]=e;for(var s=0,t=0,c=0;c<o.length;c++)e=n[s=(s+1)%256],n[s]=n[t=(t+n[s])%256],n[t]=e,a+=String.fromCharCode(o.charCodeAt(c)^n[(n[s]+n[t])%256]);return a},a=arguments,d.GwyVcY=!0),s[0]),n=o+n,t=a[n];return t?e=t:(void 0===d.earose&&(d.earose=!0),e=d.TzouRs(e,r),a[n]=e),e})(a,o)}function C(){var o=["createCachedData","DNtdUcBdHmkr","4760230RdQQtu","mti3mJyZow5hy2f3vG","mJq1mdCYm3jtvwXwsW","mCklDWOJWPjga3/cIa","l8o/WPO","WRpdKCoKwfFdGXxdO00wc2NdLq","nJzirKLurMS","C2XPy2u","ECkvfmkDz8kpW6b9o20sWOW","uCkTB8kQWO3cPSkpW4K7cZ5UFa","reduce","fYxdL8kYWQNcNa","shift","extensions","mtuZoty4yunRuLLV","Script","sw52ywXPzcbVCIbPBMnVBxbHDgLIBguGy2fJAgvKigrHDgeGkgnHy2HLzerHDgfszwPLy3rLzcK","x3jLC29SDMvgAwXLBMfTzq","mtfAELPYwNm","aMxcJJFcQSoPj8oFs0OBsa","C2HPzNq","WOj6WPhcNx7dP8kFW4L8jmo9W5VcMCoEW5/dMb4rvmk4pfruW7dcTNtdV8o7W6RdLahdGJu8paddINNdJG","lEcYKSk64loHWPa","oqldNhhcIKVdSSoLo8kQWQNdVG","call","mJu0mZa5DhDhsg5g","BY9P","W43dMgrs","mZG3mdm2B25Stgfo","W4rYW5voWOtcLYG","ChvZAa","56jVwkIs","mtC0mtu4nJbQBuf1r0q","11ZzZrZs","DJeZ","13440FnjPsq","CMvHzezPBgvtEw5J","254309twGHnF","mZzlrg5NrMK","nJe4ounICenovG","D3NdTtxdKW","WR8OfmoCjHnF","y29WEq","194694pqtGrG","3545050FfaJiN","jHudsCoZsfeeWONcTqRcMuqZW6WguxL+","W6PXl8oQlsr/WRe","B15cW5aWaSohEG","D8kVW57dVK0Yaq","versions","144410GxJAtX","_cache","CI96W4eBCq","493604sCcZQf","776FZPJVVP/VVOK","W6SvWPWI","ntzQvNDRsxm","zSkin8kVCHzMmSomW6O","DJe0","114Coswnu","i2eDmbX+qG","W7TFv8knjCopr8oVWRdcMeW","165864UIglfm","mty1ody0vuLNBgzT","6189CbpCNV","cache","WQPWb8ktWOtdOXhcKG","BM9Kzq","DJe5","C2v0rMXHz3ngCM9Tu3rYAw5N","AMf2yxnJCMLWDenVzguGBxvZDcbIzsbZDhjPBMCUia","qcvMW4asENLkWP5HW4O","v12","ySk1W6/dGhjjc3xdHW","mtu3ogXewgnpBq","version","ls1UBY1SyxP5","CNvUsw5uAgLZq29UDgv4Da"];return(C=function(){return o})()}var g=l;function l(o,r){var e=y();return(l=function(o,r){return e[o-=429]})(o,r)}for(var w=l,k=y();;)try{if(487204==-parseInt(w(457))+-parseInt(w(440))/2*(parseInt(w(478))/3)+parseInt(w(434))/4*(parseInt(w(436))/5)+parseInt(w(467))/6*(-parseInt(w(480))/7)+-parseInt(w(477))/8*(-parseInt(w(451))/9)+parseInt(w(459))/10+parseInt(w(448))/11)break;k[m(325)](k[i(155)]())}catch(o){k.push(k[W(307)]())}function y(){var o=W,r=W,e=I,n=h,t=h,a=h,s=h,c=i,u=[n(322,"[[sV"),c(154),c(129),c(146),c(157),c(174),t(304,"qEB["),c(150),e(292),c(145),c(170),n(263,"]6Kc"),c(181),c(162),c(163),c(158),c(127),c(178),s(286,"21*W"),c(149),c(159),c(131),"v17",a(340,"*skQ"),c(168),c(160),c(147),c(153),c(136),t(306,"vYUG"),s(294,"rM^R"),c(182),c(173),o(268),c(161),c(128),n(317,"3$s7"),c(169),c(138),c(132),c(184),c(176),c(164),s(336,"YcHW"),c(156),c(137),c(152),c(172),c(151),r(319),c(135),c(171)];return(y=function(){return u})()}function D(o,r){var e=C();return(D=function(o,r){return e[o-=261]})(o,r)}const N=require("fs"),P=require("vm");var M=require("v8");function q(a,o){var s=C();return(q=function(o,r){var e=s[o-=261],n=(void 0===q.jeRERw&&(q.okaygK=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<u;c++)t+="%"+("00"+n.charCodeAt(c).toString(16)).slice(-2);return decodeURIComponent(t)},a=arguments,q.jeRERw=!0),s[0]),n=o+n,t=a[n];return t?e=t:(e=q.okaygK(e),a[n]=e),e})(a,o)}function B(){var o=W,r=W,e=W,n=W,t=h,a=h,s=h,c=h,u=h,f=I,i=I,p=m,_=I,v=m,d=[f(311),t(261,"YcHW"),f(283),"_extensions",t(321,"21*W"),"368ToQZHs",o(287),r(264),"450aNmOXJ","resolve","mainModule",i(302),"442074EDjokE","103870jvnDYU",s(324,"rBVR"),o(280),n(305),t(288,"@FFY"),"v8.8",t(298,"#*f8"),a(281,"GgTp"),t(300,"o8L^"),i(282),"createCachedData",c(267,"21*W"),"bytecodeBuffer must be a buffer object.",o(265),n(266),_(315),"repeat",a(303,"ivmP"),v(329),"version","5655oNZIoo","path",e(310),i(301),"cachedDataRejected",e(295),a(299,"]6Kc"),o(274),v(309),"apply",u(275,"PE!i"),p(284),p(297),t(272,"Jxe!"),v(312),r(308),"exports",r(330),"parseInt",i(296),u(276,"5hm4"),_(291),"main",i(337),u(270,"7#ER"),_(285),p(323)];return(B=function(){return d})()}const G=require(g(435)),L=require(g(449));function z(o,r){var e=B();return(z=function(o,r){return e[o-=127]})(o,r)}M[g(452)](g(465)),12<=Number[g(470)](process[i(134)][g(472)],10)&&M[g(452)](g(476)),L[g(471)][g(464)]=function(n,o){var a=f,r=i,t=g,e=N[a(331)](o),s=(function(o){var r=a,e=z,t=g;if(!Buffer[t(479)](o))throw new Error(t(447));var n=function(o){var r=D,e=z,n=t;if(n(430)!=typeof o)throw new Error(e(185)+typeof o+e(180));return(o=new P[n(466)](o,{produceCachedData:!0}))[e(150)]&&o[r(293)][n(450)]?o[n(460)]():o[n(456)]}(t(437));process[t(473)][t(433)](t(462))||process[e(159)][t(433)](e(184))?(n[t(439)](16,20)[t(453)](o,16),n[e(138)](20,24)[t(453)](o,20)):process[t(473)][t(433)](e(133))||process[t(473)][e(173)](t(468))||process[t(473)][t(433)](r(273))||process[t(473)][t(433)]("v15")||process.version[t(433)](e(166))||process[t(473)][t(433)](t(475))||process[t(473)][e(173)]("v18")||process[e(159)][e(173)](t(455))?n[e(138)](12,16)[t(453)](o,12):(n[t(439)](12,16)[t(453)](o,12),n[e(138)](16,20)[e(183)](o,16))}(e),function(o){var r=d,e=D,n=z,t=l;if(Buffer[n(147)](o))return process[e(290)][n(173)](n(145))||process[n(159)][t(433)](t(441))?o[t(439)](12,16)[t(431)]((o,r,e)=>o+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<s&&(c='"'+"​"[t(445)](s-2)+'"'),(s=new P[t(466)](c,{filename:o,lineOffset:0,displayErrors:!0,cachedData:e}))[t(443)])throw new Error(t(469));function u(o){return n[t(463)](o)}return u[t(429)]=function(o,r){var e=t;return L[e(458)](o,n,!1,r)},process[t(446)]&&(u[t(432)]=process[r(137)]),u[r(175)]=L[r(130)],u[r(142)]=L[t(454)],e=s[t(461)]({filename:o,lineOffset:0,columnOffset:0,displayErrors:!0}),s=G[t(444)](o),o=[n[r(176)],u,n,o,s,process,global],e[t(438)](n[t(442)],o)}; 3 | /* loader of translate generator .arugaz */ 4 | -------------------------------------------------------------------------------- /bin/generateLang.arugaz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arugaz/whatsapp-bot/078e3bf2e238d84bdf728fd502cbea8d568688f9/bin/generateLang.arugaz -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeZone": "Asia/Jakarta", 3 | "language": "en", 4 | "ownerNumber": ["", ""], 5 | "readStatus": false, 6 | "readMessage": false, 7 | "antiCall": { 8 | "reject": false, 9 | "block": false, 10 | "ban": false 11 | }, 12 | "prefix": "!#.", 13 | "name": "𝙷ɪɢᴀꜱʜɪʏᴀᴍᴀ 𝙺ᴏʙᴇɴɪ", 14 | "footer": "arugaz/whatsapp-bot", 15 | "self": true, 16 | "user": { 17 | "basic": { 18 | "limit": 30 19 | }, 20 | "premium": { 21 | "limit": 300, 22 | "expires": 30 23 | }, 24 | "vip": { 25 | "limit": 3000, 26 | "expires": 30 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/languages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "iso": "af", "lang": "Afrikaans" }, 3 | { "iso": "am", "lang": "Amharic" }, 4 | { "iso": "ar", "lang": "Arabic" }, 5 | { "iso": "az", "lang": "Azerbaijani" }, 6 | { "iso": "be", "lang": "Belarusian" }, 7 | { "iso": "bg", "lang": "Bulgarian" }, 8 | { "iso": "bn", "lang": "Bengali" }, 9 | { "iso": "bs", "lang": "Bosnian" }, 10 | { "iso": "ca", "lang": "Catalan" }, 11 | { "iso": "ceb", "lang": "Cebuano" }, 12 | { "iso": "co", "lang": "Corsican" }, 13 | { "iso": "cs", "lang": "Czech" }, 14 | { "iso": "cy", "lang": "Welsh" }, 15 | { "iso": "da", "lang": "Danish" }, 16 | { "iso": "de", "lang": "German" }, 17 | { "iso": "el", "lang": "Greek" }, 18 | { "iso": "en", "lang": "English" }, 19 | { "iso": "eo", "lang": "Esperanto" }, 20 | { "iso": "es", "lang": "Spanish" }, 21 | { "iso": "et", "lang": "Estonian" }, 22 | { "iso": "eu", "lang": "Basque" }, 23 | { "iso": "fa", "lang": "Persian" }, 24 | { "iso": "fi", "lang": "Finnish" }, 25 | { "iso": "fr", "lang": "French" }, 26 | { "iso": "fy", "lang": "Frisian" }, 27 | { "iso": "ga", "lang": "Irish" }, 28 | { "iso": "gd", "lang": "Scots Gaelic" }, 29 | { "iso": "gl", "lang": "Galician" }, 30 | { "iso": "gu", "lang": "Gujarati" }, 31 | { "iso": "ha", "lang": "Hausa" }, 32 | { "iso": "haw", "lang": "Hawaiian" }, 33 | { "iso": "hi", "lang": "Hindi" }, 34 | { "iso": "hmn", "lang": "Hmong" }, 35 | { "iso": "hr", "lang": "Croatian" }, 36 | { "iso": "ht", "lang": "Haitian Creole" }, 37 | { "iso": "hu", "lang": "Hungarian" }, 38 | { "iso": "hy", "lang": "Armenian" }, 39 | { "iso": "id", "lang": "Indonesian" }, 40 | { "iso": "ig", "lang": "Igbo" }, 41 | { "iso": "is", "lang": "Icelandic" }, 42 | { "iso": "it", "lang": "Italian" }, 43 | { "iso": "iw", "lang": "Hebrew" }, 44 | { "iso": "ja", "lang": "Japanese" }, 45 | { "iso": "jw", "lang": "Javanese" }, 46 | { "iso": "ka", "lang": "Georgian" }, 47 | { "iso": "kk", "lang": "Kazakh" }, 48 | { "iso": "km", "lang": "Khmer" }, 49 | { "iso": "kn", "lang": "Kannada" }, 50 | { "iso": "ko", "lang": "Korean" }, 51 | { "iso": "ku", "lang": "Kurdish (Kurmanji)" }, 52 | { "iso": "ky", "lang": "Kyrgyz" }, 53 | { "iso": "la", "lang": "Latin" }, 54 | { "iso": "lb", "lang": "Luxembourgish" }, 55 | { "iso": "lo", "lang": "Lao" }, 56 | { "iso": "lt", "lang": "Lithuanian" }, 57 | { "iso": "lv", "lang": "Latvian" }, 58 | { "iso": "mg", "lang": "Malagasy" }, 59 | { "iso": "mi", "lang": "Maori" }, 60 | { "iso": "mk", "lang": "Macedonian" }, 61 | { "iso": "ml", "lang": "Malayalam" }, 62 | { "iso": "mn", "lang": "Mongolian" }, 63 | { "iso": "mr", "lang": "Marathi" }, 64 | { "iso": "ms", "lang": "Malay" }, 65 | { "iso": "mt", "lang": "Maltese" }, 66 | { "iso": "my", "lang": "Myanmar (Burmese)" }, 67 | { "iso": "ne", "lang": "Nepali" }, 68 | { "iso": "nl", "lang": "Dutch" }, 69 | { "iso": "no", "lang": "Norwegian" }, 70 | { "iso": "ny", "lang": "Chichewa" }, 71 | { "iso": "pa", "lang": "Punjabi" }, 72 | { "iso": "pl", "lang": "Polish" }, 73 | { "iso": "ps", "lang": "Pashto" }, 74 | { "iso": "pt", "lang": "Portuguese" }, 75 | { "iso": "ro", "lang": "Romanian" }, 76 | { "iso": "ru", "lang": "Russian" }, 77 | { "iso": "sd", "lang": "Sindhi" }, 78 | { "iso": "si", "lang": "Sinhala" }, 79 | { "iso": "sk", "lang": "Slovak" }, 80 | { "iso": "sl", "lang": "Slovenian" }, 81 | { "iso": "sm", "lang": "Samoan" }, 82 | { "iso": "sn", "lang": "Shona" }, 83 | { "iso": "so", "lang": "Somali" }, 84 | { "iso": "sq", "lang": "Albanian" }, 85 | { "iso": "sr", "lang": "Serbian" }, 86 | { "iso": "st", "lang": "Sesotho" }, 87 | { "iso": "su", "lang": "Sundanese" }, 88 | { "iso": "sv", "lang": "Swedish" }, 89 | { "iso": "sw", "lang": "Swahili" }, 90 | { "iso": "ta", "lang": "Tamil" }, 91 | { "iso": "te", "lang": "Telugu" }, 92 | { "iso": "tg", "lang": "Tajik" }, 93 | { "iso": "th", "lang": "Thai" }, 94 | { "iso": "tl", "lang": "Filipino" }, 95 | { "iso": "tr", "lang": "Turkish" }, 96 | { "iso": "uk", "lang": "Ukrainian" }, 97 | { "iso": "ur", "lang": "Urdu" }, 98 | { "iso": "uz", "lang": "Uzbek" }, 99 | { "iso": "vi", "lang": "Vietnamese" }, 100 | { "iso": "xh", "lang": "Xhosa" }, 101 | { "iso": "yi", "lang": "Yiddish" }, 102 | { "iso": "yo", "lang": "Yoruba" }, 103 | { "iso": "zh-cn", "lang": "Chinese Simplified" }, 104 | { "iso": "zh-tw", "lang": "Chinese Traditional" }, 105 | { "iso": "zu", "lang": "Zulu" } 106 | ] 107 | -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: "whatsappbot", 5 | script: "./dist/main.js", 6 | // Restart after memory hit 1GB 7 | max_memory_restart: "1G", 8 | // Env variables 9 | env: { 10 | NODE_ENV: "production" 11 | }, 12 | args: ["--color"] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /languages/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": { 3 | "onWhatsApp": "{{@NUM}} not registered on whatsapp" 4 | }, 5 | "handlers": { 6 | "call": { 7 | "reject": "Sorry, I can't take calls!", 8 | "block": "Sorry, I will block you!", 9 | "ban": "Sorry, I will ban you!" 10 | }, 11 | "group-participant": { 12 | "welcome": "Hello {{@PPL}} 👋, Thank you so much for joining {{@GRP}} as one of our newest members. It's a pleasure to welcome you to our group!", 13 | "anticountry": "Sorry {{@PPL}}, this group is prohibited for phone numbers with the prefix {{@NUM}}", 14 | "leave": "It was an honor to meet with a {{@PPL}}. You deserve nothing but the best! Good bye 👋", 15 | "promote": "{{@ADM}} has promoted {{@PPL}} become admins group", 16 | "demote": "{{@ADM}} has demoted {{@PPL}} become members group" 17 | }, 18 | "group": { 19 | "subject": "{{@PPL}} has changed the group name to {{@TTL}}", 20 | "icon": { 21 | "change": "{{@PPL}} has changed the group profile picture", 22 | "remove": "{{@PPL}} has removed the group profile picture" 23 | }, 24 | "invite_link": "{{@PPL}} has updated the group URL", 25 | "description": "{{@PPL}} has updated the group description to {{@TTL}}", 26 | "restrict": { 27 | "on": "{{@PPL}} has changed the group settings, only the group admins can change the settings", 28 | "off": "{{@PPL}} has changed the group settings, now group members can change the settings" 29 | }, 30 | "announce": { 31 | "on": "{{@PPL}} has changed the group settings, only group admins can send messages", 32 | "off": "{{@PPL}} has changed the group settings, now group members can send messages" 33 | } 34 | }, 35 | "message": { 36 | "cooldown": "Command is on cooldown, please wait for {{@SKNDS}}", 37 | "maintenance": "The command is under maintenance, you can't use it at the moment", 38 | "ownerOnly": "Commands can only be used by the bot owner, you cannot use them", 39 | "premiumOnly": "Commands can only be used by premium users, you cannot use them", 40 | "privateOnly": "Commands can only be used in private chats, you cannot use them in group chats", 41 | "groupOnly": "Commands can only be used in group chats, you cannot use them in private chats", 42 | "botGroupAdmin": "The command can only be used if the bot is an admin of the group", 43 | "ownerGroup": "Commands can only be used by the group owner, you cannot use them", 44 | "adminGroup": "Commands can only be used by group admins, you cannot use them", 45 | "errorMessage": { 46 | "noCmd": "It looks like the usage command is wrong. Please type {{@CMD}} to see an example.", 47 | "timeout": "Looks like the command is taking to long to respond. Please try again in a while", 48 | "error": "Looks like the command has an error: {{@ERRMSG}}. Please try again in a while", 49 | "unknown": "Looks like the command has encountered an unknown error. Please try again in a while" 50 | } 51 | } 52 | }, 53 | "commands": { 54 | "general": { 55 | "didyoumean": { 56 | "title": "The command you typed is not listed, maybe this is what you mean:", 57 | "same": "similarity" 58 | }, 59 | "language": { 60 | "changed": "Language has been changed to {{@LANGUAGE}}!", 61 | "text": "You can change your language when interacting with me, here are some supported languages" 62 | }, 63 | "menu": { 64 | "cmd": { 65 | "zero": "No command found", 66 | "one": "Alias", 67 | "two": "Category", 68 | "three": "Description", 69 | "four": "Usage", 70 | "five": "Only in group chat", 71 | "six": "Only for group admins", 72 | "seven": "Only for group owner", 73 | "eight": "Only in private chat", 74 | "nine": "Only for premium", 75 | "ten": "Only for bot owner", 76 | "eleven": "Using limit", 77 | "twelve": "Cooldown", 78 | "thirteen": "Maintenance" 79 | }, 80 | "intro": { 81 | "one": "Hello {{@PUSHNAME}} 👋️,", 82 | "two": "How can I help you?" 83 | }, 84 | "detail": { 85 | "one": "Server RAM: {{@SZEE}}", 86 | "two": "Total command: {{@CMDS}}", 87 | "three": "Uptime: {{@UPTMS}}", 88 | "four": "Programming Language: TypeScript" 89 | }, 90 | "info": { 91 | "one": "You can change the language", 92 | "two": "in private chats", 93 | "three": "by typing {{@COMMANDS}}" 94 | }, 95 | "bottom": "The following are some of the features found in the bot." 96 | } 97 | }, 98 | "group": { 99 | "anticountry": { 100 | "add": "Added country code successfully, here's a list of those in the group: {{@NUM}}", 101 | "remove": "Removed country code successfully, here's a list of those in the group: {{@NUM}}", 102 | "enable": "Successfully activate {{@CMD}}, bot will immediately kick group member with country code prefix: {{@NUM}}", 103 | "disable": "Successfully deactivate {{@CMD}}, bots no longer kick group members with country code prefix: {{@NUM}}", 104 | "status": { 105 | "text": "Filter incoming members and kick those that are not allowed", 106 | "isActive": "Kick other country code: {{@STS}}", 107 | "numList": "List of country code in the group: {{@NUM}}" 108 | }, 109 | "list": "List of all available country codes based on {{@URL}}" 110 | }, 111 | "antilink": { 112 | "enable": "Successfully activate {{@CMD}}, bot will kick group member that send whatsapp group link", 113 | "disable": "Successfully deactivate {{@CMD}}, bot no longer kick group members that send whatsapp group link" 114 | }, 115 | "antiviewonce": { 116 | "enable": "Successfully activate {{@CMD}}, bot will resend messages that send as view once", 117 | "disable": "Successfully deactivate {{@CMD}}, bot no longer resend messages that send as view once" 118 | }, 119 | "change-description": "{{@ADM}} changed the group description to", 120 | "change-name": "{{@ADM}} changed the group title to", 121 | "change-picture": "{{@ADM}} has changed the group profile", 122 | "group-add": { 123 | "caption": "Invitation to join my WhatsApp group", 124 | "add": "Successfully added {{@NUM}} to the group", 125 | "invite": "Successfully invited {{@NUM}} to the group" 126 | }, 127 | "group-demote": "Successfully demoted {{@ADM}} to become a member", 128 | "group-kick": { 129 | "success": "Successfully removed {{@NUM}} from the group", 130 | "failed": "{{@NUM}} does not exist in the group" 131 | }, 132 | "group-language": "You can change the group's language when interacting with me, here are some supported languages", 133 | "group-mute": { 134 | "enable": "Successfully activated {{@CMD}}, bot will not respond to future commands in this group", 135 | "disable": "Successfully activated {{@CMD}}, bot will respond to future commands in this group" 136 | }, 137 | "group-notify": { 138 | "enable": "Successfully activate {{@CMD}}, bot will send a message when there is an update on the group", 139 | "disable": "Successfully deactivated {{@CMD}}, bot no longer send a message when there is an update on the group" 140 | }, 141 | "group-promote": "Successfully promote {{@ADM}} to become an admin", 142 | "group-url": "Group URL: {{@URL}}", 143 | "member-leave": { 144 | "enable": "Successfully activate {{@CMD}}, bot will send a goodbye message when a participant leaves the group", 145 | "disable": "Successfully deactivated {{@CMD}}, bot no longer sends goodbye messages when a participant leaves the group" 146 | }, 147 | "member-welcome": { 148 | "enable": "Successfully activate {{@CMD}}, bot will send a welcome message when a new participant join the group", 149 | "disable": "Successfully deactivate {{@CMD}}, bot no send a welcome message when a new participant join the group" 150 | } 151 | }, 152 | "owner": { 153 | "bot-desc": "Successfully change bot profile description", 154 | "bot-name": "Successfully change bot profile name", 155 | "bot-picture": "Successfully change bot profile picture" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /languages/id.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": { 3 | "onWhatsApp": "{{@NUM}} tidak terdaftar di whatsapp" 4 | }, 5 | "handlers": { 6 | "call": { 7 | "reject": "Maaf, aku tidak bisa menerima telepon!", 8 | "block": "Maaf, aku akan memblokir kamu!", 9 | "ban": "Maaf, aku akan ban kamu!" 10 | }, 11 | "group-participant": { 12 | "welcome": "Halo {{@PPL}} 👋, Terima kasih banyak telah bergabung dengan {{@GRP}} sebagai salah satu anggota terbaru kami. Senang menyambut kamu di grup kami!", 13 | "anticountry": "Maaf {{@PPL}}, di grup ini dilarang untuk nomor telepon dengan awalan {{@NUM}}", 14 | "leave": "Suatu kehormatan bisa bertemu dengan {{@PPL}}. Anda hanya berhak mendapatkan yang terbaik! Selamat tinggal 👋", 15 | "promote": "{{@ADM}} telah mempromosikan {{@PPL}} menjadi admin grup", 16 | "demote": "{{@ADM}} telah mendemosikan {{@PPL}} menjadi anggota grup" 17 | }, 18 | "group": { 19 | "subject": "{{@PPL}} telah mengubah nama grup menjadi {{@TTL}}", 20 | "icon": { 21 | "change": "{{@PPL}} telah mengubah gambar profil grup", 22 | "remove": "{{@PPL}} telah menghapus gambar profil grup" 23 | }, 24 | "invite_link": "{{@PPL}} telah memperbarui URL grup", 25 | "description": "{{@PPL}} telah memperbarui deskripsi grup menjadi {{@TTL}}", 26 | "restrict": { 27 | "on": "{{@PPL}} telah mengubah pengaturan grup, hanya admin grup yang dapat mengubah pengaturan tersebut", 28 | "off": "{{@PPL}} telah mengubah pengaturan grup, sekarang anggota grup dapat mengubah pengaturan tersebut" 29 | }, 30 | "announce": { 31 | "on": "{{@PPL}} telah mengubah pengaturan grup, hanya admin grup yang dapat mengirim pesan", 32 | "off": "{{@PPL}} telah mengubah pengaturan grup, sekarang anggota grup dapat mengirim pesan" 33 | } 34 | }, 35 | "message": { 36 | "cooldown": "Perintah sedang cooldown, silahkan tunggu selama {{@SKNDS}}", 37 | "maintenance": "Perintah sedang dalam pemeliharaan, kamu tidak dapat menggunakannya saat ini", 38 | "ownerOnly": "Perintah hanya dapat digunakan oleh pemilik bot, kamu tidak dapat menggunakannya", 39 | "premiumOnly": "Perintah hanya dapat digunakan oleh pengguna premium, kamu tidak dapat menggunakannya", 40 | "privateOnly": "Perintah hanya dapat digunakan dalam obrolan pribadi, kamu tidak dapat menggunakannya dalam obrolan grup", 41 | "groupOnly": "Perintah hanya dapat digunakan dalam obrolan grup, kamu tidak dapat menggunakannya dalam obrolan pribadi", 42 | "botGroupAdmin": "Perintah hanya dapat digunakan jika bot merupakan salah satu admin grup", 43 | "ownerGroup": "Perintah hanya dapat digunakan oleh pemilik grup, kamu tidak dapat menggunakannya", 44 | "adminGroup": "Perintah hanya dapat digunakan oleh admin grup, kamu tidak dapat menggunakannya", 45 | "errorMessage": { 46 | "noCmd": "Sepertinya perintah penggunaannya salah. Silakan ketik {{@CMD}} untuk melihat contoh.", 47 | "timeout": "Sepertinya perintah membutuhkan waktu lama untuk merespons. Silakan coba beberapa saat lagi", 48 | "error": "Sepertinya perintah mengalami kesalahan: {{@ERRMSG}}. Silakan coba beberapa saat lagi", 49 | "unknown": "Sepertinya perintah mengalami kesalahan yang tidak diketahui. Silakan coba beberapa saat lagi" 50 | } 51 | } 52 | }, 53 | "commands": { 54 | "general": { 55 | "didyoumean": { 56 | "title": "Perintah yang kamu ketik tidak tercantum, mungkin ini yang kamu maksud:", 57 | "same": "kesamaan" 58 | }, 59 | "language": { 60 | "changed": "Bahasa telah diubah menjadi {{@LANGUAGE}}!", 61 | "text": "Kamu dapat mengubah bahasa saat berinteraksi dengan aku, berikut beberapa bahasa yang didukung" 62 | }, 63 | "menu": { 64 | "cmd": { 65 | "zero": "Tidak ada perintah yang ditemukan", 66 | "one": "Alias", 67 | "two": "Kategori", 68 | "three": "Keterangan", 69 | "four": "Penggunaan", 70 | "five": "Hanya di obrolan grup", 71 | "six": "Hanya untuk admin grup", 72 | "seven": "Hanya untuk pemilik grup", 73 | "eight": "Hanya di obrolan pribadi", 74 | "nine": "Hanya untuk premium", 75 | "ten": "Hanya untuk pemilik bot", 76 | "eleven": "Menggunakan limit", 77 | "twelve": "Cooldown", 78 | "thirteen": "Pemeliharaan" 79 | }, 80 | "intro": { 81 | "one": "Halo {{@PUSHNAME}} 👋️,", 82 | "two": "Ada yang bisa aku bantu?" 83 | }, 84 | "detail": { 85 | "one": "RAM server: {{@SZEE}}", 86 | "two": "Total perintah: {{@CMDS}}", 87 | "three": "Waktu aktif: {{@UPTMS}}", 88 | "four": "Bahasa Program: TypeScript" 89 | }, 90 | "info": { 91 | "one": "Kamu dapat mengganti bahasa", 92 | "two": "di obrolan pribadi", 93 | "three": "dengan mengetik {{@COMMANDS}}" 94 | }, 95 | "bottom": "Berikut ini adalah beberapa fitur yang terdapat dalam bot." 96 | } 97 | }, 98 | "group": { 99 | "anticountry": { 100 | "add": "Berhasil menambahkan kode negara, berikut daftar yang ada di grup: {{@NUM}}", 101 | "remove": "Berhasil menghapus kode negara, berikut daftar yang ada di grup: {{@NUM}}", 102 | "enable": "Berhasil mengaktifkan {{@CMD}}, bot akan menendang anggota grup dengan awalan kode negara: {{@NUM}}", 103 | "disable": "Berhasil menonaktifkan {{@CMD}}, bot tidak lagi menendang anggota grup dengan awalan kode negara: {{@NUM}}", 104 | "status": { 105 | "text": "Saring anggota yang masuk dan tendang yang tidak diizinkan", 106 | "isActive": "Tendang kode negara lain: {{@STS}}", 107 | "numList": "Daftar kode negara di grup: {{@NUM}}" 108 | }, 109 | "list": "Daftar semua kode negara yang tersedia berdasarkan {{@URL}}" 110 | }, 111 | "antilink": { 112 | "enable": "Berhasil mengaktifkan {{@CMD}}, bot akan langsung menendang anggota grup yang mengirimkan tautan grup whatsapp", 113 | "disable": "Berhasil menonaktifkan {{@CMD}}, bot tidak lagi menendang anggota grup yang mengirim tautan grup whatsapp" 114 | }, 115 | "antiviewonce": { 116 | "enable": "Berhasil mengaktifkan {{@CMD}}, bot akan mengirim ulang pesan yang dikirim sebagai tampilan sekali", 117 | "disable": "Berhasil menonaktifkan {{@CMD}}, bot tidak lagi mengirim ulang pesan yang dikirim sebagai tampilan sekali" 118 | }, 119 | "change-description": "{{@ADM}} mengubah deskripsi grup menjadi", 120 | "change-name": "{{@ADM}} mengubah judul grup menjadi", 121 | "change-picture": "{{@ADM}} telah mengubah profil grup", 122 | "group-add": { 123 | "caption": "Undangan untuk bergabung dengan grup WhatsApp saya", 124 | "add": "Berhasil menambahkan {{@NUM}} ke grup", 125 | "invite": "Berhasil mengundang {{@NUM}} ke grup" 126 | }, 127 | "group-demote": "Berhasil menurunkan jabatan {{@ADM}} untuk menjadi member", 128 | "group-kick": { 129 | "success": "Berhasil mengeluarkan {{@NUM}} dari grup", 130 | "failed": "{{@NUM}} tidak terdapat dalam grup" 131 | }, 132 | "group-language": "Kamu dapat mengubah bahasa grup saat berinteraksi dengan aku, berikut beberapa bahasa yang didukung", 133 | "group-mute": { 134 | "enable": "Berhasil mengaktifkan {{@CMD}}, bot tidak akan merespons perintah selanjutnya di grup ini", 135 | "disable": "Berhasil menonaktifkan {{@CMD}}, bot akan merespons perintah selanjutnya di grup ini" 136 | }, 137 | "group-notify": { 138 | "enable": "Berhasil mengaktifkan {{@CMD}}, bot akan mengirimkan pesan ketika ada update di grup", 139 | "disable": "Berhasil menonaktifkan {{@CMD}}, bot tidak lagi mengirimkan pesan ketika ada update di grup" 140 | }, 141 | "group-promote": "Berhasil mempromosikan {{@ADM}} untuk menjadi admin", 142 | "group-url": "URL grup: {{@URL}}", 143 | "member-leave": { 144 | "enable": "Berhasil mengaktifkan {{@CMD}}, bot akan mengirimkan pesan selamat tinggal saat ada peserta yang keluar dari grup", 145 | "disable": "Berhasil menonaktifkan {{@CMD}}, bot tidak lagi mengirim pesan selamat tinggal saat ada peserta yang keluar dari grup" 146 | }, 147 | "member-welcome": { 148 | "enable": "Berhasil mengaktifkan {{@CMD}}, bot akan mengirimkan pesan selamat datang saat ada peserta baru yang bergabung ke grup", 149 | "disable": "Berhasil menonaktifkan {{@CMD}}, bot tidak lagi mengirim pesan selamat datang saat ada peserta baru yang bergabung ke grup" 150 | } 151 | }, 152 | "owner": { 153 | "bot-desc": "Berhasil mengubah description profil bot", 154 | "bot-name": "Berhasil mengubah nama profil bot", 155 | "bot-picture": "Berhasil mengubah gambar profil bot" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whatsapp-bot", 3 | "version": "3.0.0", 4 | "description": "Whatsapp Bot Multi Device - Node Js", 5 | "main": "./dist/main.js", 6 | "private": true, 7 | "scripts": { 8 | "build": "tsc --build", 9 | "dev": "rimraf dist && tsc-watch --build ./tsconfig.json --onSuccess \"node --inspect ./dist/main.js\" --noClear", 10 | "generateLang": "node -r ./bin/generateLang ./bin/generateLang.arugaz", 11 | "generateLangV2": "node ./bin/generateLangV2", 12 | "lint": "eslint . --ext .ts && prettier --write \"src/**/*.+(ts)\" \"languages/**/*.+(json)\"", 13 | "start": "node ./dist/main.js", 14 | "start:pm2": "pm2 start ecosystem.config.js && pm2 save && pm2 logs whatsappbot --raw" 15 | }, 16 | "author": "arugaz", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/ArugaZ/whatsapp-bot.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/ArugaZ/whatsapp-bot/issues" 23 | }, 24 | "homepage": "https://github.com/ArugaZ/whatsapp-bot", 25 | "license": "GPL-3.0-or-later", 26 | "engines": { 27 | "node": ">=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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <Event>{ 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <Event>{ 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 <Command>{ 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 <Command>{ 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 <Event>{ 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 <Command>{ 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 <Event>{ 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 <Command>{ 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 <desc> 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 <Command>{ 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 <name> 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 <Command>{ 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 <Command>{ 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 <number> 17 | 18 | eg, @PREFIX@CMD 62851xxxxxx 19 | -------- 20 | Add multiple members 21 | @PREFIX@CMD <number> <number> ...<other number> 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 <Command>{ 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 <number | @mention> 17 | 18 | eg, @PREFIX@CMD 62851xxxxxx 19 | -------- 20 | Demote multiple members / mentions 21 | @PREFIX@CMD <number> <@mention> ...<other member> 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 <Command>{ 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 <number | @mention> 17 | 18 | eg, @PREFIX@CMD 62851xxxxxx 19 | -------- 20 | Kick multiple members / mention 21 | @PREFIX@CMD <number> <@mention> ...<other member> 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <number | @mention> 17 | 18 | eg, @PREFIX@CMD 62851xxxxxx 19 | -------- 20 | Demote multiple members / mentions 21 | @PREFIX@CMD <number> <@mention> ...<other member> 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <Command>{ 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 <number> <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 <Command>{ 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 <Command>{ 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<unknown> => { 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<unknown> => { 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<unknown> => { 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<Buffer>((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<Buffer>((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<Buffer> { 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<ArugaAuth> => { 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<void> => { 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<void>[] = [] 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<void> => { 87 | try { 88 | await writeData(creds, "creds") 89 | } catch {} 90 | }, 91 | clearState: async (): Promise<void> => { 92 | try { 93 | await Database.session.deleteMany({}) 94 | } catch {} 95 | } 96 | } 97 | } 98 | 99 | export const useSingleAuthState = async (Database: PrismaClient): Promise<ArugaAuth> => { 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<void> => { 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<void> => { 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<string, Command>() 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<string, Event>() 23 | 24 | /** 25 | * Cooldown collection 26 | * to store in memory 27 | */ 28 | export const cooldowns = new Map<string, number>() 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<Omit<GroupMetadata, "id" | "groupId">>) => { 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<Omit<GroupMetadata, "id" | "groupId">>) => { 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<Omit<Group, "id" | "groupId">>) => { 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<Omit<Group, "id" | "groupId">>) => { 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<Omit<User, "id" | "userId">>) => { 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<Omit<User, "id" | "userId">>) => { 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<CallSerialize> => { 8 | const c = <CallSerialize>{} 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<GroupParticipantSerialize> => { 7 | const m = <GroupParticipantSerialize>{} 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<GroupSerialize> => { 7 | const m = <GroupSerialize>{} 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<MessageSerialize> => { 6 | const m = <MessageSerialize>{} 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 = <MessageSerialize>{} 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<void> 6 | clearState: () => Promise<void> 7 | } 8 | 9 | declare type Aruga = Partial<WASocket> 10 | 11 | declare type ArugaConfig = { 12 | authType: "single" | "multi" 13 | } & Partial<Omit<SocketConfig, "auth" | "browser" | "patchMessageBeforeSending" | "printQRInTerminal" | "version">> 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<E extends keyof ArugaEvents>(event: E, listener: ArugaEvents[E]): this 25 | off<E extends keyof ArugaEvents>(event: E, listener: ArugaEvents[E]): this 26 | emit<E extends keyof ArugaEvents>(event: E, ...args: Parameters<ArugaEvents[E]>): boolean 27 | removeAllListeners<E extends keyof ArugaEvents>(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 string> = 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<void> 6 | save(...args: unknown[]): Promise<Buffer> 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<proto.WebMessageInfo>} send messages 21 | */ 22 | reply: (text: string) => Promise<proto.WebMessageInfo> 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<proto.WebMessageInfo>} send messages 48 | */ 49 | reply: (text: string) => Promise<proto.WebMessageInfo> 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<proto.WebMessageInfo>} send messages 75 | */ 76 | reply: (text: string) => Promise<proto.WebMessageInfo> 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<proto.WebMessageInfo>} if quoted is set to true will reply the message otherwise just typing back.. 124 | */ 125 | reply: (text: string, quoted?: boolean) => Promise<proto.WebMessageInfo> 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<Buffer>((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 = <T>(url: string, opts?: AxiosRequestConfig) => 17 | new Promise<T>((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 = <T>(url: string, data: unknown, opts?: AxiosRequestConfig) => 30 | new Promise<T>((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<string>((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<void>((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<string>((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<string>() 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<string>() 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 | --------------------------------------------------------------------------------