├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .eslintrc.cjs ├── .github ├── ISSUE_TEMPLATE │ ├── wechaty-bug-report.md │ ├── wechaty-feature-request.md │ └── wechaty-question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── node.js.yml ├── .gitignore ├── .gitpod.dockerfile ├── .gitpod.yml ├── .vscode └── settings.json ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── docs ├── gitpod.md └── images │ ├── gitpod-wechaty.webp │ └── video-tutorial-img.webp ├── examples ├── README.md ├── advanced │ ├── busy-bot.js │ ├── demo-in-tutorial.js │ ├── dump-room-member.ts │ ├── friend-bot.js │ ├── gist-bot │ │ ├── index.js │ │ ├── on-friend.js │ │ ├── on-message.js │ │ └── on-room-join.js │ ├── media-file-bot.js │ ├── plugin-bot.ts │ ├── room-bot.js │ ├── room-say-cli.js │ ├── self-testing-bot.js │ └── shell-bot.ts ├── basic │ ├── contact-bot.js │ ├── ding-dong-bot.js │ └── the-worlds-shortest-chatbot-code-in-6-lines.js ├── cqrs │ └── ding-dong-bot.ts ├── ding-dong-bot.ts ├── professional │ ├── api-ai-bot.ts │ ├── blessed-twins-bot │ │ ├── README.md │ │ └── bless-twins-bot.ts │ ├── chatgpt-bot │ │ ├── README.md │ │ ├── bot.ts │ │ ├── dotenv │ │ ├── package.json │ │ ├── run.sh │ │ └── utils.ts │ ├── ctrl-c-signal-bot.ts │ ├── hot-import-bot │ │ ├── README.md │ │ ├── hot-import-bot.js │ │ ├── listeners │ │ │ ├── on-friend.js │ │ │ ├── on-login.js │ │ │ ├── on-message.js │ │ │ └── on-scan.js │ │ ├── run-by-docker.sh │ │ └── run-by-node.sh │ ├── monster-bot │ │ ├── README.md │ │ ├── config.js │ │ ├── index.js │ │ ├── listeners │ │ │ ├── on-friend.js │ │ │ ├── on-login.js │ │ │ ├── on-message.js │ │ │ └── on-scan.js │ │ └── run-monster-bot.sh │ ├── send-link.ts │ ├── speech-to-text-bot.ts │ ├── telegram-roger-bot.js │ └── tuling123-bot.js ├── tensorflow.js │ └── fingerpose │ │ ├── README.md │ │ ├── bot │ │ └── fingerpose-bot.ts │ │ ├── examples │ │ ├── handpose.ts │ │ ├── paper.png │ │ ├── rock.png │ │ └── scissors.png │ │ ├── package.json │ │ └── tsconfig.json ├── third-parties │ ├── codesandbox │ │ ├── .gitignore │ │ ├── README.md │ │ ├── ding-dong-bot.ts │ │ ├── install-gotty.sh │ │ ├── package.json │ │ ├── sandbox.config.json │ │ └── tsconfig.json │ ├── maodou │ │ ├── README.md │ │ ├── maodou-classes-bot.js │ │ ├── maodou-course-api.js │ │ ├── maodou-nlp.js │ │ └── package.json │ └── xiaoli │ │ ├── README.md │ │ ├── package.json │ │ └── xiaoli-news-bot.js └── tutorials │ └── google-cloud-shell-tutorial.md ├── package.json ├── tests ├── wechaty-puppet-mock.spec.ts ├── wechaty-puppet-service.spec.ts ├── wechaty-puppet-wechat.spec.ts └── wechaty-puppet-wechat4u.spec.ts └── tsconfig.json /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/universal:2", 3 | "waitFor": "onCreateCommand", 4 | "updateContentCommand": "npm install", 5 | "postCreateCommand": "npm install cross-env -g", 6 | "customizations": { 7 | "codespaces": { 8 | "openFiles": ["examples/ding-dong-bot.ts"] 9 | } 10 | }, 11 | 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | 16 | # 4 tab indentation 17 | [Makefile] 18 | indent_style = tab 19 | indent_size = 4 20 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | 2 | const rules = { 3 | } 4 | 5 | module.exports = { 6 | extends: '@chatie', 7 | rules, 8 | } 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/wechaty-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Wechaty Bug Report 3 | about: Create a bug report for a bug you found in wechaty 4 | 5 | --- 6 | 7 | > Important:Please file the issue follow the template, or we won't help you to solve the problem. 8 | 9 | 10 | ## 0. Report Issue Guide 11 | 12 | 1. Please run the following command and check whether the problem has been fixed: 13 | ``` 14 | rm -rf package-lock.json 15 | rm -rf node_modules 16 | npm install 17 | ``` 18 | 19 | 2. Please search in [FAQ List](https://docs.chatie.io/faq) first, and make sure your problem has not been solved before. 20 | 21 | 3. Please search in the issue first, and make sure your problem had not been reported before 22 | 23 | ## 1. Versions 24 | - What is your wechaty version? 25 | Answer: 26 | 27 | - Which puppet are you using for wechaty? (padchat/puppeteer/padpro/...) 28 | Answer: 29 | 30 | - What is your wechaty-puppet-XXX(padchat/puppeteer/) version? 31 | Answer: 32 | 33 | - What is your node version? (run `node --version`) 34 | Answer: 35 | 36 | - What os are you using 37 | Answer: 38 | 39 | ## 2. Describe the bug 40 | Give a clear and concise description of what the bug is. 41 | 42 | ## 3. To Reproduce 43 | This part is very important: if you can not provide any reproduce steps, then the problem will be very hard to be recognized. 44 | 45 | Steps to reproduce the behavior: 46 | 1. run '...' 47 | 2. ... 48 | 3. ... 49 | 50 | ## 4. Expected behavior 51 | Give a clear and concise description of what you expected to happen. 52 | 53 | ## 5. Actual behavior 54 | If applicable, add screenshots to help explain your problem. But do not paste log screenshots here. 55 | 56 | 57 | ## 6. Full Output Logs 58 | Set env `WECHATY_LOG=silly` in order to set log level to silly, then we can get the full log (If you dosen't set log env, log level is info as default, we cannot get the full log) 59 | 60 | **We need full log instead of log screenshot or log fragments!** 61 | 62 |
63 | 64 | Show Logs 65 | 66 | 67 | ```shell 68 | $ WECHATY_LOG=silly node yourbot.js 69 | 70 | Question: Paste your FULL(DO NOT ONLY PROVIDE FRAGMENTS) log messages 71 | Answer: 72 | 73 | ``` 74 | 75 |
76 | 77 | ## 7. Additional context 78 | Add any other context about the problem here. 79 | 80 | [bug] 81 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/wechaty-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Wechaty Feature Request 3 | about: Suggest a feature for wechaty 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | 19 | [enhancement] 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/wechaty-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Wechaty Question 3 | about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/wechaty 4 | 5 | --- 6 | 7 | 🚨 The issue tracker is not for questions 🚨 8 | 9 | If you have a question, please ask it on https://stackoverflow.com/questions/tagged/wechaty 10 | 11 | [question] 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## I'm submitting a... 2 | 3 | ``` 4 | [ ] Bug Fix 5 | [ ] Feature 6 | [ ] Other (Refactoring, Added tests, Documentation, ...) 7 | ``` 8 | 9 | ## Checklist 10 | 11 | - [ ] Commit Messages follow the [Conventional Commits](https://conventionalcommits.org/) pattern 12 | - A feature commit message is prefixed "feat:" 13 | - A bugfix commit message is prefixed "fix:" 14 | - [ ] Tests for the changes have been added 15 | 16 | 17 | ## Description 18 | 19 | _please describe the changes that you are making_ 20 | 21 | _for features, please describe how to use the new feature_ 22 | 23 | _please include a reference to an existing issue, if applicable_ 24 | 25 | 26 | ## Does this PR introduce a breaking change? 27 | 28 | ``` 29 | [ ] Yes 30 | [ ] No 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | schedule: 12 | - cron: 0 5 * * 0 # 5 am Sunday (weekly) 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | os: 19 | - macos-latest 20 | - windows-latest 21 | - ubuntu-latest 22 | node-version: 23 | - 16 24 | runs-on: ${{ matrix.os }} 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Use Node.js ${{ matrix.node-version }} 29 | uses: actions/setup-node@v2 30 | with: 31 | node-version: ${{ matrix.node-version }} 32 | cache: npm 33 | cache-dependency-path: package.json 34 | - run: npm install 35 | - run: npm test 36 | env: 37 | CI: true 38 | WECHATY_PUPPET_SERVICE_TOKEN: ${{ secrets.WECHATY_PUPPET_SERVICE_TOKEN }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | npm-shrinkwrap.json 36 | 37 | .*.swp 38 | 39 | # TAP Coverage 40 | coverage 41 | .nyc_output 42 | 43 | # Wechaty session file 44 | demo.wechaty.json 45 | 46 | # Cloud 9 47 | .c9/ 48 | 49 | # Other staff 50 | 0 51 | t/ 52 | t.* 53 | .DS_Store 54 | /dist/ 55 | yarn.lock 56 | .yarn/ 57 | .yarn-cache/ 58 | .v8flags.* 59 | 60 | # why? forgot... 2017-05-18 by zixia 61 | /tests/fixtures/docker/package.json 62 | 63 | tags 64 | *.memory-card.json 65 | *.wechaty.json 66 | /package 67 | /wechaty-*.*.*.tgz 68 | *.bak 69 | package-lock.json 70 | .babel.json 71 | 72 | .idea 73 | examples/.config 74 | examples/.pki 75 | .config/ 76 | log 77 | /*.jpg 78 | .env 79 | .env.* 80 | data/ 81 | 82 | # Puppet WhatsApp Auth Folder 83 | # TODO: remove this after https://github.com/wechaty/puppet-whatsapp/issues/350 has been fixed 84 | .wwebjs_*/ 85 | -------------------------------------------------------------------------------- /.gitpod.dockerfile: -------------------------------------------------------------------------------- 1 | FROM wechaty/wechaty:next 2 | LABEL maintainer="Huan " 3 | 4 | RUN cd /tmp \ 5 | && wget https://github.com/yudai/gotty/releases/download/v2.0.0-alpha.3/gotty_2.0.0-alpha.3_linux_amd64.tar.gz \ 6 | && tar zxvf ./gotty*.tar.gz \ 7 | && rm -f gotty*.tar.gz \ 8 | && mv gotty* /usr/local/bin 9 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.dockerfile 3 | 4 | # List the ports you want to expose and what to do when they are served. See https://www.gitpod.io/docs/config-ports/ 5 | # https://www.gitpod.io/docs/config-ports/ 6 | ports: 7 | - port: 8080 8 | onOpen: open-preview 9 | 10 | # List the start up tasks. You can start them in parallel in multiple terminals. See https://www.gitpod.io/docs/config-start-tasks/ 11 | # https://www.gitpod.io/docs/config-start-tasks/ 12 | tasks: 13 | - name: Wechaty ding-dong BOT 14 | openIn: main 15 | openMode: split-right 16 | # 17 | # Huan(202002): `npm install` will fail the gitpod loading in browser 18 | # https://github.com/wechaty/getting-started/issues/160 19 | # 20 | init: | 21 | echo '`npm install` will fail the gitpod loading in browser' 22 | echo 'See: https://github.com/wechaty/getting-started/issues/160' 23 | command: | 24 | npm install 25 | # WECHATY_PUPPET_SERVICE_TOKEN=XXX npm run start:puppet:service 26 | # WECHATY_PUPPET_PADLOCAL_TOKEN=XXX npm run start:wechat:padlocal 27 | # npm run start:whatsapp:web 28 | npm run start:wechat:web 29 | 30 | github: 31 | prebuilds: 32 | # enable for the master/default branch (defaults to true) 33 | master: true 34 | # enable for all branches in this repo (defaults to false) 35 | branches: false 36 | # enable for pull requests coming from this repo (defaults to true) 37 | pullRequests: true 38 | # enable for pull requests coming from forks (defaults to false) 39 | pullRequestsFromForks: false 40 | # add a "Review in Gitpod" button as a comment to pull requests (defaults to true) 41 | addComment: true 42 | # add a "Review in Gitpod" button to pull requests (defaults to false) 43 | addBadge: false 44 | # add a label once the prebuild is ready to pull requests (defaults to false) 45 | addLabel: false 46 | # add a check to pull requests (defaults to true) 47 | addCheck: true 48 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | 4 | "editor.fontFamily": "'Fira Code iScript', Consolas, 'Courier New', monospace", 5 | "editor.fontLigatures": true, 6 | 7 | "editor.tokenColorCustomizations": { 8 | "textMateRules": [ 9 | { 10 | "scope": [ 11 | //following will be in italics (=Pacifico) 12 | "comment", 13 | // "entity.name.type.class", //class names 14 | "keyword", //import, export, return… 15 | "support.class.builtin.js", //String, Number, Boolean…, this, super 16 | "storage.modifier", //static keyword 17 | "storage.type.class.js", //class keyword 18 | "storage.type.function.js", // function keyword 19 | "storage.type.js", // Variable declarations 20 | "keyword.control.import.js", // Imports 21 | "keyword.control.from.js", // From-Keyword 22 | "entity.name.type.js", // new … Expression 23 | "keyword.control.flow.js", // await 24 | "keyword.control.conditional.js", // if 25 | "keyword.control.loop.js", // for 26 | "keyword.operator.new.js", // new 27 | ], 28 | "settings": { 29 | "fontStyle": "italic", 30 | }, 31 | }, 32 | { 33 | "scope": [ 34 | //following will be excluded from italics (My theme (Monokai dark) has some defaults I don't want to be in italics) 35 | "invalid", 36 | "keyword.operator", 37 | "constant.numeric.css", 38 | "keyword.other.unit.px.css", 39 | "constant.numeric.decimal.js", 40 | "constant.numeric.json", 41 | "entity.name.type.class.js" 42 | ], 43 | "settings": { 44 | "fontStyle": "", 45 | }, 46 | } 47 | ] 48 | }, 49 | 50 | "files.exclude": { 51 | "dist/": true, 52 | "doc/": true, 53 | "node_modules/": true, 54 | "package/": true, 55 | }, 56 | "alignment": { 57 | "operatorPadding": "right", 58 | "indentBase": "firstline", 59 | "surroundSpace": { 60 | "colon": [1, 1], // The first number specify how much space to add to the left, can be negative. The second number is how much space to the right, can be negative. 61 | "assignment": [1, 1], // The same as above. 62 | "arrow": [1, 1], // The same as above. 63 | "comment": 2, // Special how much space to add between the trailing comment and the code. 64 | // If this value is negative, it means don't align the trailing comment. 65 | } 66 | }, 67 | "editor.formatOnSave": false, 68 | "python.pythonPath": "python3", 69 | "eslint.validate": [ 70 | "javascript", 71 | "typescript", 72 | ], 73 | "cSpell.words": [ 74 | "KUBERNETES", 75 | "gitpod", 76 | "gotty", 77 | "prebuild", 78 | "prebuilds" 79 | ], 80 | } 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Huan LI (李卓桓) and 190 | Wechaty Contributors . 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Wechaty Getting Started 2 | # 3 | # GitHb: https://github.com/wechaty/getting-started 4 | # Author: Huan LI https://github.com/huan 5 | # 6 | # Make commands supported: 7 | # install 8 | # bot 9 | # lint 10 | # test 11 | # clean 12 | # 13 | 14 | .PHONY: all 15 | all : install bot 16 | 17 | .PHONY: install 18 | install: 19 | npm install 20 | 21 | .PHONY: bot 22 | bot: 23 | npm start 24 | 25 | .PHONY: clean 26 | clean: 27 | npm run clean 28 | 29 | .PHONY: lint 30 | lint: npm run lint 31 | 32 | .PHONY: test 33 | test: 34 | npm test 35 | 36 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Wechaty is a Conversational RPA SDK for Chatbot Makers. 2 | Copyright 2016-now Huan (李卓桓) and Wechaty Community Contributors. 3 | 4 | This product includes software developed at 5 | The Wechaty Organization (https://github.com/wechaty). 6 | 7 | This software contains code derived from the Stackoverflow, 8 | including various modifications by GitHub. 9 | -------------------------------------------------------------------------------- /docs/gitpod.md: -------------------------------------------------------------------------------- 1 | # 👇 **Click me** 2 | 3 | [![GitPod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/wechaty/getting-started) 4 | 5 | Click the ☝️ above `[Gitpod | Ready-to-Code]` button to launch Gitpod with Wechaty Getting Started ding-dong BOT instantly inside your favorite browser. 6 | 7 | The button will take you to this link: 8 | 9 | GitHub links: 10 | 11 | - Repo: 12 | - Pages 13 | 14 | ## Screenshot 15 | 16 | You can [join our Gitter](https://gitter.im/wechaty/wechaty) network if you aren’t already a member. 17 | 18 | [![GitPod Wechaty Getting Started](images/gitpod-wechaty.webp)](https://gitpod.io/#https://github.com/wechaty/getting-started) 19 | 20 | > Wechaty ❤️ Gitpod 21 | 22 | ## What is Gitpod 23 | 24 | > Gitpod is an online IDE which can be launched from any GitHub page. Simply prefix any GitHub-URL with “” or use our browser extension that adds a button to GitHub pages. 25 | > 26 | > Within seconds, Gitpod provides you with a fully working development environment, including a VS Code-powered IDE and a cloud-based Linux container configured specifically for the project at hand. 27 | > 28 | > — [Gitpod — Online IDE For GitHub](https://medium.com/gitpod/gitpod-gitpod-online-ide-for-github-6296b907a886) 29 | 30 | [Here](https://stackoverflow.com/a/63595356/1123955) has a Stackoverflow answer from Gitpod co-founder Moriz Eysholdt. 31 | 32 | ## What is Wechaty 33 | 34 | [Wechaty](https://github.com/wechaty/wechaty/) is a Conversational RPA SDK for Chatbot Makers. It's well designed with an easy to use API, supports all OSs including Linux, OSX, Win32, Docker, and lots of IMs including WeChat, WeCom, Whatsapp, Lark, Gitter, etc. 35 | 36 | ## What is Wechaty Getting Started 37 | 38 | [Wechaty Getting Started](https://github.com/wechaty/getting-started) is a repository that work out-of-the-box, which is the best start point for Wechaty beginners. 39 | 40 | As a developer, you can use Wechaty to easily build your bot, effectively manage message sending/receiving, room creating/inviting, contact friendship, and delightful add artificial intelligence between users and your bot. 41 | 42 | - GitHub: 43 | - `ding-dong-bot.ts`: 44 | 45 | ## Maintainers 46 | 47 | [@wechaty/contributors](https://github.com/orgs/wechaty/teams/contributors/members) 48 | 49 | ## Copyright & License 50 | 51 | - Code & Docs © 2018-now Huan and Wechaty Community Contributors () 52 | - Code released under the Apache-2.0 License 53 | - Docs released under Creative Commons 54 | -------------------------------------------------------------------------------- /docs/images/gitpod-wechaty.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechaty/getting-started/4753cac2d0fad46b8b33043218b82de615521f05/docs/images/gitpod-wechaty.webp -------------------------------------------------------------------------------- /docs/images/video-tutorial-img.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechaty/getting-started/4753cac2d0fad46b8b33043218b82de615521f05/docs/images/video-tutorial-img.webp -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # WECHATY OFFICIAL EXAMPLES LIST DIRECTORY 2 | 3 | The `wechaty-getting-started/examples` directory is provided for people who want to learn coding wechaty, for them to get an easier start. If you start coding wechaty for the first time, we highly recommend to start with the [ding-dong-bot.js](basic/ding-dong-bot.js), the best starter template for you. 4 | 5 | | Filename | Description | 描述 | 6 | | --- | --- | --- | 7 | | ding-dong-bot.ts | The Best Starter Template for You | | 8 | 9 | Moreover, 10 | 11 | - All the other examples have been carefully arranged into different categories so as to better guide you for what to choose/see, as you move along. 12 | - In order to keep things simple, we just put the examples in the subdirectory of the examples/ directory, so that they each can just run out-of-the-box. 13 | - We also recommend that you at least read through them all, before starting coding, for a even better, smoother ride. 14 | - More demos are welcome. If you have anything interesting to add, please don't hesitate to reach out to us. 15 | 16 | Furthermore, after some careful thinking, the Wechaty author, Huan LI, feels it's necessary to add the examples as vanilla Javascript example, instead of Typescript ones. So if you want to contribute one, please consider so. 17 | 18 | ## 1. BASIC 19 | 20 | Wechaty Basic Functions (demos under the `basic/` directory) 21 | 22 | | FileName | Description | 描述 | 23 | | --- | --- | --- | 24 | | the-worlds-shortest-chatbot-code-in-6-lines.js | The very first wechaty example showcasing how easy it is to get started | | 25 | | ding-dong-bot.js | Practical example illustrates on how to do message handling | | 26 | | contact-bot.js | List all contacts by Wechat ID & Name | 在终端下输出微信号下所有联系的人微信ID和昵称。| 27 | 28 | ## 2. ADVANCED 29 | 30 | Wechaty Advanced Functions (demos under the `advanced/` directory) 31 | 32 | | FileName | Description | 描述 | 33 | | --- | --- | --- | 34 | | demo-in-tutorial.js | the demo bot from the tutorial | | 35 | | busy-bot.js | auto response "busy" message for you when you are | | 36 | | media-file-bot.js | Save Media Attachment in Message to local files | 将消息中的文件、图片、视频等非文本信息存到本地。 | 37 | | room-bot.js,room-say-cli.js | Practical example illustrates on how to do room handling | | 38 | | friend-bot.js | Practical example illustrates on how to do friend handling | | 39 | | gist-bot/ | Best template for bigger modules, with each handler in separated files | | 40 | 41 | ## 3. PROFESSIONAL 42 | 43 | Wechaty Integrated with Other Modules/Services (demos under the `professional/` directory) 44 | 45 | | FileName | Description | 描述 | 46 | | --- | --- | --- | 47 | | hot-import-bot/ | Using Hot Module Reload(HMR) for Wechaty Listeners | | 48 | | ctrl-c-signal-bot.ts | Ctrl-C signal handling demo | | 49 | | monster-bot/ | demo that tried to include everything -- message, room, HMR & signal handling, with each handler in separated files | | 50 | | api-ai-bot.ts | Wechaty bot that uses ApiAi.com brain | | 51 | | speech-to-text-bot.ts | bot that uses baidu speech (vop.baidu.com) | | 52 | | tuling123-bot.ts | Connect to [tuling123](http://www.tuling123.com/) chatbot | 接入[tuling123机器人](http://www.tuling123.com/) | 53 | | telegram-roger-bot.js | single bot that runs under/for both Telegram and WeChaty | | 54 | | blessed-twins-bot/ | Wechaty multi-instance support (v0.16+) demo | | 55 | 56 | ## Multi-lang Wechaty Examples Directories 57 | 58 | 1. Python: 59 | 1. Java: 60 | 1. Go: 61 | 62 | I hope you will enjoy them, cheers! 63 | -------------------------------------------------------------------------------- /examples/advanced/busy-bot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Wechaty - https://github.com/chatie/wechaty 4 | * 5 | * @copyright 2016-2018 Huan LI 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | import qrTerm from 'qrcode-terminal' 21 | 22 | import { 23 | IoClient, 24 | Wechaty, 25 | config, 26 | log, 27 | } from 'wechaty' 28 | 29 | console.log(` 30 | =============== Powered by Wechaty =============== 31 | -------- https://github.com/Chatie/wechaty -------- 32 | 33 | I'm the BUSY BOT, I can do auto response message for you when you are BUSY. 34 | 35 | Send command to FileHelper to: 36 | 37 | 1. '#busy' - set busy mode ON 38 | 2. '#busy I'm busy' - set busy mode ON and set a Auto Reply Message 39 | 3. '#free' - set busy mode OFF 40 | 4. '#status' - check the current Busy Mode and Auto Reply Message. 41 | 42 | Loading... please wait for QrCode Image Url and then scan to login. 43 | `) 44 | 45 | let bot 46 | 47 | const token = config.token 48 | 49 | if (token) { 50 | log.info('Wechaty', 'TOKEN: %s', token) 51 | 52 | bot = Wechaty.instance({ profile: token }) 53 | const ioClient = new IoClient({ 54 | token, 55 | wechaty: bot, 56 | }) 57 | 58 | ioClient.start().catch(e => { 59 | log.error('Wechaty', 'IoClient.init() exception: %s', e) 60 | bot.emit('error', e) 61 | }) 62 | } else { 63 | log.verbose('Wechaty', 'TOKEN: N/A') 64 | bot = Wechaty.instance() 65 | } 66 | 67 | bot 68 | .on('scan', (qrcode, status) => { 69 | qrTerm.generate(qrcode, { small: true }) 70 | console.log(`${status}: ${qrcode} - Scan QR Code of the url to login:`) 71 | }) 72 | .on('logout' , user => log.info('Bot', `${user.name()} logouted`)) 73 | .on('error' , e => log.info('Bot', 'error: %s', e)) 74 | 75 | .on('login', async function(user) { 76 | const msg = `${user.name()} logined` 77 | 78 | log.info('Bot', msg) 79 | await this.say(msg) 80 | 81 | }) 82 | 83 | /** 84 | * Global Event: message 85 | */ 86 | 87 | let busyIndicator = false 88 | let busyAnnouncement = `Automatic Reply: I cannot read your message because I'm busy now, will talk to you when I get back.` 89 | 90 | bot.on('message', async function(msg) { 91 | log.info('Bot', '(message) %s', msg) 92 | 93 | const filehelper = bot.Contact.load('filehelper') 94 | 95 | const sender = msg.from() 96 | const receiver = msg.to() 97 | const text = msg.text() 98 | const room = msg.room() 99 | 100 | // if (msg.age() > 60) { 101 | // log.info('Bot', 'on(message) skip age(%d) > 60 seconds: %s', msg.age(), msg) 102 | // return 103 | // } 104 | 105 | if (!sender || !receiver) { 106 | return 107 | } 108 | 109 | if (receiver.id === 'filehelper') { 110 | if (text === '#status') { 111 | await filehelper.say('in busy mode: ' + busyIndicator) 112 | await filehelper.say('auto reply: ' + busyAnnouncement) 113 | 114 | } else if (text === '#free') { 115 | busyIndicator = false 116 | await filehelper.say('auto reply stopped.') 117 | 118 | } else if (/^#busy/i.test(text)) { 119 | 120 | busyIndicator = true 121 | await filehelper.say('in busy mode: ' + 'ON') 122 | 123 | const matches = text.match(/^#busy (.+)$/i) 124 | if (!matches || !matches[1]) { 125 | await filehelper.say('auto reply message: "' + busyAnnouncement + '"') 126 | 127 | } else { 128 | busyAnnouncement = matches[1] 129 | await filehelper.say('set auto reply to: "' + busyAnnouncement + '"') 130 | 131 | } 132 | } 133 | 134 | return 135 | } 136 | 137 | if (sender.type() !== bot.Contact.Type.Personal) { 138 | return 139 | } 140 | 141 | if (!busyIndicator) { 142 | return // free 143 | } 144 | 145 | if (msg.self()) { 146 | return 147 | } 148 | 149 | /** 150 | * 1. Send busy anoncement to contact 151 | */ 152 | if (!room) { 153 | await msg.say(busyAnnouncement) 154 | return 155 | } 156 | 157 | /** 158 | * 2. If there's someone mentioned me in a room, 159 | * then send busy annoncement to room and mention the contact who mentioned me. 160 | */ 161 | const contactList = await msg.mention() 162 | const contactIdList = contactList.map(c => c.id) 163 | if (contactIdList.includes(this.userSelf().id)) { 164 | await msg.say(busyAnnouncement, sender) 165 | } 166 | 167 | }) 168 | 169 | bot.start() 170 | .catch(e => console.error(e)) 171 | -------------------------------------------------------------------------------- /examples/advanced/demo-in-tutorial.js: -------------------------------------------------------------------------------- 1 | import qrTerm from 'qrcode-terminal' 2 | 3 | import { 4 | Wechaty, 5 | Room 6 | } from 'wechaty' 7 | 8 | const bot = WechatyBuilder.build() 9 | 10 | bot.on('scan', function (qrcode, status) { 11 | qrTerm.generate(qrcode, { small: true }) 12 | }) 13 | 14 | bot.on('login', function (user) { 15 | console.log(`${user} login`) 16 | }) 17 | 18 | bot.on('logout', function (user) { 19 | console.log(`${user} logout`) 20 | }) 21 | 22 | bot.on('friendship', async function (friendship) { 23 | console.log(`get FRIENDSHIP event!`) 24 | 25 | switch (friendship.type()) { 26 | case this.Friendship.Type.Receive: 27 | await friendship.accept() 28 | console.log(`accept friendship!`) 29 | break 30 | case this.Friendship.Type.Confirm: 31 | friendship.contact().say(`Nice to meet you~`) 32 | break 33 | } 34 | }) 35 | 36 | bot.on('message', async function (msg) { 37 | const contact = msg.from() 38 | const text = msg.text() 39 | const room = msg.room() 40 | 41 | if (msg.self()) { 42 | return 43 | } 44 | 45 | if (room) { 46 | const topic = await room.topic() 47 | console.log(`Room: ${topic} Contact: ${contact.name()} Text: ${text}`) 48 | } else { 49 | console.log(`Contact: ${contact.name()} Text: ${text}`) 50 | } 51 | 52 | if (/Hello/.test(text)) { 53 | msg.say('Welcome to wechaty, I am wechaty bot RUI!') 54 | } 55 | 56 | if (/room/.test(text)) { 57 | const keyroom = await bot.Room.find({topic: 'wechaty test room'}) 58 | 59 | if (keyroom) { 60 | const topic = await keyroom.topic() 61 | await keyroom.add(contact) 62 | await keyroom.say(`Welcome to join ${topic}`, contact) 63 | } 64 | } 65 | 66 | if (/fword/.test(text)) { 67 | let keyroom = await bot.Room.find({topic: 'wechaty test room'}) 68 | 69 | if (keyroom) { 70 | await keyroom.say('You said fword, I will remove from the room', contact) 71 | await keyroom.del(contact) 72 | } 73 | } 74 | 75 | }) 76 | 77 | bot.start() 78 | .catch(console.error) 79 | 80 | -------------------------------------------------------------------------------- /examples/advanced/dump-room-member.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /** 3 | * Wechaty - Conversational RPA SDK for Chatbot Makers. 4 | * - https://github.com/wechaty/wechaty 5 | */ 6 | import { 7 | Contact, 8 | ScanStatus, 9 | WechatyBuilder, 10 | log, 11 | } from 'wechaty' 12 | 13 | import qrcodeTerminal from 'qrcode-terminal' 14 | 15 | function onScan (qrcode: string, status: ScanStatus) { 16 | if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) { 17 | const qrcodeImageUrl = [ 18 | 'https://wechaty.js.org/qrcode/', 19 | encodeURIComponent(qrcode), 20 | ].join('') 21 | log.info('DumpRoomMemberBot', 'onScan: %s(%s) - %s', ScanStatus[status], status, qrcodeImageUrl) 22 | 23 | qrcodeTerminal.generate(qrcode, { small: true }) // show qrcode on console 24 | 25 | } else { 26 | log.info('DumpRoomMemberBot', 'onScan: %s(%s)', ScanStatus[status], status) 27 | } 28 | } 29 | 30 | function onLogin (user: Contact) { 31 | log.info('DumpRoomMemberBot', '%s login', user) 32 | } 33 | 34 | const bot = WechatyBuilder.build({ 35 | name: 'ding-dong-bot', 36 | puppet: 'wechaty-puppet-wechat', 37 | }) 38 | 39 | const onReady = async () => { 40 | log.info('DumpRoomMemberBot', '%s ready', bot.name()) 41 | 42 | const roomTopicRegex = /TEEC人工智能委员会/ 43 | 44 | const dump = async () => { 45 | const room = await bot.Room.find({ topic: roomTopicRegex }) 46 | if (!room) { 47 | log.error('DumpRoomMemberBot', 'no room found, wait 3 seconds') 48 | log.info('DumpRoomMemberBot', 'Tip: if you are using web protocol, you can try to say something in the room so that the puppet can discover it') 49 | setTimeout(dump, 3 * 1000) 50 | return 51 | } 52 | 53 | log.info('DumpRoomMemberBot', 'dumping members for room: %s ...', room) 54 | for await (const member of room) { 55 | console.info(member.name()) 56 | } 57 | log.info('DumpRoomMemberBot', 'dumping members for room: %s ... done', room) 58 | 59 | process.exit(0) 60 | } 61 | 62 | dump() 63 | } 64 | 65 | bot.on('scan', onScan) 66 | bot.on('login', onLogin) 67 | bot.on('ready', onReady) 68 | 69 | bot.start() 70 | .then(() => log.info('DumpRoomMemberBot', 'DumpRoomMember Bot Started.')) 71 | .catch(e => log.error('DumpRoomMemberBot', e)) 72 | -------------------------------------------------------------------------------- /examples/advanced/friend-bot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | /* tslint:disable:variable-name */ 21 | import qrTerm from 'qrcode-terminal' 22 | 23 | import { 24 | config, 25 | // Contact, 26 | log, 27 | Wechaty, 28 | Friendship, 29 | } from 'wechaty' 30 | 31 | const welcome = ` 32 | =============== Powered by Wechaty =============== 33 | -------- https://github.com/Chatie/wechaty -------- 34 | 35 | Hello, 36 | 37 | I'm a Wechaty Botie with the following super powers: 38 | 39 | 1. Send Friend Request 40 | 2. Accept Friend Request 41 | 3. Recongenize Verify Message 42 | 43 | If you send friend request to me, 44 | with a verify message 'ding', 45 | I will accept your request automaticaly! 46 | __________________________________________________ 47 | 48 | Hope you like it, and you are very welcome to 49 | upgrade me for more super powers! 50 | 51 | Please wait... I'm trying to login in... 52 | 53 | ` 54 | 55 | console.log(welcome) 56 | const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE }) 57 | 58 | bot 59 | .on('login' , user => log.info('Bot', `${user.name()} logined`)) 60 | .on('logout' , user => log.info('Bot', `${user.name()} logouted`)) 61 | .on('error' , e => log.info('Bot', 'error: %s', e)) 62 | .on('scan', (qrcode, status) => { 63 | qrTerm.generate(qrcode) 64 | console.log(`${qrcode}\n[${status}] Scan QR Code in above url to login: `) 65 | }) 66 | /** 67 | * 68 | * Wechaty Event: `friend` 69 | * 70 | */ 71 | .on('friendship', async friendship => { 72 | let logMsg 73 | const fileHelper = bot.Contact.load('filehelper') 74 | 75 | try { 76 | logMsg = 'received `friend` event from ' + friendship.contact().name() 77 | await fileHelper.say(logMsg) 78 | console.log(logMsg) 79 | 80 | switch (friendship.type()) { 81 | /** 82 | * 83 | * 1. New Friend Request 84 | * 85 | * when request is set, we can get verify message from `request.hello`, 86 | * and accept this request by `request.accept()` 87 | */ 88 | case Friendship.Type.Receive: 89 | if (friendship.hello() === 'ding') { 90 | logMsg = 'accepted automatically because verify messsage is "ding"' 91 | console.log('before accept') 92 | await friendship.accept() 93 | 94 | // if want to send msg , you need to delay sometimes 95 | await new Promise(r => setTimeout(r, 1000)) 96 | await friendship.contact().say('hello from Wechaty') 97 | console.log('after accept') 98 | 99 | } else { 100 | logMsg = 'not auto accepted, because verify message is: ' + friendship.hello() 101 | } 102 | break 103 | 104 | /** 105 | * 106 | * 2. Friend Ship Confirmed 107 | * 108 | */ 109 | case Friendship.Type.Confirm: 110 | logMsg = 'friend ship confirmed with ' + friendship.contact().name() 111 | break 112 | } 113 | } catch (e) { 114 | logMsg = e.message 115 | } 116 | 117 | console.log(logMsg) 118 | await fileHelper.say(logMsg) 119 | 120 | }) 121 | 122 | bot.start() 123 | .catch(async e => { 124 | log.error('Bot', 'init() fail: %s', e) 125 | await bot.stop() 126 | process.exit(-1) 127 | }) 128 | -------------------------------------------------------------------------------- /examples/advanced/gist-bot/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | import { 21 | config, 22 | Wechaty, 23 | log, 24 | } from 'wechaty' 25 | 26 | import { onMessage } from './on-message.js' 27 | import { onFriendship } from './on-friend.js' 28 | import { onRoomJoin } from './on-room-join.js' 29 | 30 | import qrTerm from 'qrcode-terminal' 31 | 32 | const welcome = ` 33 | =============== Powered by Wechaty =============== 34 | -------- https://github.com/Chatie/wechaty -------- 35 | 36 | Please wait... I'm trying to login in... 37 | 38 | ` 39 | console.info(welcome) 40 | 41 | const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE }) 42 | 43 | bot 44 | .on('scan', (qrcode, status) => { 45 | qrTerm.generate(qrcode) 46 | console.info(`${qrcode}\n[${status}] Scan QR Code in above url to login: `) 47 | }) 48 | 49 | .on('login', async function (user) { 50 | log.info('Bot', `${user.name()} logined`) 51 | await this.say(`wechaty logined`) 52 | }) 53 | 54 | .on('logout', user => log.info('Bot', `${user.name()} logouted`)) 55 | .on('error', error => log.info('Bot', 'error: %s', error)) 56 | 57 | .on('message', onMessage) 58 | .on('friendship', onFriendship) 59 | .on('room-join', onRoomJoin) 60 | 61 | .start() 62 | .catch(e => console.error(e)) 63 | -------------------------------------------------------------------------------- /examples/advanced/gist-bot/on-friend.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | import { 21 | Friendship, 22 | Wechaty, 23 | // Room, 24 | } from 'wechaty' 25 | 26 | async function onFriendship ( 27 | request, 28 | ) { 29 | try { 30 | const contact = request.contact() 31 | 32 | if (request.type() === Friendship.Type.Confirm) { 33 | console.info('New friend ' + contact.name() + ' relationship confirmed!') 34 | return 35 | } 36 | 37 | /******************************************** 38 | * 39 | * 从这里开始修改 vvvvvvvvvvvv 40 | * 41 | */ 42 | await request.accept() 43 | 44 | setTimeout( 45 | async _ => { 46 | await contact.say('thank you for adding me') 47 | }, 48 | 3000, 49 | ) 50 | 51 | if (request.hello() === 'ding') { 52 | const myRoom = await this.Room.find({ topic: 'ding' }) 53 | if (!myRoom) return 54 | setTimeout( 55 | async _ => { 56 | await myRoom.add(contact) 57 | await myRoom.say('welcome ' + contact.name()) 58 | }, 59 | 3000, 60 | ) 61 | } 62 | 63 | /** 64 | * 65 | * 到这里结束修改 ^^^^^^^^^^^^ 66 | * 67 | */ 68 | /*******************************************/ 69 | } catch (e) { 70 | console.info(e) 71 | } 72 | } 73 | 74 | module.exports = { 75 | default: onFriendship, 76 | onFriendship, 77 | } 78 | -------------------------------------------------------------------------------- /examples/advanced/gist-bot/on-message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | import { 21 | Message, 22 | Wechaty, 23 | } from 'wechaty' 24 | 25 | async function onMessage(message) { 26 | try { 27 | const room = message.room() 28 | const sender = message.from() 29 | const content = message.text() 30 | 31 | console.info((room ? '[' + await room.topic() + ']' : '') 32 | + '<' + (sender && sender.name()) + '>' 33 | + ':' + message, 34 | ) 35 | 36 | if (message.self() || room) { 37 | console.info('message is sent from myself, or inside a room.') 38 | return 39 | } 40 | 41 | /******************************************** 42 | * 43 | * 从下面开始修改vvvvvvvvvvvv 44 | * 45 | */ 46 | if (!sender) return 47 | 48 | if (content === 'ding') { 49 | await message.say('thanks for ding me') 50 | 51 | const myRoom = await this.Room.find({ topic: 'ding' }) 52 | if (!myRoom) return 53 | 54 | if (await myRoom.has(sender)) { 55 | await sender.say('no need to ding again, because you are already in ding room') 56 | return 57 | } 58 | 59 | await sender.say('ok, I will put you in ding room!') 60 | await myRoom.add(sender) 61 | return 62 | 63 | } else if (content === 'dong') { 64 | await sender.say('ok, dong me is welcome, too.') 65 | return 66 | } 67 | 68 | /** 69 | * 70 | * 到这里结束修改^^^^^^^^^^^^ 71 | * 72 | */ 73 | /*********************************************/ 74 | } catch (e) { 75 | console.error(e) 76 | } 77 | } 78 | 79 | module.exports = { 80 | default: onMessage, 81 | onMessage, 82 | } 83 | -------------------------------------------------------------------------------- /examples/advanced/gist-bot/on-room-join.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | import { 21 | Contact, 22 | Room, 23 | Wechaty, 24 | } from 'wechaty' 25 | 26 | async function onRoomJoin ( 27 | room, 28 | inviteeList, 29 | inviter, 30 | ) { 31 | try { 32 | const inviteeName = inviteeList.map(c => c.name()).join(', ') 33 | /******************************************** 34 | * 35 | * 从这里开始修改 vvvvvvvvvvvv 36 | * 37 | */ 38 | 39 | if (await room.topic() !== 'ding') { 40 | await this.say('Room ' + await room.topic() 41 | + ' got new memeber ' + inviteeName 42 | + ' invited by ' + inviter.name(), 43 | ) 44 | return 45 | } 46 | 47 | const inviterIsMyself = inviter.self() 48 | 49 | if (inviterIsMyself) { 50 | await room.say('Welcome to my room: ' + inviteeName) 51 | return 52 | } 53 | 54 | await room.say('请勿私自拉人。需要拉人请加我', inviter) 55 | await room.say('请先加我好友,然后我来拉你入群。先把你移出啦。', inviteeList) 56 | 57 | inviteeList.forEach(async c => { 58 | await room.del(c) 59 | }) 60 | 61 | /** 62 | * 63 | * 到这里结束修改^^^^^^^^^^^^ 64 | * 65 | */ 66 | /*********************************************/ 67 | 68 | } catch (e) { 69 | console.info(e) 70 | } 71 | 72 | } 73 | 74 | module.exports = { 75 | default: onRoomJoin, 76 | onRoomJoin, 77 | } 78 | -------------------------------------------------------------------------------- /examples/advanced/media-file-bot.js: -------------------------------------------------------------------------------- 1 | import qrTerm from 'qrcode-terminal' 2 | 3 | import { 4 | Wechaty, 5 | Message, 6 | } from 'wechaty' 7 | 8 | const welcome = ` 9 | =============== Powered by Wechaty =============== 10 | -------- https://github.com/chatie/wechaty -------- 11 | 12 | I'm a bot, I can save file to local for you! 13 | __________________________________________________ 14 | 15 | Please wait... I'm trying to login in... 16 | 17 | ` 18 | console.log(welcome) 19 | 20 | const bot = WechatyBuilder.build() 21 | 22 | bot.on('scan', onScan) 23 | bot.on('login', onLogin) 24 | bot.on('logout', onLogout) 25 | bot.on('message', onMessage) 26 | bot.on('error', onError) 27 | 28 | bot.start() 29 | .catch(console.error) 30 | 31 | function onScan (qrcode, status) { 32 | qrTerm.generate(qrcode, { small: true }) // show qrcode on console 33 | } 34 | 35 | function onLogin (user) { 36 | console.log(`${user} login`) 37 | } 38 | 39 | function onLogout (user) { 40 | console.log(`${user} logout`) 41 | } 42 | 43 | function onError (e) { 44 | console.error(e) 45 | } 46 | 47 | async function onMessage(msg) { 48 | console.log(`RECV: ${msg}`) 49 | 50 | if (msg.type() !== Message.Type.Text) { 51 | const file = await msg.toFileBox() 52 | const name = file.name 53 | console.log('Save file to: ' + name) 54 | file.toFile(name) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/advanced/plugin-bot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty Chatbot SDK - https://github.com/wechaty/wechaty 3 | * 4 | * @copyright 2016 Huan LI (李卓桓) , and 5 | * Wechaty Contributors . 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | import { 21 | WechatyBuilder, 22 | type, 23 | } from 'wechaty' 24 | 25 | /** 26 | * Wechaty Plugin Support with Kickout Example #1939 27 | * https://github.com/wechaty/wechaty/issues/1939 28 | */ 29 | import { 30 | QRCodeTerminal, 31 | EventLogger, 32 | DingDong, 33 | OneToManyRoomConnector, 34 | ManyToOneRoomConnector, 35 | ManyToManyRoomConnector, 36 | } from 'wechaty-plugin-contrib' 37 | /** 38 | * 39 | * 1. Declare your Bot! 40 | * 41 | */ 42 | const bot = WechatyBuilder.build({ 43 | name : 'ding-dong-bot', 44 | }) 45 | 46 | /** 47 | * 48 | * 2. Register event handlers for Bot 49 | * 50 | */ 51 | bot.use( 52 | QRCodeTerminal(), 53 | EventLogger(), 54 | DingDong(), 55 | OneToManyRoomConnector({ 56 | blacklist: [async () => true], 57 | many: [ 58 | '20049383519@chatroom', // 小句子测试 59 | '5611663299@chatroom', // 'ChatOps - Mike BO' 60 | ], 61 | map: async message => message.from()?.name() + '(one to many): ' + message.text(), 62 | one: '17237607145@chatroom', // PreAngel 动态 63 | whitelist: [async message => message.type() === type.Message.Text], 64 | }), 65 | ManyToOneRoomConnector({ 66 | blacklist: [async () => true], 67 | many: [ 68 | '20049383519@chatroom', // 小句子测试 69 | '5611663299@chatroom', // 'ChatOps - Mike BO' 70 | ], 71 | map: async message => message.from()?.name() + '(many to one): ' + message.text(), 72 | one: '17237607145@chatroom', // PreAngel 动态 73 | whitelist: [async message => message.type() === type.Message.Text], 74 | }), 75 | ManyToManyRoomConnector({ 76 | blacklist: [async () => true], 77 | many: [ 78 | '20049383519@chatroom', // 小句子测试 79 | '5611663299@chatroom', // 'ChatOps - Mike BO' 80 | ], 81 | map: async message => message.from()?.name() + '(many to many): ' + message.text(), 82 | whitelist: [async message => message.type() === type.Message.Text], 83 | }), 84 | 85 | ) 86 | 87 | /** 88 | * 89 | * 3. Start the bot! 90 | * 91 | */ 92 | bot.start() 93 | .catch(async e => { 94 | console.error('Bot start() fail:', e) 95 | await bot.stop() 96 | process.exit(-1) 97 | }) 98 | 99 | bot.once('login', () => { 100 | bot.Room.findAll().then(async roomList => { 101 | for (const room of roomList) { 102 | console.info(room.id, await room.topic()) 103 | } 104 | return undefined 105 | }).catch(console.error) 106 | }) 107 | -------------------------------------------------------------------------------- /examples/advanced/room-bot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Wechaty - https://github.com/chatie/wechaty 4 | * 5 | * @copyright 2016-2018 Huan LI 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | /** 22 | * 23 | * Known ISSUES: 24 | * - BUG1: can't find member by this NickName: 25 | * ' leaver: 艾静JOY 26 | * - BUG2: leave event not right: sometimes can not found member (any more, because they left) 27 | * create a room need at least three people 28 | * when we create a room, the following one is the 3rd people. 29 | * 30 | * put name of one of your friend here, or room create function will not work. 31 | * 32 | * ::::::::: ___CHANGE ME___ ::::::::: 33 | * vvvvvvvvv 34 | * vvvvvvvvv 35 | * vvvvvvvvv 36 | */ 37 | const HELPER_CONTACT_NAME = '李卓桓' 38 | 39 | /** 40 | * ^^^^^^^^^ 41 | * ^^^^^^^^^ 42 | * ^^^^^^^^^ 43 | * ::::::::: ___CHANGE ME___ ::::::::: 44 | * 45 | */ 46 | 47 | /* tslint:disable:variable-name */ 48 | import qrTerm from 'qrcode-terminal' 49 | 50 | import { 51 | config, 52 | Contact, 53 | Room, 54 | Wechaty, 55 | log, 56 | } from 'wechaty' 57 | 58 | const welcome = ` 59 | =============== Powered by Wechaty =============== 60 | -------- https://github.com/Chatie/wechaty -------- 61 | 62 | Hello, 63 | 64 | I'm a Wechaty Botie with the following super powers: 65 | 66 | 1. Find a room 67 | 2. Add people to room 68 | 3. Del people from room 69 | 4. Change room topic 70 | 5. Monitor room events 71 | 6. etc... 72 | 73 | If you send a message of magic word 'ding', 74 | you will get a invitation to join my own room! 75 | __________________________________________________ 76 | 77 | Hope you like it, and you are very welcome to 78 | upgrade me for more super powers! 79 | 80 | Please wait... I'm trying to login in... 81 | 82 | ` 83 | console.log(welcome) 84 | const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE }) 85 | 86 | bot 87 | .on('scan', (qrcode, status) => { 88 | qrTerm.generate(qrcode, { small: true }) 89 | console.log(`${qrcode}\n[${status}] Scan QR Code in above url to login: `) 90 | }) 91 | .on('logout' , user => log.info('Bot', `"${user.name()}" logouted`)) 92 | .on('error' , e => log.info('Bot', 'error: %s', e)) 93 | 94 | /** 95 | * Global Event: login 96 | * 97 | * do initialization inside this event. 98 | * (better to set a timeout, for browser need time to download other data) 99 | */ 100 | .on('login', async function(user) { 101 | let msg = `${user.name()} logined` 102 | 103 | log.info('Bot', msg) 104 | await this.say(msg) 105 | 106 | msg = `setting to manageDingRoom() after 3 seconds ... ` 107 | log.info('Bot', msg) 108 | await this.say(msg) 109 | 110 | setTimeout(manageDingRoom.bind(this), 3000) 111 | }) 112 | 113 | /** 114 | * Global Event: room-join 115 | */ 116 | .on('room-join', async function(room, inviteeList, inviter) { 117 | log.info( 'Bot', 'EVENT: room-join - Room "%s" got new member "%s", invited by "%s"', 118 | await room.topic(), 119 | inviteeList.map(c => c.name()).join(','), 120 | inviter.name(), 121 | ) 122 | console.log('bot room-join room id:', room.id) 123 | const topic = await room.topic() 124 | await room.say(`welcome to "${topic}"!`, inviteeList[0]) 125 | }) 126 | 127 | /** 128 | * Global Event: room-leave 129 | */ 130 | .on('room-leave', async function(room, leaverList) { 131 | log.info('Bot', 'EVENT: room-leave - Room "%s" lost member "%s"', 132 | await room.topic(), 133 | leaverList.map(c => c.name()).join(','), 134 | ) 135 | const topic = await room.topic() 136 | const name = leaverList[0] ? leaverList[0].name() : 'no contact!' 137 | await room.say(`kick off "${name}" from "${topic}"!` ) 138 | }) 139 | 140 | /** 141 | * Global Event: room-topic 142 | */ 143 | .on('room-topic', async function(room, topic, oldTopic, changer) { 144 | try { 145 | log.info('Bot', 'EVENT: room-topic - Room "%s" change topic from "%s" to "%s" by member "%s"', 146 | room, 147 | oldTopic, 148 | topic, 149 | changer, 150 | ) 151 | await room.say(`room-topic - change topic from "${oldTopic}" to "${topic}" by member "${changer.name()}"` ) 152 | } catch (e) { 153 | log.error('Bot', 'room-topic event exception: %s', e.stack) 154 | } 155 | }) 156 | 157 | /** 158 | * Global Event: message 159 | */ 160 | .on('message', async function(msg) { 161 | if (msg.age() > 3 * 60) { 162 | log.info('Bot', 'on(message) skip age("%d") > 3 * 60 seconds: "%s"', msg.age(), msg) 163 | return 164 | } 165 | 166 | const room = msg.room() 167 | const from = msg.from() 168 | const text = msg.text() 169 | 170 | if (!from) { 171 | return 172 | } 173 | 174 | console.log((room ? '[' + await room.topic() + ']' : '') 175 | + '<' + from.name() + '>' 176 | + ':' + msg, 177 | ) 178 | 179 | if (msg.self()) { 180 | return // skip self 181 | } 182 | 183 | /** 184 | * `dong` will be the magic(toggle) word: 185 | * bot will quit current room by herself. 186 | */ 187 | if (/^dong$/i.test(text)) { 188 | if (room) { 189 | await room.say('You said dong in the room, I will quit by myself!', from) 190 | await room.quit() 191 | } else { 192 | await from.say('Nothing to do. If you say "dong" in a room, I will quit from the room.') 193 | } 194 | return 195 | } 196 | 197 | /** 198 | * `ding` will be the magic(toggle) word: 199 | * 1. say ding first time, will got a room invitation 200 | * 2. say ding in room, will be removed out 201 | */ 202 | if (/^ding$/i.test(text)) { 203 | 204 | /** 205 | * in-room message 206 | */ 207 | if (room) { 208 | if (/^ding/i.test(await room.topic())) { 209 | /** 210 | * move contact out of room 211 | */ 212 | await getOutRoom(from, room) 213 | } 214 | 215 | /** 216 | * peer to peer message 217 | */ 218 | } else { 219 | 220 | /** 221 | * find room name start with "ding" 222 | */ 223 | try { 224 | const dingRoom = await this.Room.find({ topic: /^ding/i }) 225 | if (dingRoom) { 226 | /** 227 | * room found 228 | */ 229 | log.info('Bot', 'onMessage: got dingRoom: "%s"', await dingRoom.topic()) 230 | 231 | if (await dingRoom.has(from)) { 232 | /** 233 | * speaker is already in room 234 | */ 235 | const topic = await dingRoom.topic() 236 | log.info('Bot', 'onMessage: sender has already in dingRoom') 237 | await dingRoom.say(`I found you have joined in room "${topic}"!`, from) 238 | await from.say(`no need to ding again, because you are already in room: "${topic}"`) 239 | // sendMessage({ 240 | // content: 'no need to ding again, because you are already in ding room' 241 | // , to: sender 242 | // }) 243 | 244 | } else { 245 | /** 246 | * put speaker into room 247 | */ 248 | log.info('Bot', 'onMessage: add sender("%s") to dingRoom("%s")', from.name(), dingRoom.topic()) 249 | await from.say('ok, I will put you in ding room!') 250 | await putInRoom(from, dingRoom) 251 | } 252 | 253 | } else { 254 | /** 255 | * room not found 256 | */ 257 | log.info('Bot', 'onMessage: dingRoom not found, try to create one') 258 | /** 259 | * create the ding room 260 | */ 261 | const newRoom = await createDingRoom(from) 262 | console.log('createDingRoom id:', newRoom.id) 263 | /** 264 | * listen events from ding room 265 | */ 266 | await manageDingRoom() 267 | } 268 | } catch (e) { 269 | log.error(e) 270 | } 271 | } 272 | } 273 | }) 274 | .start() 275 | .catch(e => console.error(e)) 276 | 277 | async function manageDingRoom() { 278 | log.info('Bot', 'manageDingRoom()') 279 | 280 | /** 281 | * Find Room 282 | */ 283 | try { 284 | const room = await bot.Room.find({ topic: /^ding/i }) 285 | if (!room) { 286 | log.warn('Bot', 'there is no room topic ding(yet)') 287 | return 288 | } 289 | log.info('Bot', 'start monitor "ding" room join/leave/topic event') 290 | 291 | /** 292 | * Event: Join 293 | */ 294 | room.on('join', function(inviteeList, inviter) { 295 | log.verbose('Bot', 'Room EVENT: join - "%s", "%s"', 296 | inviteeList.map(c => c.name()).join(', '), 297 | inviter.name(), 298 | ) 299 | console.log('room.on(join) id:', this.id) 300 | checkRoomJoin.call(this, room, inviteeList, inviter) 301 | }) 302 | 303 | /** 304 | * Event: Leave 305 | */ 306 | room.on('leave', (leaverList, remover) => { 307 | log.info('Bot', 'Room EVENT: leave - "%s" leave(remover "%s"), byebye', leaverList.join(','), remover || 'unknown') 308 | }) 309 | 310 | /** 311 | * Event: Topic Change 312 | */ 313 | room.on('topic', (topic, oldTopic, changer) => { 314 | log.info('Bot', 'Room EVENT: topic - changed from "%s" to "%s" by member "%s"', 315 | oldTopic, 316 | topic, 317 | changer.name(), 318 | ) 319 | }) 320 | } catch (e) { 321 | log.warn('Bot', 'Room.find rejected: "%s"', e.stack) 322 | } 323 | } 324 | 325 | async function checkRoomJoin(room, inviteeList, inviter) { 326 | log.info('Bot', 'checkRoomJoin("%s", "%s", "%s")', 327 | await room.topic(), 328 | inviteeList.map(c => c.name()).join(','), 329 | inviter.name(), 330 | ) 331 | 332 | try { 333 | // let to, content 334 | const userSelf = bot.userSelf() 335 | 336 | if (inviter.id !== userSelf.id) { 337 | 338 | await room.say('RULE1: Invitation is limited to me, the owner only. Please do not invit people without notify me.', 339 | inviter, 340 | ) 341 | await room.say('Please contact me: by send "ding" to me, I will re-send you a invitation. Now I will remove you out, sorry.', 342 | inviteeList, 343 | ) 344 | 345 | await room.topic('ding - warn ' + inviter.name()) 346 | setTimeout( 347 | _ => inviteeList.forEach(c => room.del(c)), 348 | 10 * 1000, 349 | ) 350 | 351 | } else { 352 | 353 | await room.say('Welcome to my room! :)') 354 | 355 | let welcomeTopic 356 | welcomeTopic = inviteeList.map(c => c.name()).join(', ') 357 | await room.topic('ding - welcome ' + welcomeTopic) 358 | } 359 | 360 | } catch (e) { 361 | log.error('Bot', 'checkRoomJoin() exception: %s', e.stack) 362 | } 363 | 364 | } 365 | 366 | async function putInRoom(contact, room) { 367 | log.info('Bot', 'putInRoom("%s", "%s")', contact.name(), await room.topic()) 368 | 369 | try { 370 | await room.add(contact) 371 | setTimeout( 372 | _ => room.say('Welcome ', contact), 373 | 10 * 1000, 374 | ) 375 | } catch (e) { 376 | log.error('Bot', 'putInRoom() exception: ' + e.stack) 377 | } 378 | } 379 | 380 | async function getOutRoom(contact, room) { 381 | log.info('Bot', 'getOutRoom("%s", "%s")', contact, room) 382 | 383 | try { 384 | await room.say('You said "ding" in my room, I will remove you out.') 385 | await room.del(contact) 386 | } catch (e) { 387 | log.error('Bot', 'getOutRoom() exception: ' + e.stack) 388 | } 389 | } 390 | 391 | function getHelperContact() { 392 | log.info('Bot', 'getHelperContact()') 393 | 394 | // create a new room at least need 3 contacts 395 | return bot.Contact.find({ name: HELPER_CONTACT_NAME }) 396 | } 397 | 398 | async function createDingRoom(contact) { 399 | log.info('Bot', 'createDingRoom("%s")', contact) 400 | 401 | try { 402 | const helperContact = await getHelperContact() 403 | 404 | if (!helperContact) { 405 | log.warn('Bot', 'getHelperContact() found nobody') 406 | await contact.say(`You don't have a friend called "${HELPER_CONTACT_NAME}", 407 | because create a new room at least need 3 contacts, please set [HELPER_CONTACT_NAME] in the code first!`) 408 | return 409 | } 410 | 411 | log.info('Bot', 'getHelperContact() ok. got: "%s"', helperContact.name()) 412 | 413 | const contactList = [contact, helperContact] 414 | log.verbose('Bot', 'contactList: "%s"', contactList.join(',')) 415 | 416 | await contact.say(`There isn't ding room. I'm trying to create a room with "${helperContact.name()}" and you`) 417 | const room = await bot.Room.create(contactList, 'ding') 418 | log.info('Bot', 'createDingRoom() new ding room created: "%s"', room) 419 | 420 | await room.topic('ding - created') 421 | await room.say('ding - created') 422 | 423 | return room 424 | 425 | } catch (e) { 426 | log.error('Bot', 'getHelperContact() exception:', e.stack) 427 | throw e 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /examples/advanced/room-say-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Wechaty - https://github.com/chatie/wechaty 4 | * 5 | * @copyright 2016-2018 Huan LI 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | /* tslint:disable:variable-name */ 22 | import qrTerm from 'qrcode-terminal' 23 | 24 | import { 25 | config, 26 | Wechaty, 27 | log, 28 | } from '../mod.js' 29 | 30 | async function main () { 31 | const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE }) 32 | 33 | bot 34 | .on('scan', (qrcode, status) => { 35 | qrTerm.generate(qrcode, { small: true }) 36 | // Generate a QR Code online via 37 | // http://goqr.me/api/doc/create-qr-code/ 38 | const qrcodeImageUrl = [ 39 | 'https://wechaty.js.org/qrcode/', 40 | encodeURIComponent(qrcode), 41 | ].join('') 42 | console.log(`[${status}] ${qrcodeImageUrl}\nScan QR Code above to log in: `) 43 | }) 44 | .on('logout' , user => log.info('Bot', `"${user.name()}" logouted`)) 45 | .on('error' , e => log.info('Bot', 'error: %s', e)) 46 | 47 | /** 48 | * Global Event: login 49 | * 50 | * do initialization inside this event. 51 | * (better to set a timeout, for browser need time to download other data) 52 | */ 53 | .on('login', user => { 54 | console.log(`${user} logined`) 55 | }) 56 | 57 | /** 58 | * Global Event: message 59 | */ 60 | .on('message', async function(msg) { 61 | console.log(msg.toString()) 62 | }) 63 | 64 | await bot.start() 65 | 66 | const searchTopic = process.argv[2] 67 | if (!searchTopic) { 68 | throw new Error('no arg 1 defined as search topic!') 69 | } 70 | 71 | const sayText = process.argv[3] 72 | if (!sayText) { 73 | throw new Error('no arg 2 defined as say text!') 74 | } 75 | 76 | console.log('searching topic: ', searchTopic) 77 | 78 | const searchRegex = new RegExp(searchTopic) 79 | 80 | // wait the bot for logging in 81 | while (!bot.logonoff()) { 82 | await new Promise(r => setTimeout(r, 100)) 83 | } 84 | 85 | const room = await bot.Room.find({ topic: searchRegex }) 86 | 87 | console.log(searchRegex) 88 | 89 | if (!room) { 90 | console.log('not found') 91 | return 92 | } 93 | 94 | console.log(await room.topic(), 'found') 95 | await room.say(sayText) 96 | 97 | return 0 98 | } 99 | 100 | main() 101 | .then(process.exit) 102 | .catch(e => { 103 | console.error(e) 104 | process.exit(1) 105 | }) 106 | -------------------------------------------------------------------------------- /examples/advanced/self-testing-bot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import path from 'path' 20 | 21 | /* tslint:disable:variable-name */ 22 | import qrTerm from 'qrcode-terminal' 23 | import { FileBox } from 'file-box' 24 | 25 | import { 26 | Wechaty, 27 | log, 28 | } from 'wechaty' 29 | 30 | const BOT_QR_CODE_IMAGE_FILE_BOX = FileBox 31 | .fromUrl('https://wechaty.js.org/img/friday-qrcode.svg') 32 | 33 | const bot = Wechaty.instance() 34 | 35 | const welcome = ` 36 | | __ __ _ _ 37 | | \\ \\ / /__ ___| |__ __ _| |_ _ _ 38 | | \\ \\ /\\ / / _ \\/ __| '_ \\ / _\` | __| | | | 39 | | \\ V V / __/ (__| | | | (_| | |_| |_| | 40 | | \\_/\\_/ \\___|\\___|_| |_|\\__,_|\\__|\\__, | 41 | | |___/ 42 | 43 | =============== Powered by Wechaty =============== 44 | -------- https://github.com/chatie/wechaty -------- 45 | Version: ${bot.version(true)} 46 | 47 | I'm a bot, my superpower is talk in Wechat. 48 | 49 | If you send me a 'ding', I will reply you a 'dong'! 50 | __________________________________________________ 51 | 52 | Hope you like it, and you are very welcome to 53 | upgrade me to more superpowers! 54 | 55 | Please wait... I'm trying to login in... 56 | 57 | ` 58 | 59 | console.log(welcome) 60 | 61 | bot 62 | .on('logout' , user => log.info('Bot', `${user.name()} logouted`)) 63 | .on('login' , user => { 64 | log.info('Bot', `${user.name()} login`) 65 | bot.say('Wechaty login').catch(console.error) 66 | }) 67 | .on('scan', (qrcode, status) => { 68 | qrTerm.generate(qrcode, { small: true }) 69 | console.log(`${qrcode}\n[${status}] Scan QR Code in above url to login: `) 70 | }) 71 | .on('message', async msg => { 72 | const from = msg.from() 73 | 74 | if (!from) { 75 | log.info('Bot', 'on(message) skip no-from() message: %s', msg) 76 | return 77 | } 78 | 79 | if (msg.type() !== bot.Message.Type.Text) { 80 | log.info('Bot', 'on(message) skip non-text message: %s', msg) 81 | return 82 | } 83 | 84 | if (msg.age() > 60) { 85 | log.info('Bot', 'on(message) skip message older(%d) than 60 seconds: %s', msg.age(), msg) 86 | return 87 | } 88 | 89 | try { 90 | console.log(msg.toString()) // on(message) exception: Error: no file 91 | const text = msg.text() 92 | 93 | // Room.findAll() 94 | if (/^testRoom$/i.test(text)) { 95 | const roomList = await bot.Room.findAll() 96 | const topicList = await Promise.all( 97 | roomList.map(async room => await room.topic()), 98 | ) 99 | 100 | const totalNum = roomList.length 101 | let n = 0 102 | 103 | const replyText = [ 104 | `Total room number: ${totalNum}`, 105 | ...topicList 106 | .slice(0, 17) 107 | .map(topic => ++n + '. ' + topic), 108 | ].join('\n') 109 | 110 | await msg.say(replyText) 111 | 112 | return 113 | } 114 | 115 | // Contact.findAll() 116 | if (/^testContact$/i.test(text)) { 117 | const contactList = await bot.Contact.findAll() 118 | console.log('bot.Contact.findAll() done.') 119 | 120 | const totalNum = contactList.length 121 | let n = 0 122 | 123 | const replyText = [ 124 | `Total contact number: ${totalNum}`, 125 | contactList 126 | .slice(0, 17) 127 | .map(contact => contact.name()) 128 | .map(name => ++n + '. ' + name), 129 | ].join('\n') 130 | 131 | await from.say(replyText) 132 | 133 | return 134 | } 135 | 136 | if (/^fcontact$/.test(text)) { 137 | console.log('begin to check msg forward contact') 138 | // const contact = await bot.Contact.find({ 139 | // name: /李佳芮/, 140 | // }) 141 | const contact = await bot.Contact.load('qq512436430') 142 | if (!contact) { 143 | console.error('contact not found') 144 | return 145 | } 146 | await msg.forward(contact) 147 | return 148 | } 149 | 150 | if (/^froom$/.test(text)) { 151 | console.log('begin to check msg forward room') 152 | const dingRoom = await bot.Room.find({ topic: /^ding/i }) 153 | if (dingRoom) { 154 | await msg.forward(dingRoom) 155 | } else { 156 | await msg.say('Cannot find dingRoom, please create a ding room first!') 157 | } 158 | return 159 | } 160 | 161 | if (/^geta$/.test(text)) { 162 | console.log('begin to check get contact alias') 163 | await from.say(from.alias() || 'no alias') 164 | return 165 | } 166 | 167 | if (/^seta$/.test(text)) { 168 | console.log('begin to check set contact alias') 169 | await from.alias('wechaty-alias') 170 | setTimeout(async () => { 171 | await from.say(from.alias() || 'no alais') 172 | }, 3 * 1000) 173 | return 174 | } 175 | 176 | if (/^avatar$/.test(text)) { 177 | console.log('begin to check get contact avatar') 178 | const file = await from.avatar() 179 | await from.say(file) 180 | return 181 | } 182 | 183 | if (/^(ding|ping|bing|code)$/i.test(msg.text()) /*&& !msg.self()*/) { 184 | /** 185 | * 1. reply 'dong' 186 | */ 187 | log.info('Bot', 'REPLY: dong') 188 | await msg.say('dong') 189 | 190 | const joinWechaty = `Join Wechaty Developers' Community\n\n` + 191 | `Wechaty is used in many ChatBot projects by hundreds of developers.\n\n` + 192 | `If you want to talk with other developers, just scan the following QR Code in WeChat with secret code: wechaty,\n\n` + 193 | `you can join our Wechaty Developers' Home at once` 194 | await msg.say(joinWechaty) 195 | 196 | /** 197 | * 2. reply qrcode image 198 | */ 199 | // const fileBox = FileBox.packStream( 200 | // fs.createReadStream(BOT_QR_CODE_IMAGE_FILE), 201 | // BOT_QR_CODE_IMAGE_FILE, 202 | // ) 203 | 204 | log.info('Bot', 'REPLY: %s', BOT_QR_CODE_IMAGE_FILE_BOX) 205 | await msg.say(BOT_QR_CODE_IMAGE_FILE_BOX) 206 | 207 | /** 208 | * 3. reply 'scan now!' 209 | */ 210 | await msg.say('Scan now, because other Wechaty developers want to talk with you too!\n\n(secret code: wechaty)') 211 | 212 | } 213 | } catch (e) { 214 | log.error('Bot', 'on(message) exception: %s' , e) 215 | console.error(e) 216 | } 217 | }) 218 | 219 | bot.on('error', async e => { 220 | log.error('Bot', 'error: %s', e) 221 | if (bot.logonoff()) { 222 | await bot.say('Wechaty error: ' + e.message).catch(console.error) 223 | } 224 | // await bot.stop() 225 | }) 226 | 227 | // let killChrome: NodeJS.SignalsListener 228 | 229 | bot.start() 230 | .then(() => { 231 | const listenerList = process.listeners('SIGINT') 232 | for (const listener of listenerList) { 233 | if (listener.name === 'killChrome') { 234 | process.removeListener('SIGINT', listener) 235 | // killChrome = listener 236 | } 237 | } 238 | }) 239 | .catch(async e => { 240 | log.error('Bot', 'start() fail: %s', e) 241 | await bot.stop() 242 | process.exit(-1) 243 | }) 244 | 245 | // let quiting = false 246 | // finis((code, signal) => { 247 | // log.info('Bot', 'finis(%s, %s)', code, signal) 248 | 249 | // if (!bot.logonoff()) { 250 | // log.info('Bot', 'finis() bot had been already stopped') 251 | // doExit(code) 252 | // } 253 | 254 | // if (quiting) { 255 | // log.warn('Bot', 'finis() already quiting... return and wait...') 256 | // return 257 | // } 258 | 259 | // quiting = true 260 | // let done = false 261 | // // let checkNum = 0 262 | 263 | // const exitMsg = `Wechaty will exit ${code} because of ${signal} ` 264 | 265 | // log.info('Bot', 'finis() broadcast quiting message for bot') 266 | // bot.say(exitMsg) 267 | // // .then(() => bot.stop()) 268 | // .catch(e => log.error('Bot', 'finis() catch rejection: %s', e)) 269 | // .then(() => done = true) 270 | 271 | // setImmediate(checkForExit) 272 | 273 | // function checkForExit() { 274 | // // if (checkNum++ % 100 === 0) { 275 | // log.info('Bot', 'finis() checkForExit() checking done: %s', done) 276 | // // } 277 | // if (done) { 278 | // log.info('Bot', 'finis() checkForExit() done!') 279 | // setTimeout(() => doExit(code), 1000) // delay 1 second 280 | // return 281 | // } 282 | // // death loop to wait for `done` 283 | // // process.nextTick(checkForExit) 284 | // // setImmediate(checkForExit) 285 | // setTimeout(checkForExit, 100) 286 | // } 287 | // }) 288 | 289 | // function doExit(code: number): void { 290 | // log.info('Bot', 'doExit(%d)', code) 291 | // if (killChrome) { 292 | // killChrome('SIGINT') 293 | // } 294 | // process.exit(code) 295 | // } 296 | 297 | // process.on('SIGINT', function() { 298 | // console.log('Nice SIGINT-handler') 299 | // const listeners = process.listeners('SIGINT') 300 | // for (let i = 0; i < listeners.length; i++) { 301 | // console.log(listeners[i].toString()) 302 | // } 303 | // }) 304 | -------------------------------------------------------------------------------- /examples/advanced/shell-bot.ts: -------------------------------------------------------------------------------- 1 | import repl from 'repl' 2 | 3 | import { Wechaty } from 'wechaty' 4 | 5 | async function main () { 6 | const wechaty = WechatyBuilder.build({ 7 | name: 'shell-bot', 8 | }) 9 | 10 | console.info('Starting...') 11 | 12 | await wechaty.start() 13 | 14 | const shell = repl.start('wechaty> ') 15 | shell.context.wechaty = wechaty 16 | shell.on('exit', () => wechaty.stop()) 17 | } 18 | 19 | main() 20 | .catch(console.error) 21 | -------------------------------------------------------------------------------- /examples/basic/contact-bot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/wechaty/wechaty 3 | * 4 | * @copyright 2016-now Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import qrTerm from 'qrcode-terminal' 20 | 21 | import { 22 | log, 23 | WechatyBuilder, 24 | } from 'wechaty' 25 | 26 | const welcome = ` 27 | =============== Powered by Wechaty =============== 28 | -------- https://github.com/Chatie/wechaty -------- 29 | 30 | I can list all your contacts with weixn id & name 31 | __________________________________________________ 32 | 33 | Please wait... I'm trying to login in... 34 | ` 35 | 36 | console.log(welcome) 37 | const bot = WechatyBuilder.build() 38 | 39 | bot.on('scan', onScan) 40 | bot.on('login', onLogin) 41 | bot.on('logout', onLogout) 42 | bot.on('error', onError) 43 | 44 | bot.start() 45 | .catch(console.error) 46 | 47 | function onScan (qrcode, status) { 48 | qrTerm.generate(qrcode, { small: true }) // show qrcode on console 49 | } 50 | 51 | function onLogin (user) { 52 | console.log(`${user} login`) 53 | main() 54 | } 55 | 56 | function onLogout (user) { 57 | console.log(`${user} logout`) 58 | } 59 | 60 | function onError (e) { 61 | console.error(e) 62 | } 63 | 64 | /** 65 | * Main Contact Bot 66 | */ 67 | async function main() { 68 | const contactList = await bot.Contact.findAll() 69 | 70 | log.info('Bot', '#######################') 71 | log.info('Bot', 'Contact number: %d\n', contactList.length) 72 | 73 | /** 74 | * official contacts list 75 | */ 76 | for (let i = 0; i < contactList.length; i++) { 77 | const contact = contactList[i] 78 | if (contact.type() === bot.Contact.Type.Official) { 79 | log.info('Bot', `official ${i}: ${contact}`) 80 | } 81 | } 82 | 83 | /** 84 | * personal contact list 85 | */ 86 | 87 | for (let i = 0; i < contactList.length; i++) { 88 | const contact = contactList[i] 89 | if (contact.type() === bot.Contact.Type.Personal) { 90 | log.info('Bot', `personal ${i}: ${contact.name()} : ${contact.id}`) 91 | } 92 | } 93 | 94 | const MAX = 17 95 | for (let i = 0; i < contactList.length; i++ ) { 96 | const contact = contactList[i] 97 | 98 | /** 99 | * Save avatar to file like: "1-name.jpg" 100 | */ 101 | const file = await contact.avatar() 102 | const name = file.name 103 | await file.toFile(name, true) 104 | 105 | log.info('Bot', 'Contact: "%s" with avatar file: "%s"', 106 | contact.name(), 107 | name, 108 | ) 109 | 110 | if (i > MAX) { 111 | log.info('Bot', 'Contacts too many, I only show you the first %d ... ', MAX) 112 | break 113 | } 114 | } 115 | 116 | const SLEEP = 7 117 | log.info('Bot', 'I will re-dump contact weixin id & names after %d second... ', SLEEP) 118 | setTimeout(main, SLEEP * 1000) 119 | 120 | } 121 | -------------------------------------------------------------------------------- /examples/basic/ding-dong-bot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/wechaty/wechaty 3 | * 4 | * @copyright 2016-now Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import 'dotenv/config.js' 20 | 21 | import { 22 | WechatyBuilder, 23 | ScanStatus, 24 | log, 25 | } from 'wechaty' 26 | import qrcodeTerminal from 'qrcode-terminal' 27 | 28 | function onScan (qrcode, status) { 29 | if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) { 30 | qrcodeTerminal.generate(qrcode, { small: true }) // show qrcode on console 31 | 32 | const qrcodeImageUrl = [ 33 | 'https://wechaty.js.org/qrcode/', 34 | encodeURIComponent(qrcode), 35 | ].join('') 36 | 37 | log.info('StarterBot', 'onScan: %s(%s) - %s', ScanStatus[status], status, qrcodeImageUrl) 38 | 39 | } else { 40 | log.info('StarterBot', 'onScan: %s(%s)', ScanStatus[status], status) 41 | } 42 | } 43 | 44 | function onLogin (user) { 45 | log.info('StarterBot', '%s login', user) 46 | } 47 | 48 | function onLogout (user) { 49 | log.info('StarterBot', '%s logout', user) 50 | } 51 | 52 | async function onMessage (msg) { 53 | log.info('StarterBot', msg.toString()) 54 | 55 | if (msg.text() === 'ding') { 56 | await msg.say('dong') 57 | } 58 | } 59 | 60 | const bot = WechatyBuilder.build({ 61 | name: 'ding-dong-bot', 62 | /** 63 | * How to set Wechaty Puppet Provider: 64 | * 65 | * 1. Specify a `puppet` option when instantiating Wechaty. (like `{ puppet: 'wechaty-puppet-padlocal' }`, see below) 66 | * 1. Set the `WECHATY_PUPPET` environment variable to the puppet NPM module name. (like `wechaty-puppet-padlocal`) 67 | * 68 | * You can use the following providers: 69 | * - wechaty-puppet-wechat (no token required) 70 | * - wechaty-puppet-padlocal (token required) 71 | * - wechaty-puppet-service (token required, see: ) 72 | * - etc. see: 73 | */ 74 | // puppet: 'wechaty-puppet-wechat', 75 | }) 76 | 77 | bot.on('scan', onScan) 78 | bot.on('login', onLogin) 79 | bot.on('logout', onLogout) 80 | bot.on('message', onMessage) 81 | 82 | bot.start() 83 | .then(() => log.info('StarterBot', 'Starter Bot Started.')) 84 | .catch(e => log.error('StarterBot', e)) 85 | -------------------------------------------------------------------------------- /examples/basic/the-worlds-shortest-chatbot-code-in-6-lines.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/wechaty/wechaty 3 | * 4 | * @copyright 2016-now Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import { WechatyBuilder } from 'wechaty' 20 | 21 | WechatyBuilder.singleton() // Singleton 22 | .on('scan', (qrcode, status) => console.log(`Scan QR Code to login: ${status}\nhttps://wechaty.js.org/qrcode/${encodeURIComponent(qrcode)}`)) 23 | .on('login', user => console.log(`User ${user} logined`)) 24 | .on('message', message => console.log(`Message: ${message}`)) 25 | .start() 26 | -------------------------------------------------------------------------------- /examples/cqrs/ding-dong-bot.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /** 3 | * Wechaty Open Source Software - https://github.com/wechaty 4 | * 5 | * @copyright 2016 Huan LI (李卓桓) , and 6 | * Wechaty Contributors . 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | */ 21 | import * as WECHATY from 'wechaty' 22 | import * as CQRS from 'wechaty-cqrs' 23 | 24 | import { 25 | of, 26 | merge, 27 | defer, 28 | } from 'rxjs' 29 | import { 30 | filter, 31 | ignoreElements, 32 | map, 33 | mergeMap, 34 | takeUntil, 35 | tap, 36 | finalize, 37 | switchMap, 38 | } from 'rxjs/operators' 39 | 40 | 41 | const onScan$ = (source$: CQRS.BusObs) => CQRS.events$.scanReceivedEvent$(source$).pipe( 42 | map(scanReceivedEvent => scanReceivedEvent.payload), 43 | tap(({ qrcode, status }) => { 44 | const qrStatusList = [ 45 | WECHATY.types.ScanStatus.Waiting, 46 | WECHATY.types.ScanStatus.Timeout, 47 | ] 48 | if (qrcode && qrStatusList.includes(status)) { 49 | const qrcodeImageUrl = [ 50 | 'https://wechaty.js.org/qrcode/', 51 | encodeURIComponent(qrcode), 52 | ].join('') 53 | 54 | console.info('onScan: %s(%s) - %s', WECHATY.types.ScanStatus[status], status, qrcodeImageUrl) 55 | } else { 56 | console.info('onScan: %s(%s)', WECHATY.types.ScanStatus[status], status) 57 | } 58 | }), 59 | ) 60 | 61 | const onMessage$ = (bus$: CQRS.Bus) => CQRS.events$.messageReceivedEvent$(bus$).pipe( 62 | tap(messageReceivedEvent => console.info('messageReceivedEvent', messageReceivedEvent)), 63 | mergeMap(messageReceivedEvent => of(messageReceivedEvent.payload.messageId).pipe( 64 | /** 65 | * message -> sayable 66 | */ 67 | map(messageId => CQRS.duck.actions.getSayablePayloadQuery( 68 | messageReceivedEvent.meta.puppetId, 69 | messageId, 70 | )), 71 | mergeMap(CQRS.execute$(bus$)(CQRS.duck.actions.getSayablePayloadQuery)), 72 | map(sayablePayloadGotMessage => sayablePayloadGotMessage.payload), 73 | filter(Boolean), 74 | tap(sayable => console.info('sayable:', sayable)), 75 | 76 | /** 77 | * sayable -> ding 78 | */ 79 | filter(CQRS.sayables.isText), 80 | map(sayable => sayable.payload.text), 81 | filter(text => text === 'ding'), 82 | tap(text => console.info('text:', text)), 83 | 84 | mergeMap(() => of(messageReceivedEvent).pipe( 85 | /** 86 | * ding -> talkerId 87 | */ 88 | map(messageReceivedEvent => CQRS.duck.actions.getMessagePayloadQuery(messageReceivedEvent.meta.puppetId, messageReceivedEvent.payload.messageId)), 89 | mergeMap(CQRS.execute$(bus$)(CQRS.duck.actions.getMessagePayloadQuery)), 90 | /** 91 | * Huan(202203): `.fromId` deprecated, will be removed after v2.0 92 | */ 93 | map(messagePayloadGotMmessage => messagePayloadGotMmessage.payload?.talkerId || messagePayloadGotMmessage.payload?.fromId), 94 | filter(Boolean), 95 | tap(talkerId => console.info('talkerId:', talkerId)), 96 | 97 | /** 98 | * talkerId -> command 99 | */ 100 | map(talkerId => CQRS.duck.actions.sendMessageCommand( 101 | messageReceivedEvent.meta.puppetId, 102 | talkerId, 103 | CQRS.sayables.text('dong'), 104 | )), 105 | tap(command => console.info('sendMessageCommand:', command)), 106 | 107 | /** 108 | * execute command (return MessageSentMessage) 109 | */ 110 | mergeMap(CQRS.execute$(bus$)(CQRS.duck.actions.sendMessageCommand)), 111 | )), 112 | )), 113 | ) 114 | 115 | async function cqrsWechaty () { 116 | const wechaty = WECHATY.WechatyBuilder.build({ name: 'ding-dong-bot' }) 117 | await wechaty.init() 118 | 119 | // wechaty.on('message', m => console.info('message:', String(m))) 120 | 121 | const bus$ = CQRS.from(wechaty) 122 | 123 | return { 124 | bus$, 125 | puppetId: wechaty.puppet.id, 126 | } 127 | } 128 | 129 | async function main () { 130 | const { 131 | bus$, 132 | puppetId, 133 | } = await cqrsWechaty() 134 | 135 | const onStartedEvent$ = (bus$: CQRS.Bus) => CQRS.events$.startedEvent$(bus$).pipe( 136 | switchMap(() => merge( 137 | onScan$(bus$), 138 | onMessage$(bus$), 139 | ).pipe( 140 | takeUntil(CQRS.events$.stoppedEvent$(bus$)), 141 | )), 142 | ) 143 | 144 | const main$ = defer(() => of(CQRS.duck.actions.startCommand(puppetId))).pipe( 145 | mergeMap(startCommand => merge( 146 | onStartedEvent$(bus$), 147 | CQRS.execute$(bus$)(CQRS.duck.actions.startCommand)(startCommand), 148 | )), 149 | ignoreElements(), 150 | finalize(() => bus$.next(CQRS.duck.actions.stopCommand(puppetId))), 151 | ) 152 | 153 | /** 154 | * Enable logging all bus events to console 155 | */ 156 | // bus$.subscribe(event => console.info('bus$ event:', event)) 157 | 158 | /** 159 | * Bootstrap the main system 160 | */ 161 | main$.subscribe() 162 | } 163 | 164 | /** 165 | * No top-level await here: for CJS compatible when building Dual-ESM-CJS module 166 | */ 167 | main() 168 | .catch(console.error) 169 | -------------------------------------------------------------------------------- /examples/ding-dong-bot.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /** 3 | * Wechaty - Conversational RPA SDK for Chatbot Makers. 4 | * - https://github.com/wechaty/wechaty 5 | */ 6 | // https://stackoverflow.com/a/42817956/1123955 7 | // https://github.com/motdotla/dotenv/issues/89#issuecomment-587753552 8 | import 'dotenv/config.js' 9 | 10 | import { 11 | Contact, 12 | Message, 13 | ScanStatus, 14 | WechatyBuilder, 15 | log, 16 | } from 'wechaty' 17 | 18 | import qrcodeTerminal from 'qrcode-terminal' 19 | 20 | function onScan (qrcode: string, status: ScanStatus) { 21 | if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) { 22 | const qrcodeImageUrl = [ 23 | 'https://wechaty.js.org/qrcode/', 24 | encodeURIComponent(qrcode), 25 | ].join('') 26 | log.info('StarterBot', 'onScan: %s(%s) - %s', ScanStatus[status], status, qrcodeImageUrl) 27 | 28 | qrcodeTerminal.generate(qrcode, { small: true }) // show qrcode on console 29 | 30 | } else { 31 | log.info('StarterBot', 'onScan: %s(%s)', ScanStatus[status], status) 32 | } 33 | } 34 | 35 | function onLogin (user: Contact) { 36 | log.info('StarterBot', '%s login', user) 37 | } 38 | 39 | function onLogout (user: Contact) { 40 | log.info('StarterBot', '%s logout', user) 41 | } 42 | 43 | async function onMessage (msg: Message) { 44 | log.info('StarterBot', msg.toString()) 45 | 46 | if (msg.text() === 'ding') { 47 | await msg.say('dong') 48 | } 49 | } 50 | 51 | const bot = WechatyBuilder.build({ 52 | name: 'ding-dong-bot', 53 | /** 54 | * You can specific `puppet` and `puppetOptions` here with hard coding: 55 | * 56 | puppet: 'wechaty-puppet-wechat', 57 | puppetOptions: { 58 | uos: true, 59 | }, 60 | */ 61 | /** 62 | * How to set Wechaty Puppet Provider: 63 | * 64 | * 1. Specify a `puppet` option when instantiating Wechaty. (like `{ puppet: 'wechaty-puppet-whatsapp' }`, see below) 65 | * 1. Set the `WECHATY_PUPPET` environment variable to the puppet NPM module name. (like `wechaty-puppet-whatsapp`) 66 | * 67 | * You can use the following providers locally: 68 | * - wechaty-puppet-wechat (web protocol, no token required) 69 | * - wechaty-puppet-whatsapp (web protocol, no token required) 70 | * - wechaty-puppet-padlocal (pad protocol, token required) 71 | * - etc. see: 72 | */ 73 | // puppet: 'wechaty-puppet-whatsapp' 74 | 75 | /** 76 | * You can use wechaty puppet provider 'wechaty-puppet-service' 77 | * which can connect to remote Wechaty Puppet Services 78 | * for using more powerful protocol. 79 | * Learn more about services (and TOKEN) from https://wechaty.js.org/docs/puppet-services/ 80 | */ 81 | // puppet: 'wechaty-puppet-service' 82 | // puppetOptions: { 83 | // token: 'xxx', 84 | // } 85 | }) 86 | 87 | bot.on('scan', onScan) 88 | bot.on('login', onLogin) 89 | bot.on('logout', onLogout) 90 | bot.on('message', onMessage) 91 | 92 | bot.start() 93 | .then(() => log.info('StarterBot', 'Starter Bot Started.')) 94 | .catch(e => log.error('StarterBot', e)) 95 | -------------------------------------------------------------------------------- /examples/professional/api-ai-bot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | /** 21 | * Wechaty bot use a ApiAi.com brain 22 | * 23 | * Apply Your Own ApiAi Developer API_KEY at: 24 | * http://www.api.ai 25 | * 26 | * Enjoy! 27 | */ 28 | /* tslint:disable:variable-name */ 29 | import QrcodeTerminal from 'qrcode-terminal' 30 | 31 | import { Brolog as log } from 'brolog' 32 | /* tslint:disable:no-var-requires */ 33 | // import co from 'co' 34 | /* tslint:disable:variable-name */ 35 | import ApiAi from 'apiai' 36 | import { EventEmitter } from 'events' 37 | 38 | /** 39 | * Change `import { ... } from '../.js'` 40 | * to `import { ... } from 'wechaty'` 41 | * when you are runing with Docker or NPM instead of Git Source. 42 | */ 43 | import { 44 | config, 45 | Wechaty, 46 | Message, 47 | } from 'wechaty' 48 | 49 | // log.level = 'verbose' 50 | // log.level = 'silly' 51 | 52 | /** 53 | * 54 | * `7217d7bce18c4bcfbe04ba7bdfaf9c08` for Wechaty demo 55 | * 56 | */ 57 | const APIAI_API_KEY = '7217d7bce18c4bcfbe04ba7bdfaf9c08' 58 | const brainApiAi = ApiAi(APIAI_API_KEY) 59 | 60 | const bot = Wechaty.instance({ profile: config.default.DEFAULT_PROFILE }) 61 | 62 | console.log(` 63 | Welcome to api.AI Wechaty Bot. 64 | Api.AI Doc: https://docs.api.ai/v16/docs/get-started 65 | 66 | Notice: This bot will only active in the room which name contains 'wechaty'. 67 | /* if (m.room() && /Wechaty/i.test(m.room().name())) { */ 68 | 69 | Loading... please wait for QrCode Image Url and then scan to login. 70 | `) 71 | 72 | bot 73 | .on('scan', (qrcode, status) => { 74 | QrcodeTerminal.generate(qrcode) 75 | console.log(`${qrcode}\n[${status}] Scan QR Code in above url to login: `) 76 | }) 77 | .on('login' , user => log.info('Bot', `bot login: ${user}`)) 78 | .on('logout' , user => log.info('Bot', 'bot %s logout.', user)) 79 | .on('message', async m => { 80 | if (m.self()) { return } 81 | 82 | // co(function* () { 83 | // const msg = yield m.load() 84 | const room = m.room() 85 | 86 | if (room && /Wechaty/i.test(await room.topic())) { 87 | log.info('Bot', 'talk: %s' , m) 88 | talk(m) 89 | } else { 90 | log.info('Bot', 'recv: %s' , m) 91 | } 92 | // }) 93 | // .catch(e => log.error('Bot', 'on message rejected: %s' , e)) 94 | }) 95 | 96 | bot.start() 97 | .catch(async e => { 98 | log.error('Bot', 'init() fail:' + e) 99 | await bot.stop() 100 | process.exit(-1) 101 | }) 102 | 103 | class Talker extends EventEmitter { 104 | private thinker: (text: string) => Promise 105 | private obj: { 106 | text: string[], 107 | time: number[], 108 | } 109 | private timer?: NodeJS.Timer 110 | 111 | constructor( 112 | thinker: (text: string) => Promise, 113 | ) { 114 | log.verbose('Talker()') 115 | super() 116 | this.thinker = thinker 117 | this.obj = { 118 | text: [], 119 | time: [], 120 | } 121 | } 122 | 123 | public save(text: string) { 124 | log.verbose('Talker', 'save(%s)', text) 125 | this.obj.text.push(text) 126 | this.obj.time.push(Date.now()) 127 | } 128 | public load() { 129 | const text = this.obj.text.join(', ') 130 | log.verbose('Talker', 'load(%s)', text) 131 | this.obj.text = [] 132 | this.obj.time = [] 133 | return text 134 | } 135 | 136 | public updateTimer(delayTime?: number) { 137 | delayTime = delayTime || this.delayTime() 138 | log.verbose('Talker', 'updateTimer(%s)', delayTime) 139 | 140 | if (this.timer) { clearTimeout(this.timer) } 141 | this.timer = setTimeout(this.say.bind(this), delayTime, 3) 142 | } 143 | 144 | public hear(text: string) { 145 | log.verbose('Talker', `hear(${text})`) 146 | this.save(text) 147 | this.updateTimer() 148 | } 149 | public async say() { 150 | log.verbose('Talker', 'say()') 151 | const text = this.load() 152 | await this.thinker(text) 153 | .then(reply => this.emit('say', reply)) 154 | this.timer = undefined 155 | } 156 | 157 | public delayTime() { 158 | const minDelayTime = 5000 159 | const maxDelayTime = 15000 160 | const delayTime = Math.floor(Math.random() * (maxDelayTime - minDelayTime)) + minDelayTime 161 | return delayTime 162 | } 163 | } 164 | 165 | /* tslint:disable:variable-name */ 166 | const Talkers: { 167 | [index: string]: Talker, 168 | } = {} 169 | 170 | function talk(m: Message) { 171 | const from = m.from() 172 | const fromId = from && from.id 173 | 174 | const room = m.room() 175 | const roomId = room && room.id 176 | 177 | const content = m.text() 178 | 179 | const talkerName = roomId + (fromId || '') 180 | if (!Talkers[talkerName]) { 181 | Talkers[talkerName] = new Talker(function(text: string) { 182 | return new Promise((resolve, reject) => { 183 | brainApiAi.textRequest(text) 184 | .on('response', function(response: any) { 185 | console.log(response) 186 | /* 187 | { id: 'a09381bb-8195-4139-b49c-a2d03ad5e014', 188 | timestamp: '2016-05-27T17:22:46.597Z', 189 | result: 190 | { source: 'domains', 191 | resolvedQuery: 'hi', 192 | action: 'smalltalk.greetings', 193 | parameters: { simplified: 'hello' },w 194 | metadata: {}, 195 | fulfillment: { speech: 'Hi there.' }, 196 | score: 0 }, 197 | status: { code: 200, errorType: 'success' } } 198 | */ 199 | const reply: string = response.result.fulfillment.speech 200 | if (!reply) { 201 | log.info('ApiAi', `Talker do not want to talk for "${text}"`) 202 | return reject() 203 | } 204 | log.info('ApiAi', 'Talker reply:"%s" for "%s" ', reply, text) 205 | return resolve(reply) 206 | }) 207 | .on('error', function(error: Error) { 208 | log.error('ApiAi', error) 209 | reject(error) 210 | }) 211 | .end() 212 | }) 213 | }) 214 | Talkers[talkerName].on('say', reply => m.say(reply)) 215 | } 216 | Talkers[talkerName].hear(content) 217 | } 218 | -------------------------------------------------------------------------------- /examples/professional/blessed-twins-bot/README.md: -------------------------------------------------------------------------------- 1 | # BLESSED TWINS BOT 2 | 3 | [![blessed twins bot](https://wechaty.github.io/wechaty/images/blessed-twins-bot.png)](https://asciinema.org/a/177857) 4 | 5 | ## How to run 6 | 7 | ```bash 8 | npm install blessed blessed-contrib @types/blessed 9 | ts-node blessed-twins-bot.ts 10 | ``` 11 | 12 | ## See Also 13 | 14 | * Example Source Code: 15 | * Blog: [New Feature: Multi-Instance Support for Wechaty v0.16(WIP)](https://blog.chatie.io/blessed-twins-bot/) 16 | -------------------------------------------------------------------------------- /examples/professional/blessed-twins-bot/bless-twins-bot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty Twins Bot + Blessed Contrib Demo 3 | * Credit: https://github.com/yaronn/blessed-contrib/blob/06a05f107a3b54c91b5200a041ad8c15e6489de9/examples/dashboard.js 4 | */ 5 | import * as blessed from 'blessed' 6 | import * as contrib from 'blessed-contrib' 7 | 8 | import { generate } from 'qrcode-terminal' 9 | 10 | import { 11 | Wechaty, WechatyOptions, 12 | } from 'wechaty' 13 | 14 | const screen = blessed.screen({ 15 | smartCSR: true, 16 | fullUnicode: true, // https://github.com/chjj/blessed/issues/226#issuecomment-188777457 17 | }) 18 | 19 | // create layout and widgets 20 | 21 | const grid = new contrib.grid({rows: 12, cols: 12, screen: screen}) 22 | 23 | /** 24 | * Donut Options 25 | * self.options.radius = options.radius || 14; // how wide is it? over 5 is best 26 | * self.options.arcWidth = options.arcWidth || 4; //width of the donut 27 | * self.options.yPadding = options.yPadding || 2; //padding from the top 28 | */ 29 | const donut = grid.set(8, 8, 4, 2, contrib.donut, 30 | { 31 | label: 'Percent Donut', 32 | radius: 16, 33 | arcWidth: 4, 34 | yPadding: 2, 35 | data: [{label: 'Storage', percent: 87}], 36 | }) 37 | 38 | // const latencyLine = grid.set(8, 8, 4, 2, contrib.line, 39 | // { style: 40 | // { line: "yellow" 41 | // , text: "green" 42 | // , baseline: "black"} 43 | // , xLabelPadding: 3 44 | // , xPadding: 5 45 | // , label: 'Network Latency (sec)'}) 46 | 47 | const gauge = grid.set(8, 10, 2, 2, contrib.gauge, {label: 'Storage', percent: [80, 20]}) 48 | const gaugeTwo = grid.set(2, 9, 2, 3, contrib.gauge, {label: 'Deployment Progress', percent: 80}) 49 | 50 | const sparkline = grid.set(10, 10, 2, 2, contrib.sparkline, 51 | { label: 'Throughput (bits/sec)' 52 | , tags: true 53 | , style: { fg: 'blue', titleFg: 'white' }}) 54 | 55 | const bar = grid.set(4, 6, 4, 3, contrib.bar, 56 | { label: 'Server Utilization (%)' 57 | , barWidth: 4 58 | , barSpacing: 6 59 | , xOffset: 2 60 | , maxHeight: 9}) 61 | 62 | const table = grid.set(4, 9, 4, 3, contrib.table, 63 | { keys: true 64 | , fg: 'green' 65 | , label: 'Active Processes' 66 | , columnSpacing: 1 67 | , columnWidth: [24, 10, 10]}) 68 | 69 | /* 70 | * 71 | * LCD Options 72 | //these options need to be modified epending on the resulting positioning/size 73 | options.segmentWidth = options.segmentWidth || 0.06; // how wide are the segments in % so 50% = 0.5 74 | options.segmentInterval = options.segmentInterval || 0.11; // spacing between the segments in % so 50% = 0.5 75 | options.strokeWidth = options.strokeWidth || 0.11; // spacing between the segments in % so 50% = 0.5 76 | //default display settings 77 | options.elements = options.elements || 3; // how many elements in the display. or how many characters can be displayed. 78 | options.display = options.display || 321; // what should be displayed before anything is set 79 | options.elementSpacing = options.spacing || 4; // spacing between each element 80 | options.elementPadding = options.padding || 2; // how far away from the edges to put the elements 81 | //coloring 82 | options.color = options.color || "white"; 83 | */ 84 | const lcdLineOne = grid.set(0, 9, 2, 3, contrib.lcd, 85 | { 86 | label: 'LCD Test', 87 | segmentWidth: 0.06, 88 | segmentInterval: 0.11, 89 | strokeWidth: 0.1, 90 | elements: 5, 91 | display: 3210, 92 | elementSpacing: 4, 93 | elementPadding: 2, 94 | }, 95 | ) 96 | 97 | const errorsLine = grid.set(0, 6, 4, 3, contrib.line, { 98 | style: { 99 | line: 'red', 100 | text: 'white', 101 | baseline: 'black', 102 | }, 103 | label: 'Errors Rate', 104 | maxY: 60, 105 | showLegend: true, 106 | }) 107 | 108 | const boyConsole = grid.set(0, 0, 6, 6, contrib.log, { 109 | fg: 'green', 110 | selectedFg: 'green', 111 | label: 'Boy Bot', 112 | }) 113 | 114 | const girlConsole = grid.set(6, 0, 6, 6, contrib.log, { 115 | fg: 'red', 116 | selectedFg: 'red', 117 | label: 'Girl Bot', 118 | }) 119 | 120 | const log = grid.set(8, 6, 4, 2, contrib.log, { 121 | fg: 'green', 122 | selectedFg: 'green', 123 | label: 'Server Log', 124 | }) 125 | 126 | // dummy data 127 | const servers = ['US1', 'US2', 'EU1', 'AU1', 'AS1', 'JP1'] 128 | const commands = ['grep', 'node', 'java', 'timer', '~/ls -l', 'netns', 'watchdog', 'gulp', 'tar -xvf', 'awk', 'npm install'] 129 | 130 | // set dummy data on gauge 131 | let gaugePercent = 0 132 | setInterval(function() { 133 | gauge.setData([gaugePercent, 100 - gaugePercent]) 134 | gaugePercent++ 135 | if (gaugePercent >= 100) gaugePercent = 0 136 | }, 200) 137 | 138 | let gaugePercentTwo = 0 139 | setInterval(function() { 140 | gaugeTwo.setData(gaugePercentTwo) 141 | gaugePercentTwo++ 142 | if (gaugePercentTwo >= 100) gaugePercentTwo = 0 143 | }, 200) 144 | 145 | // set dummy data on bar chart 146 | function fillBar() { 147 | const arr: number[] = [] 148 | for (let i = 0; i < servers.length; i++) { 149 | arr.push(Math.round(Math.random() * 10)) 150 | } 151 | bar.setData({titles: servers, data: arr}) 152 | } 153 | fillBar() 154 | setInterval(fillBar, 2000) 155 | 156 | // set dummy data for table 157 | function generateTable() { 158 | const data: any[] = [] 159 | 160 | for (let i = 0; i < 30; i++) { 161 | const row: any[] = [] 162 | row.push(commands[Math.round(Math.random() * (commands.length - 1))]) 163 | row.push(Math.round(Math.random() * 5)) 164 | row.push(Math.round(Math.random() * 100)) 165 | 166 | data.push(row) 167 | } 168 | 169 | table.setData({headers: ['Process', 'Cpu (%)', 'Memory'], data: data}) 170 | } 171 | 172 | generateTable() 173 | table.focus() 174 | setInterval(generateTable, 3000) 175 | 176 | // set log dummy data 177 | setInterval(function() { 178 | const rnd = Math.round(Math.random() * 2) 179 | if (rnd === 0) log.log('starting process ' + commands[Math.round(Math.random() * (commands.length - 1))]) 180 | else if (rnd === 1) log.log('terminating server ' + servers[Math.round(Math.random() * (servers.length - 1))]) 181 | else if (rnd === 2) log.log('avg. wait time ' + Math.random().toFixed(2)) 182 | screen.render() 183 | }, 500) 184 | 185 | // set spark dummy data 186 | const spark1 = [1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5, 4, 4, 5, 4, 1, 5, 1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5] 187 | const spark2 = [4, 4, 5, 4, 1, 5, 1, 2, 5, 2, 1, 5, 4, 4, 5, 4, 1, 5, 1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5, 1, 2, 5, 2, 1, 5] 188 | 189 | refreshSpark() 190 | setInterval(refreshSpark, 1000) 191 | 192 | function refreshSpark() { 193 | spark1.shift() 194 | spark1.push(Math.random() * 5 + 1) 195 | spark2.shift() 196 | spark2.push(Math.random() * 5 + 1) 197 | sparkline.setData(['Server1', 'Server2'], [spark1, spark2]) 198 | } 199 | 200 | // set line charts dummy data 201 | 202 | const errorsData = { 203 | title: 'server 1', 204 | x: ['00:00', '00:05', '00:10', '00:15', '00:20', '00:25'], 205 | y: [30, 50, 70, 40, 50, 20], 206 | } 207 | 208 | // const latencyData = { 209 | // x: ['t1', 't2', 't3', 't4'], 210 | // y: [5, 1, 7, 5], 211 | // } 212 | 213 | setLineData([errorsData], errorsLine) 214 | // setLineData([latencyData], latencyLine) 215 | 216 | setInterval(function() { 217 | setLineData([errorsData], errorsLine) 218 | }, 1500) 219 | 220 | setInterval(function() { 221 | const colors = ['green', 'magenta', 'cyan', 'red', 'blue'] 222 | const text = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'] 223 | 224 | const value = Math.round(Math.random() * 100) 225 | lcdLineOne.setDisplay(value + text[value % 12]!) 226 | lcdLineOne.setOptions({ 227 | color: colors[value % 5], 228 | elementPadding: 4, 229 | }) 230 | screen.render() 231 | }, 1500) 232 | 233 | let pct = 0.00 234 | 235 | function updateDonut() { 236 | if (pct > 0.99) pct = 0.00 237 | let color = 'green' 238 | if (pct >= 0.25) color = 'cyan' 239 | if (pct >= 0.5) color = 'yellow' 240 | if (pct >= 0.75) color = 'red' 241 | donut.setData([ 242 | {percent: parseFloat(((pct + 0.00) % 1) as any).toFixed(2), label: 'storage', 'color': color}, 243 | ]) 244 | pct += 0.01 245 | } 246 | 247 | setInterval(function() { 248 | updateDonut() 249 | screen.render() 250 | }, 500) 251 | 252 | function setLineData(mockData: any, line: any) { 253 | for (let i = 0; i < mockData.length; i++) { 254 | const last = mockData[i].y[mockData[i].y.length - 1] 255 | mockData[i].y.shift() 256 | const num = Math.max(last + Math.round(Math.random() * 10) - 5, 10) 257 | mockData[i].y.push(num) 258 | } 259 | 260 | line.setData(mockData) 261 | } 262 | 263 | screen.key(['escape', 'q', 'C-c'], function(ch, key) { 264 | console.log(ch, key) 265 | return process.exit(0) 266 | }) 267 | 268 | // fixes https://github.com/yaronn/blessed-contrib/issues/10 269 | screen.on('resize', function() { 270 | donut.emit('attach') 271 | gauge.emit('attach') 272 | gaugeTwo.emit('attach') 273 | sparkline.emit('attach') 274 | bar.emit('attach') 275 | table.emit('attach') 276 | lcdLineOne.emit('attach') 277 | errorsLine.emit('attach') 278 | boyConsole.emit('attach') 279 | girlConsole.emit('attach') 280 | log.emit('attach') 281 | }) 282 | 283 | screen.render() 284 | 285 | // setInterval(() => { 286 | // boyConsole.log('boy') 287 | // girlConsole.log('girl') 288 | // console.log('zixia') 289 | // }, 500) 290 | 291 | /** 292 | * 293 | * 294 | * 295 | * Wechaty multi instance support example: 296 | * boy & girl twins 297 | * 298 | * 299 | * 300 | */ 301 | const boy = WechatyBuilder.build({ profile: 'boy' } as WechatyOptions) 302 | const girl = WechatyBuilder.build({ profile: 'girl' } as WechatyOptions) 303 | 304 | startBot(boy, boyConsole) 305 | startBot(girl, girlConsole) 306 | 307 | function startBot(bot: Wechaty, logElement: any) { 308 | // logElement.log('Initing...') 309 | bot 310 | .on('logout' , user => logElement.log(`${user.name()} logouted`)) 311 | .on('login' , user => { 312 | logElement.setContent('') 313 | logElement.log(`${user.name()} login`) 314 | bot.say('Wechaty login').catch(console.error) 315 | logElement.setLabel(logElement._label.content + ' - ' + user.name()) 316 | }) 317 | .on('scan', (qrcode) => { 318 | generate( 319 | qrcode, 320 | { 321 | small: true, 322 | }, 323 | (asciiart: string) => logElement.setContent(asciiart), 324 | ) 325 | }) 326 | .on('message', async m => { 327 | logElement.log(m.toString()) 328 | }) 329 | 330 | bot.start() 331 | .catch(async e => { 332 | logElement.log(`start() fail: ${e}`) 333 | await bot.stop() 334 | process.exit(-1) 335 | }) 336 | 337 | bot.on('error', async e => { 338 | logElement.log(`error: ${e}`) 339 | if (bot.logonoff()) { 340 | await bot.say('Wechaty error: ' + e.message).catch(console.error) 341 | } 342 | // await bot.stop() 343 | }) 344 | } 345 | -------------------------------------------------------------------------------- /examples/professional/chatgpt-bot/README.md: -------------------------------------------------------------------------------- 1 | # Wechaty ChatGPT 2 | 3 | This is a wechaty implementation of ChatBot powered by OpenAI ChatGPT 4 | 5 | ## Setup 6 | 7 | Modify `dotenv` file to add your openai_api_key as well as the service_token 8 | ``` 9 | OPENAI_API_KEY="" 10 | WECHATY_PUPPET_SERVICE_TOKEN="" 11 | ``` 12 | 13 | ```shell 14 | npm install 15 | mv dotenv .env 16 | ``` 17 | 18 | ## Run 19 | 20 | ```shell 21 | npm run start 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/professional/chatgpt-bot/bot.ts: -------------------------------------------------------------------------------- 1 | // Importing the Wechaty npm package 2 | import { Wechaty,WechatyBuilder, Contact, Message, ScanStatus, log } from "wechaty"; 3 | import { Configuration, OpenAIApi, ChatCompletionRequestMessage } from "openai"; 4 | import { PuppetPadlocal } from "wechaty-puppet-padlocal"; 5 | import { onScan, onLogin, onLogout, onMessage, onFriendship } from "./utils"; 6 | import dotenv from "dotenv"; 7 | 8 | dotenv.config(); 9 | console.log(process.env.OPENAI_API_KEY) 10 | 11 | 12 | export function createBot(): Wechaty { 13 | const token: string = process.env.WECHATY_PUPPET_SERVICE_TOKEN! 14 | const puppet = new PuppetPadlocal({ 15 | token, 16 | }); 17 | 18 | return WechatyBuilder.build({ 19 | name: "chatgpt-bot", 20 | puppet, 21 | }); 22 | } 23 | 24 | 25 | // config openAI 26 | const configuration = new Configuration({ 27 | apiKey: process.env.OPENAI_API_KEY, 28 | }); 29 | export const openai = new OpenAIApi(configuration); 30 | 31 | // Initializing the bot 32 | export const bot = createBot(); 33 | 34 | // Keep the conversation state 35 | export const initState: Array = new Array({ "role": "system", "content": "You are a helpful assistant." }) 36 | 37 | bot.on('scan', onScan) 38 | bot.on('login', onLogin) 39 | bot.on('logout', onLogout) 40 | bot.on('message', onMessage) 41 | bot.on('friendship', onFriendship) 42 | 43 | bot.start() 44 | .then(() => log.info('StarterBot', 'Starter Bot Started.')) 45 | .catch(e => log.error('StarterBot', e)) 46 | 47 | bot.ready() 48 | .then(() => log.info('StarterBot', 'Starter Bot Ready.')) 49 | .catch(e => log.error('StarterBot', e)) -------------------------------------------------------------------------------- /examples/professional/chatgpt-bot/dotenv: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY="" 2 | WECHATY_PUPPET_SERVICE_TOKEN="" 3 | -------------------------------------------------------------------------------- /examples/professional/chatgpt-bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechaty-chatgpt", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "npx ts-node bot.ts", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "dotenv": "^16.0.3", 15 | "file-box": "^1.5.5", 16 | "markdown-it": "^13.0.1", 17 | "node-html-to-image": "^3.3.0", 18 | "openai": "^3.2.1", 19 | "qrcode-terminal": "^0.12.0", 20 | "ts-node": "^10.9.1", 21 | "wechaty": "^1.20.2", 22 | "wechaty-puppet-padlocal": "^1.20.1", 23 | "wechaty-puppet-service": "^1.19.9", 24 | "wechaty-puppet-wechat": "^1.18.4" 25 | }, 26 | "devDependencies": { 27 | "@types/markdown-it": "^12.2.3", 28 | "@types/qrcode-terminal": "^0.12.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/professional/chatgpt-bot/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export WECHATY_LOG=verbose 4 | export WECHATY_PUPPET=wechaty-puppet-service 5 | 6 | npm run start 7 | -------------------------------------------------------------------------------- /examples/professional/chatgpt-bot/utils.ts: -------------------------------------------------------------------------------- 1 | import { Contact, Message, ScanStatus, log, Friendship } from "wechaty"; 2 | import { bot, openai, initState } from "./bot"; 3 | import markdownIt from 'markdown-it'; 4 | import { ChatCompletionRequestMessage } from "openai"; 5 | import { FileBox } from "file-box"; 6 | import qrTerm from "qrcode-terminal"; 7 | import nodeHtmlToImage from 'node-html-to-image'; 8 | 9 | const MEMORY_LIMIT = 50; // max memory 10 | let conversation: Array = new Array(); 11 | conversation.forEach(val => initState.push(Object.assign({}, val))); 12 | 13 | function convertMarkdownToHtml(markdown: string): string { 14 | const md = new markdownIt(); 15 | return md.render(markdown); 16 | } 17 | 18 | export function onScan (qrcode: string, status: ScanStatus) { 19 | if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) { 20 | qrTerm.generate(qrcode, { small: true }) // show qrcode on console 21 | 22 | const qrcodeImageUrl = [ 23 | 'https://wechaty.js.org/qrcode/', 24 | encodeURIComponent(qrcode), 25 | ].join('') 26 | 27 | log.info('StarterBot', 'onScan: %s(%s) - %s', ScanStatus[status], status, qrcodeImageUrl) 28 | } else { 29 | log.info('StarterBot', 'onScan: %s(%s)', ScanStatus[status], status) 30 | } 31 | } 32 | 33 | export function onLogin(user: Contact) { 34 | log.info('StarterBot', '%s login', user) 35 | } 36 | 37 | export function onLogout(user: Contact) { 38 | log.info('StarterBot', '%s logout', user) 39 | } 40 | 41 | export async function onMessage(msg: Message) { 42 | log.info('StarterBot', msg.toString()) 43 | 44 | const contact = msg.talker(); 45 | const content = msg.text(); 46 | const isText = msg.type() === bot.Message.Type.Text; 47 | if (msg.self() || !isText) { // msg.self() check if the message is sent from the bot itself 48 | return; 49 | } 50 | if (content === 'ding') { 51 | await contact.say('dong'); 52 | } 53 | // return image 54 | if (content.startsWith("/img ")) { 55 | if (conversation.length === MEMORY_LIMIT) { 56 | // reset to initial state when reach the memory limit 57 | log.info("Resetting memory"); 58 | conversation = new Array(); 59 | conversation.forEach(val => initState.push(Object.assign({}, val))); 60 | } 61 | conversation.push({ "role": "user", "content": content.replace("/i", "") }) 62 | const response = await openai.createChatCompletion({ 63 | model: "gpt-3.5-turbo", 64 | messages: conversation, 65 | }); 66 | 67 | try { 68 | const replyContent = response.data.choices[0].message!.content! 69 | const html = convertMarkdownToHtml(replyContent); 70 | await nodeHtmlToImage({ 71 | output: './output.png', 72 | html: html 73 | }) 74 | 75 | log.info('The image was created successfully!') 76 | const fileBox = FileBox.fromFile("./output.png"); 77 | await contact.say(fileBox) 78 | 79 | // record reply 80 | const reply: ChatCompletionRequestMessage = { "role": "assistant", "content": replyContent }; 81 | conversation.push(reply); 82 | } catch (e) { 83 | console.error(e) 84 | } 85 | } else { 86 | // return text if no slash command is specified 87 | if (conversation.length === MEMORY_LIMIT) { 88 | // reset to initial state when reach the memory limit 89 | log.info("Resetting memory"); 90 | conversation = new Array(); 91 | conversation.forEach(val => initState.push(Object.assign({}, val))); 92 | } 93 | conversation.push({ "role": "user", "content": content.replace("/t", "") }) 94 | const response = await openai.createChatCompletion({ 95 | model: "gpt-3.5-turbo", 96 | messages: conversation, 97 | }); 98 | 99 | try { 100 | const replyContent = response.data.choices[0].message!.content! 101 | await contact.say(replyContent); 102 | 103 | // record reply 104 | const reply: ChatCompletionRequestMessage = { "role": "assistant", "content": replyContent }; 105 | conversation.push(reply); 106 | } catch (e) { 107 | console.error(e); 108 | } 109 | } 110 | } 111 | 112 | export async function onFriendship(friendship: Friendship) { 113 | let logMsg; 114 | 115 | try { 116 | logMsg = "received `friend` event from " + friendship.contact().name(); 117 | log.info(logMsg); 118 | 119 | switch (friendship.type()) { 120 | /** 121 | * 122 | * 1. New Friend Request 123 | * 124 | * when request is set, we can get verify message from `request.hello`, 125 | * and accept this request by `request.accept()` 126 | */ 127 | 128 | case bot.Friendship.Type.Receive: 129 | logMsg = 'accepted automatically'; 130 | log.info("before accept"); 131 | await friendship.accept(); 132 | 133 | // if want to send msg , you need to delay sometimes 134 | await new Promise((r) => setTimeout(r, 1000)); 135 | await friendship.contact().say(`Hi ${friendship.contact().name()} from FreeChatGPT, I am your person asistant!\n你好 ${friendship.contact().name()} 我是你的私人助理FreeChatGPT!`); 136 | console.log("after accept"); 137 | break; 138 | 139 | /** 140 | * 141 | * 2. Friend Ship Confirmed 142 | * 143 | */ 144 | case bot.Friendship.Type.Confirm: 145 | logMsg = "friendship confirmed with " + friendship.contact().name(); 146 | break; 147 | 148 | default: 149 | break; 150 | } 151 | } catch (e) { 152 | console.error(e); 153 | logMsg = "Friendship try catch failed"; 154 | } 155 | 156 | log.info(logMsg); 157 | } -------------------------------------------------------------------------------- /examples/professional/ctrl-c-signal-bot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | /* tslint:disable:variable-name */ 20 | import { generate } from 'qrcode-terminal' 21 | import { finis } from 'finis' 22 | 23 | import { 24 | Wechaty, 25 | log, 26 | qrcodeValueToImageUrl, 27 | } from 'wechaty' // from 'wechaty' 28 | 29 | const bot = Wechaty.instance() 30 | 31 | const welcome = ` 32 | | __ __ _ _ 33 | | \\ \\ / /__ ___| |__ __ _| |_ _ _ 34 | | \\ \\ /\\ / / _ \\/ __| '_ \\ / _\` | __| | | | 35 | | \\ V V / __/ (__| | | | (_| | |_| |_| | 36 | | \\_/\\_/ \\___|\\___|_| |_|\\__,_|\\__|\\__, | 37 | | |___/ 38 | 39 | =============== Powered by Wechaty =============== 40 | -------- https://github.com/chatie/wechaty -------- 41 | Version: ${bot.version(true)} 42 | 43 | I'm a bot, my superpower is: 44 | 45 | Send message to myself before I die. 46 | (When you press Ctrl+C, or kill me with a signal) 47 | __________________________________________________ 48 | 49 | Hope you like it, and you are very welcome to 50 | upgrade me to more superpowers! 51 | 52 | Please wait... I'm trying to login in... 53 | 54 | ` 55 | 56 | console.log(welcome) 57 | 58 | bot 59 | .on('login' , user => { 60 | log.info('Bot', `${user.name()} login`) 61 | bot.say('Wechaty login').catch(console.error) 62 | }) 63 | .on('scan', (qrcode, status, data) => { 64 | generate(qrcode, { small: true }) 65 | if (data) { 66 | console.log(data) 67 | } 68 | console.log(qrcodeValueToImageUrl(qrcode)) 69 | console.log('^^^ Online QR Code Image URL ^^^ ') 70 | console.log(`[${status}] ${qrcode} Scan QR Code above url to log in: `) 71 | }) 72 | .on('message', async msg => { 73 | console.log(msg.toString()) 74 | console.log('Please press Ctrl+C to kill me!') 75 | console.log(`Then I'll send my last word to myself, check it out on your Wechat!`) 76 | }) 77 | 78 | bot.on('error', async e => { 79 | log.error('Bot', 'error: %s', e) 80 | if (bot.logonoff()) { 81 | await bot.say('Wechaty error: ' + e.message).catch(console.error) 82 | } 83 | }) 84 | 85 | let killChrome: NodeJS.SignalsListener 86 | 87 | bot.start() 88 | .then(() => { 89 | const listenerList = process.listeners('SIGINT') 90 | for (const listener of listenerList) { 91 | if (listener.name === 'killChrome') { 92 | process.removeListener('SIGINT', listener) 93 | killChrome = listener 94 | } 95 | } 96 | }) 97 | .catch(async e => { 98 | log.error('Bot', 'start() fail: %s', e) 99 | await bot.stop() 100 | process.exit(-1) 101 | }) 102 | 103 | let quiting = false 104 | finis(async (code, signal) => { 105 | log.info('Bot', 'finis(%s, %s)', code, signal) 106 | 107 | if (!bot.logonoff()) { 108 | log.info('Bot', 'finis() bot had been already stopped') 109 | doExit(code) 110 | } 111 | 112 | if (quiting) { 113 | log.warn('Bot', 'finis() already quiting... return and wait...') 114 | return 115 | } 116 | 117 | quiting = true 118 | let done = false 119 | // let checkNum = 0 120 | 121 | const exitMsg = `Wechaty will exit ${code} because of ${signal} ` 122 | 123 | log.info('Bot', 'finis() broadcast quiting message for bot') 124 | await bot.say(exitMsg) 125 | // .then(() => bot.stop()) 126 | .catch(e => log.error('Bot', 'finis() catch rejection: %s', e)) 127 | .then(() => done = true) 128 | 129 | setImmediate(checkForExit) 130 | 131 | function checkForExit() { 132 | // if (checkNum++ % 100 === 0) { 133 | log.info('Bot', 'finis() checkForExit() checking done: %s', done) 134 | // } 135 | if (done) { 136 | log.info('Bot', 'finis() checkForExit() done!') 137 | setTimeout(() => doExit(code), 1000) // delay 1 second 138 | return 139 | } 140 | // death loop to wait for `done` 141 | // process.nextTick(checkForExit) 142 | // setImmediate(checkForExit) 143 | setTimeout(checkForExit, 100) 144 | } 145 | }) 146 | 147 | function doExit(code: number): void { 148 | log.info('Bot', 'doExit(%d)', code) 149 | if (killChrome) { 150 | killChrome('SIGINT') 151 | } 152 | process.exit(code) 153 | } 154 | 155 | // process.on('SIGINT', function() { 156 | // console.log('Nice SIGINT-handler') 157 | // const listeners = process.listeners('SIGINT') 158 | // for (let i = 0; i < listeners.length; i++) { 159 | // console.log(listeners[i].toString()) 160 | // } 161 | // }) 162 | -------------------------------------------------------------------------------- /examples/professional/hot-import-bot/README.md: -------------------------------------------------------------------------------- 1 | # HOT-IMPORT LISTENSER 2 | 3 | Hot import Wechaty listenser functions after change the source code without restart the program 4 | 5 | This directory is an example of how to use `hot-import` feature introduced in [this commit](https://github.com/Chatie/wechaty/commit/c47715b4470e7ade9a2590fd3e66985dd7977622). 6 | 7 | The hot-import is based on an npm package [hot-import](https://www.npmjs.com/package/hot-import) 8 | 9 | ## Run 10 | 11 | ```shell 12 | # 0. Enable LOG 13 | export WECHATY_LOG=silly 14 | 15 | # 1. by node 16 | ./run-by-node.sh 17 | 18 | # 2. by docker 19 | ./run-by-docker.sh 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/professional/hot-import-bot/hot-import-bot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | /** 21 | * Wechaty hot import bot example 22 | * 23 | * Hot import Wechaty listenser functions after change the source code without restart the program 24 | * 25 | * How to start: 26 | * ```shell 27 | * docker run -t -i --rm --name wechaty --mount type=bind,source="$(pwd)",target=/bot -m "300M" --memory-swap "1G" zixia/wechaty index.js 28 | * ``` 29 | * 30 | * P.S. We are using the hot-import module: 31 | * * Hot Module Replacement(HMR) for Node.js 32 | * * https://www.npmjs.com/package/hot-import 33 | * 34 | */ 35 | import { Wechaty } from 'wechaty' 36 | 37 | const bot = Wechaty.instance() 38 | .on('friend', './listeners/on-friend') 39 | .on('login', './listeners/on-login') 40 | .on('message', './listeners/on-message') 41 | .on('scan', './listeners/on-scan') 42 | .start() 43 | -------------------------------------------------------------------------------- /examples/professional/hot-import-bot/listeners/on-friend.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | async function onFriend (contact, request) { 20 | /** 21 | * We can get the Wechaty bot instance from this: 22 | * `const wechaty = this` 23 | * Or use `this` directly: 24 | * `console.info(this.userSelf())` 25 | */ 26 | if(request){ 27 | let name = contact.name() 28 | // await request.accept() 29 | 30 | console.log(`Contact: ${name} send request ${request.hello()}`) 31 | } 32 | } 33 | 34 | module.exports = onFriend 35 | -------------------------------------------------------------------------------- /examples/professional/hot-import-bot/listeners/on-login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | async function onLogin (user) { 20 | /** 21 | * We can get the Wechaty bot instance from this: 22 | * `const wechaty = this` 23 | * Or use `this` directly: 24 | * `console.info(this.userSelf())` 25 | */ 26 | console.log(`${user} login`) 27 | } 28 | 29 | module.exports = onLogin 30 | -------------------------------------------------------------------------------- /examples/professional/hot-import-bot/listeners/on-message.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | async function onMessage (message) { 20 | /** 21 | * We can get the Wechaty bot instance from this: 22 | * `const wechaty = this` 23 | * Or use `this` directly: 24 | * `console.info(this.userSelf())` 25 | */ 26 | console.log(`Received message: ${message}`) 27 | } 28 | 29 | module.exports = onMessage 30 | -------------------------------------------------------------------------------- /examples/professional/hot-import-bot/listeners/on-scan.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import qrTerm from 'qrcode-terminal' 20 | 21 | async function onScan (qrcode, status) { 22 | qrTerm.generate(qrcode, {small: true}) 23 | 24 | const qrcodeImageUrl = [ 25 | 'https://wechaty.js.org/qrcode/', 26 | encodeURIComponent(qrcode), 27 | ].join('') 28 | 29 | console.log(status, qrcodeImageUrl) 30 | } 31 | 32 | module.exports = onScan 33 | -------------------------------------------------------------------------------- /examples/professional/hot-import-bot/run-by-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | docker run -t -i --rm --name wechaty -e WECHATY_LOG="$WECHATY_LOG" --mount type=bind,source="$(pwd)",target=/bot zixia/wechaty hot-import-bot.js 5 | -------------------------------------------------------------------------------- /examples/professional/hot-import-bot/run-by-node.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | node hot-import-bot.js -------------------------------------------------------------------------------- /examples/professional/monster-bot/README.md: -------------------------------------------------------------------------------- 1 | # The monster demo 2 | 3 | Unlike all existing demos that emphasizes on _one specific side_ of the customization, this demo tries to incorporate anything that I think useful for a normal bot user. Hence the name, the _"monster"_ demo. It all begins when I was a JavaScript newbie, I felt the most helpful thing is not a 6-line demo code, but a comprehensive one that include all useful features, because newbie like me can't put them together. 4 | 5 | This demo is a _one-stop_ demo that includes _everything useful_ for people to get an easier start. Because it is much simpler to remove an module from it, rather than figuring out how to put all these jigsaw puzzles together. 6 | 7 | 8 | The following are the list of useful features incorporated into this demo: 9 | 10 | - _Pure ES6_ JavaScript code, no Typescript and no need the superficial transpiling step 11 | - All _even handling_ in **separated** code, instead of everything in a big file 12 | - Can read from a config file 13 | - Can do rooms/messages matching using regexp that are defined in the config file 14 | - Can do hot reload, when the `.js` scripts/files are updated 15 | * _Hot reload/import even handling_ code, without restarting the program 16 | * _Hot reload/import configuration_ files as well 17 | - _Auto-save all media files_, i.e., incorporates `media-file-bot.ts` into this getting started demo 18 | - _Emoji cleansing_ -- to simplify emoji from `` to merely `[呲牙]` 19 | - Add error handling in `on-message.js` to catch errors instead of failing the program 20 | - _Graceful exit_ on `SIGINT/SIGTERM` etc, i.e., to write key info into file, on receiving killing signals (e.g., `SIGINT (2), SIGTERM(3)`), before termination, so as to do a _"graceful shutdown"_. 21 | 22 | All in all, it will be a _turn key solution_ for getting started with wechaty customization seriously, a true blue-print solution for people to get started using wechaty. 23 | 24 | ## Run 25 | 26 | ```shell 27 | docker run -t -i --rm --name wechaty --mount type=bind,source="$(pwd)",target=/bot zixia/wechaty index.js 28 | ``` 29 | 30 | Or 31 | 32 | ```shell 33 | ./run-monster-bot.sh 34 | ``` 35 | 36 | ## About hot-import 37 | 38 | Hot import Wechaty listenser functions after change the source code without restart the program 39 | 40 | This directory is an example of how to use `hot-import` 41 | feature introduced in [this commit](https://github.com/Chatie/wechaty/commit/c47715b4470e7ade9a2590fd3e66985dd7977622). 42 | 43 | The hot-import is based on an npm package [hot-import](https://www.npmjs.com/package/hot-import) 44 | 45 | ## About Pure ES6 instead of Typescript 46 | 47 | When I code customization code for [wechaty](https://github.com/Chatie/wechaty/), I write everything in pure ES6 instead of Typescript, and there are strong reasons for that. For details, check out my blog entry, 48 | 49 | [**Can Typescript really live up to its hype?**](https://blog.chatie.io/2018/03/09/can-typescript-really-live-up-to-its-hype.html) 50 | -------------------------------------------------------------------------------- /examples/professional/monster-bot/config.js: -------------------------------------------------------------------------------- 1 | // config.js 2 | 3 | //========================================================================== 4 | //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 5 | //vvvvv Customization Starts vvvvv 6 | export const config = { 7 | db: { host: 'localhost', port: 27017, name: 'db' }, 8 | redis: 9 | { default: { port: 6379 }, 10 | development: { host: '127.0.0.1' }, 11 | production: { host: '192.168.0.11' } 12 | }, 13 | friendEnabled: false, 14 | msgKW1: 'ding', 15 | msgKW2: 'dong', 16 | } 17 | //^^^^^ Customization Ends ^^^^^ 18 | //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 19 | //========================================================================== 20 | 21 | export default config 22 | -------------------------------------------------------------------------------- /examples/professional/monster-bot/index.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////// 2 | // Program: monster 3 | // Purpose: monster all-in-one demo of Wechaty hot 4 | // Authors: Tong Sun (c) 2018, All rights reserved 5 | // Huan LI (c) 2018, All rights reserved 6 | // xinbenlv (c) 2017, All rights reserved 7 | //////////////////////////////////////////////////////////////////////////// 8 | 9 | /** 10 | * Wechaty - https://github.com/chatie/wechaty 11 | * 12 | * @copyright 2016-2017 Huan LI 13 | * 14 | * Licensed under the Apache License, Version 2.0 (the "License"); 15 | * you may not use this file except in compliance with the License. 16 | * You may obtain a copy of the License at 17 | * 18 | * http://www.apache.org/licenses/LICENSE-2.0 19 | * 20 | * Unless required by applicable law or agreed to in writing, software 21 | * distributed under the License is distributed on an "AS IS" BASIS, 22 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | * See the License for the specific language governing permissions and 24 | * limitations under the License. 25 | * 26 | */ 27 | 28 | /** 29 | * Based on the Wechaty hot import bot example 30 | * 31 | * Hot import Wechaty listenser functions after change the source code without restart the program 32 | * 33 | * P.S. We are using the hot-import module: 34 | * * Hot Module Replacement(HMR) for Node.js 35 | * * https://www.npmjs.com/package/hot-import 36 | * 37 | */ 38 | 39 | import finis from 'finis' 40 | import { Wechaty } from 'wechaty' 41 | 42 | const bot = Wechaty.instance({ profile: "default"}) 43 | 44 | async function main() { 45 | 46 | bot 47 | .on('scan', './listeners/on-scan') 48 | .on('login', './listeners/on-login') 49 | .on('message', './listeners/on-message') 50 | .on('friend', './listeners/on-friend') 51 | .start() 52 | .catch(async function(e) { 53 | console.log(`Init() fail: ${e}.`) 54 | await bot.stop() 55 | process.exit(1) 56 | }) 57 | } 58 | 59 | main() 60 | 61 | finis((code, signal, error) => { 62 | console.log('Importand data saved at this step.') 63 | 64 | // await bot.stop() 65 | bot.stop() 66 | console.log(`Wechaty exit ${code} because of ${signal}/${error})`) 67 | process.exit(1) 68 | }) 69 | 70 | -------------------------------------------------------------------------------- /examples/professional/monster-bot/listeners/on-friend.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////// 2 | // Program: monster 3 | // Purpose: monster all-in-one demo of Wechaty hot 4 | // Authors: Tong Sun (c) 2018, All rights reserved 5 | // Huan LI (c) 2018, All rights reserved 6 | // xinbenlv (c) 2017, All rights reserved 7 | //////////////////////////////////////////////////////////////////////////// 8 | 9 | /** 10 | * Wechaty - https://github.com/chatie/wechaty 11 | * 12 | * @copyright 2016-2017 Huan LI 13 | * 14 | * Licensed under the Apache License, Version 2.0 (the "License"); 15 | * you may not use this file except in compliance with the License. 16 | * You may obtain a copy of the License at 17 | * 18 | * http://www.apache.org/licenses/LICENSE-2.0 19 | * 20 | * Unless required by applicable law or agreed to in writing, software 21 | * distributed under the License is distributed on an "AS IS" BASIS, 22 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | * See the License for the specific language governing permissions and 24 | * limitations under the License. 25 | * 26 | */ 27 | 28 | import { hotImport } from 'hot-import' 29 | 30 | export default async function onFriend (contact, request) { 31 | const config = await hotImport('config.js') 32 | if (!config.friendEnabled) return 33 | 34 | if (request) { 35 | let name = contact.name() 36 | // await request.accept() 37 | 38 | console.log(`Contact: ${name} send request ${request.hello()}`) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/professional/monster-bot/listeners/on-login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2017 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | export default async function onLogin (user) { 20 | console.log(`${user} login`) 21 | } 22 | -------------------------------------------------------------------------------- /examples/professional/monster-bot/listeners/on-message.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////// 2 | // Program: monster 3 | // Purpose: monster all-in-one demo of Wechaty hot 4 | // Authors: Tong Sun (c) 2018, All rights reserved 5 | // Huan LI (c) 2018, All rights reserved 6 | // xinbenlv (c) 2017, All rights reserved 7 | //////////////////////////////////////////////////////////////////////////// 8 | 9 | /** 10 | * Wechaty - https://github.com/chatie/wechaty 11 | * 12 | * @copyright 2016-2017 Huan LI 13 | * 14 | * Licensed under the Apache License, Version 2.0 (the "License"); 15 | * you may not use this file except in compliance with the License. 16 | * You may obtain a copy of the License at 17 | * 18 | * http://www.apache.org/licenses/LICENSE-2.0 19 | * 20 | * Unless required by applicable law or agreed to in writing, software 21 | * distributed under the License is distributed on an "AS IS" BASIS, 22 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | * See the License for the specific language governing permissions and 24 | * limitations under the License. 25 | * 26 | */ 27 | 28 | import fs from 'fs' 29 | import { hotImport } from 'hot-import' 30 | 31 | import { MediaMessage, Misc, log } from 'wechaty' 32 | 33 | export default async function onMessage (message) { 34 | try { 35 | const room = message.room() 36 | const sender = message.from() 37 | const content = message.text() 38 | const roomName = room ? `[${await room.topic()}] ` : '' 39 | 40 | process.stdout.write( 41 | `${roomName}<${sender.name()}>(${message.type()}:${message.typeSub()}): `) 42 | 43 | if (message instanceof MediaMessage) { 44 | saveMediaFile(message) 45 | return 46 | } 47 | 48 | console.log(`${Misc.digestEmoji(message)}`) 49 | // add an extra CR if too long 50 | if (content.length > 80) console.log("") 51 | 52 | const config = await hotImport('config.js') 53 | // Hot import! Try to change the msgKW1&2 to 'ping' & 'pong' 54 | // after the bot has already started! 55 | if (content === config.msgKW1) { 56 | await message.say(`${config.msgKW2}, thanks for ${config.msgKW1} me`) 57 | log.info('Bot', `REPLY: ${config.msgKW2}`) 58 | } else if (content === config.msgKW2) { 59 | await sender.say('ok, ${config.msgKW2} me is welcome, too.') 60 | } else if (/^hello/i.test(content)) { 61 | return `How are you, ${sender.name()} from ${roomName}` 62 | } 63 | } catch (e) { 64 | log.error('Bot', 'on(message) exception: %s' , e) 65 | } 66 | } 67 | 68 | async function saveMediaFile(message) { 69 | const filename = message.filename() 70 | console.log('IMAGE local filename: ' + filename) 71 | 72 | const fileStream = fs.createWriteStream(filename) 73 | 74 | process.stdout.write('saving...') 75 | try { 76 | const netStream = await message.readyStream() 77 | netStream 78 | .pipe(fileStream) 79 | .on('close', _ => { 80 | const stat = fs.statSync(filename) 81 | console.log(', saved as ', filename, ' size: ', stat.size) 82 | }) 83 | } catch (e) { 84 | console.error('stream error:', e) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/professional/monster-bot/listeners/on-scan.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2017 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import qrTerm from 'qrcode-terminal' 20 | 21 | export default async function onScan (url, code) { 22 | let loginUrl = url.replace('qrcode', 'l') 23 | console.log(code, url) 24 | 25 | if (code === 0) { 26 | qrTerm.generate(loginUrl) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/professional/monster-bot/run-monster-bot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | docker run -t -i --rm --name wechaty --mount type=bind,source="$(pwd)",target=/bot zixia/wechaty index.js 5 | -------------------------------------------------------------------------------- /examples/professional/send-link.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Wechaty, 3 | Message, 4 | UrlLink, 5 | } from 'wechaty' 6 | 7 | import { PuppetPadchat } from 'wechaty-puppet-padchat' 8 | 9 | import { generate } from 'qrcode-terminal' 10 | 11 | 12 | const puppet = new PuppetPadchat() 13 | 14 | export const wechatyBot = Wechaty.instance({ 15 | puppet, 16 | }) 17 | 18 | wechatyBot 19 | .on('scan', async (qrcode) => { 20 | generate(qrcode, {small: true}) 21 | 22 | }) 23 | .on('login', async function (user) { 24 | console.log(user) 25 | }) 26 | .on('error', async err => { 27 | console.error('BOT ERROR:', `exit for ${JSON.stringify(err.message || '')}`) 28 | }) 29 | .on('message', async function (m: Message) { 30 | if (m.text() === 'ding') { 31 | const urlLink = new UrlLink ({ 32 | description : 'WeChat Bot SDK for Individual Account, Powered by TypeScript, Docker, and Love', 33 | thumbnailUrl: 'https://avatars0.githubusercontent.com/u/25162437?s=200&v=4', 34 | title : 'Welcome to Wechaty', 35 | url : 'https://github.com/chatie/wechaty', 36 | }) 37 | m.say(urlLink) 38 | } 39 | }) 40 | 41 | wechatyBot.start() 42 | -------------------------------------------------------------------------------- /examples/professional/speech-to-text-bot.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /** 3 | * Wechaty - https://github.com/chatie/wechaty 4 | * 5 | * @copyright 2016-2018 Huan LI 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | 21 | import { 22 | // createWriteStream, 23 | createReadStream, 24 | } from 'fs' 25 | import { 26 | PassThrough, 27 | Readable, 28 | } from 'stream' 29 | 30 | import request from 'request' 31 | import Ffmpeg from 'fluent-ffmpeg' 32 | import querystring from 'querystring' 33 | 34 | /* tslint:disable:variable-name */ 35 | import qrcodeTerminal from 'qrcode-terminal' 36 | 37 | /** 38 | * Change `import { ... } from '../.js'` 39 | * to `import { ... } from 'wechaty'` 40 | * when you are runing with Docker or NPM instead of Git Source. 41 | */ 42 | import { 43 | WechatyBuilder, 44 | type, 45 | } from 'wechaty' 46 | 47 | const bot = WechatyBuilder.build({ name: 'speech-bot' }) 48 | 49 | bot 50 | .on('scan', (qrcode, status) => { 51 | qrcodeTerminal.generate(qrcode) 52 | console.log(`${qrcode}\n[${status}] Scan QR Code in above url to login: `) 53 | }) 54 | .on('login' , user => console.log(`${user} logined`)) 55 | .on('message', async function(msg) { 56 | console.log(`RECV: ${msg}`) 57 | 58 | if (msg.type() !== type.Message.Audio) { 59 | return // skip no-VOICE message 60 | } 61 | 62 | // const mp3Stream = await msg.readyStream() 63 | 64 | const msgFile = await msg.toFileBox() 65 | const filename = msgFile.name 66 | msgFile.toFile(filename) 67 | 68 | const mp3Stream = createReadStream(filename) 69 | const text = await speechToText(mp3Stream) 70 | console.log('VOICE TO TEXT: ' + text) 71 | 72 | if (msg.self()) { 73 | await bot.say(text) // send text to 'filehelper' 74 | } else { 75 | await msg.say(text) // to original sender 76 | } 77 | 78 | }) 79 | 80 | bot 81 | .start() 82 | .catch(e => console.error('bot.start() error: ' + e)) 83 | 84 | async function speechToText(mp3Stream: Readable): Promise { 85 | const wavStream = mp3ToWav(mp3Stream) 86 | 87 | // const textStream = wavToText(wavStream) 88 | 89 | // textStream.on('data', text => { 90 | // console.log(text) 91 | // }) 92 | 93 | try { 94 | const text = await wavToText(wavStream) 95 | return text 96 | 97 | } catch (e) { 98 | console.log(e) 99 | return '' 100 | } 101 | } 102 | 103 | function mp3ToWav(mp3Stream: Readable): NodeJS.ReadableStream { 104 | const wavStream = new PassThrough() 105 | 106 | Ffmpeg(mp3Stream) 107 | .fromFormat('mp3') 108 | .toFormat('wav') 109 | .pipe(wavStream as any) 110 | 111 | // .on('start', function(commandLine) { 112 | // console.log('Spawned Ffmpeg with command: ' + commandLine); 113 | // }) 114 | // .on('codecData', function(data) { 115 | // console.log('Input is ' + data.audio + ' audio ' + 116 | // 'with ' + data.video + ' video'); 117 | // }) 118 | // .on('progress', progress => { 119 | // console.log('Processing: ' + progress.percent + '% done'); 120 | // }) 121 | // .on('end', function() { 122 | // console.log('Finished processing'); 123 | // }) 124 | .on('error', function(err: Error /*, stdout, stderr */) { 125 | console.log('Cannot process video: ' + err.message) 126 | }) 127 | 128 | return wavStream 129 | } 130 | 131 | /** 132 | * Baidu: 133 | * export BAIDU_SPEECH_API_KEY=FK58sUlteAuAIXZl5dWzAHCT 134 | * export BAIDU_SPEECH_SECRET_KEY=feaf24adcc5b8f02b147e7f7b1953030 135 | * curl "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=${BAIDU_SPEECH_API_KEY}&client_secret=${BAIDU_SPEECH_SECRET_KEY}" 136 | * 137 | * OAuth: http://developer.baidu.com/wiki/index.php?title=docs/oauth/overview 138 | * ASR: http://yuyin.baidu.com/docs/asr/57 139 | */ 140 | 141 | /** 142 | * YunZhiSheng: 143 | * http://dev.hivoice.cn/download_file/USC_DevelGuide_WebAPI_audioTranscription.pdf 144 | */ 145 | 146 | /** 147 | * Google: 148 | * http://blog.csdn.net/dlangu0393/article/details/7214728 149 | * http://elric2011.github.io/a/using_speech_recognize_service.html 150 | */ 151 | async function wavToText(wavStream: NodeJS.ReadableStream): Promise { 152 | const params = { 153 | 'cuid': 'wechaty', 154 | 'lan': 'zh', 155 | 'token': '24.8c6a25b5dcfb41af189a97d9e0b7c076.2592000.1482571685.282335-8943256', 156 | } 157 | 158 | const apiUrl = 'http://vop.baidu.com/server_api?' 159 | + querystring.stringify(params) 160 | 161 | const options = { 162 | headers: { 163 | 'Content-Type': 'audio/wav; rate=8000', 164 | }, 165 | } 166 | 167 | return new Promise((resolve, reject) => { 168 | wavStream.pipe(request.post(apiUrl, options, (err, _ /* httpResponse */, body) => { 169 | // "err_msg":"success.","err_no":0,"result":["这是一个测试测试语音转文字,"] 170 | if (err) { 171 | return reject(err) 172 | } 173 | try { 174 | const obj = JSON.parse(body) 175 | if (obj.err_no !== 0) { 176 | throw new Error(obj.err_msg) 177 | } 178 | 179 | return resolve(obj.result[0]) 180 | 181 | } catch (err) { 182 | return reject(err) 183 | } 184 | })) 185 | }) 186 | } 187 | -------------------------------------------------------------------------------- /examples/professional/telegram-roger-bot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - https://github.com/chatie/wechaty 3 | * 4 | * @copyright 2016-2018 Huan LI 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | /** 21 | * This is an example to show how a simple bot runs under 22 | * both Telegram and WeChat and shares the same code. 23 | * 24 | * For more information: https://github.com/hczhcz/wechaty-telegram 25 | */ 26 | 27 | import TelegramBot from 'node-telegram-bot-api' 28 | import WechatyTelegramBot from 'wechaty-telegram' 29 | 30 | const initBot = (ChatBot, token) => { 31 | const bot = new ChatBot(token, { 32 | // the "polling" option applies for a Telegram bot 33 | // for WeChat bot, "polling" and "webhook" works in the same way 34 | polling: true, 35 | // options for Wechaty and Wechaty Telegram Bot Adaptor 36 | wechaty: { 37 | // if you do not want your bot to add a friend automatically 38 | autoFriend: false 39 | } 40 | }) 41 | 42 | let roger = 'roger' 43 | 44 | bot.on('message', msg => { 45 | bot.sendMessage(msg.chat.id, roger) // send roger 46 | }) 47 | 48 | // use regular expressions to detect commands 49 | bot.onText(/^\/setroger (.*)$/, (msg, match) => { 50 | roger = match[1] // set roger message from user's input 51 | }) 52 | } 53 | 54 | initBot(TelegramBot, '123456:TOKEN') // you may obtain a Telegram bot token 55 | initBot(WechatyTelegramBot, 'mybot') // you can use your bot's name as a token here 56 | -------------------------------------------------------------------------------- /examples/professional/tuling123-bot.js: -------------------------------------------------------------------------------- 1 | import qrTerm from 'qrcode-terminal' 2 | import Tuling123 from 'tuling123-client' 3 | 4 | import { 5 | Wechaty, 6 | Message, 7 | } from 'wechaty' 8 | 9 | const welcome = ` 10 | =============== Powered by Wechaty =============== 11 | -------- https://github.com/Chatie/wechaty -------- 12 | 13 | I can talk with you using Tuling123.com 14 | Apply your own tuling123.com API_KEY 15 | at: http://www.tuling123.com/html/doc/api.html 16 | 17 | __________________________________________________ 18 | 19 | Please wait... I'm trying to login in... 20 | ` 21 | 22 | console.log(welcome) 23 | 24 | /** 25 | * 26 | * Apply Your Own Tuling123 Developer API_KEY at: 27 | * http://www.tuling123.com 28 | * 29 | */ 30 | const TULING123_API_KEY = '18f25157e0446df58ade098479f74b21' 31 | const tuling = new Tuling123(TULING123_API_KEY) 32 | 33 | const bot = WechatyBuilder.build() 34 | 35 | bot.on('scan', onScan) 36 | bot.on('login', onLogin) 37 | bot.on('logout', onLogout) 38 | bot.on('message', onMessage) 39 | bot.on('error', onError) 40 | 41 | bot.start() 42 | .catch(console.error) 43 | 44 | function onScan (qrcode, status) { 45 | qrTerm.generate(qrcode, { small: true }) // show qrcode on console 46 | } 47 | 48 | function onLogin (user) { 49 | console.log(`${user} login`) 50 | } 51 | 52 | function onLogout (user) { 53 | console.log(`${user} logout`) 54 | } 55 | 56 | function onError (e) { 57 | console.error(e) 58 | } 59 | 60 | async function onMessage (msg) { 61 | // Skip message from self, or inside a room 62 | if (msg.self() || msg.room() || msg.from().name() === '微信团队' || msg.type() !== Message.Type.Text) return 63 | 64 | console.log('Bot', 'talk: %s' , msg.text()) 65 | 66 | try { 67 | const {text: reply} = await tuling.ask(msg.text(), {userid: msg.from()}) 68 | console.log('Tuling123', 'Talker reply:"%s" for "%s" ', 69 | reply, 70 | msg.text(), 71 | ) 72 | await msg.say(reply) 73 | } catch (e) { 74 | console.error('Bot', 'on message tuling.ask() exception: %s' , e && e.message || e) 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /examples/tensorflow.js/fingerpose/README.md: -------------------------------------------------------------------------------- 1 | # Wechaty + TensorFlow.js FingerPose 2 | 3 | To be written: play paper-rock-scissors game. 4 | 5 | ## Requirements 6 | 7 | 1. Node.js v12+ 8 | 1. Wechaty Token 9 | 10 | ## Steps 11 | 12 | ### 1. Build a Wechaty Bot 13 | 14 | ```sh 15 | npm i wechaty @chatie/tsconfig 16 | ``` 17 | 18 | ### 2. Test Handpose Model 19 | 20 | ```sh 21 | npm i @tensorflow/tfjs-node @tensorflow-models/handpose 22 | npm i canvas file-box 23 | ``` 24 | 25 | ## Resources 26 | 27 | - [MediaPipe Handpose](https://github.com/tensorflow/tfjs-models/tree/master/handpose) 28 | - [fingerpose: Finger pose classifier for hand landmarks detected by TensorFlow.js' handpose model](https://github.com/andypotato/fingerpose) 29 | - [TensorFlow Blog: Face and hand tracking in the browser with MediaPipe and TensorFlow.js](https://blog.tensorflow.org/2020/03/face-and-hand-tracking-in-browser-with-mediapipe-and-tensorflowjs.html) 30 | - [Real Time AI GESTURE RECOGNITION with Tensorflow.JS + React.JS + FingerPose](https://www.youtube.com/watch?v=9MTiQMxTXPE) 31 | -------------------------------------------------------------------------------- /examples/tensorflow.js/fingerpose/bot/fingerpose-bot.ts: -------------------------------------------------------------------------------- 1 | import { Wechaty } from 'wechaty' 2 | 3 | const bot = WechatyBuilder.build({ 4 | puppet: 'wechaty-puppet-service', 5 | puppetOptions: { 6 | token: 'puppet_hostie_gdg_zhengzhou', 7 | }, 8 | }) 9 | 10 | bot.on('scan', qrcode => { 11 | console.info('qrcode:', qrcode) 12 | }) 13 | 14 | bot.on('login', user => { 15 | console.info('user:' + user) 16 | }) 17 | 18 | bot.on('message', message => { 19 | console.info('message: ' + message) 20 | }) 21 | 22 | bot.start() 23 | .catch(console.error) 24 | -------------------------------------------------------------------------------- /examples/tensorflow.js/fingerpose/examples/handpose.ts: -------------------------------------------------------------------------------- 1 | import '@tensorflow/tfjs-node' 2 | import { 3 | Image, 4 | createCanvas, 5 | } from 'canvas' 6 | import { FileBox } from 'file-box' 7 | 8 | import handpose from '@tensorflow-models/handpose' 9 | 10 | async function main() { 11 | console.info('Loading model...') 12 | const model = await handpose.load() 13 | console.info('Loading model... done.') 14 | 15 | const fileBox = FileBox.fromFile(__dirname + '/paper.jpg') 16 | const dataURL = await fileBox.toDataURL() 17 | const image = new Image() 18 | const future = new Promise(resolve => image.onload = resolve) 19 | image.src = dataURL 20 | console.info(0) 21 | await future 22 | 23 | // const imageData = imageToData(image) 24 | // console.info(imageData.width, imageData.height, imageData) 25 | // console.info(image) 26 | console.info(1) 27 | const predictions = await model.estimateHands(image) 28 | console.info(2) 29 | console.log('Predictions:', predictions) 30 | } 31 | 32 | export function imageToData (image: Image) { 33 | const canvas = createCanvas(image.width, image.height) 34 | const ctx = canvas.getContext('2d') 35 | if (!ctx) { 36 | throw new Error('getContext found null') 37 | } 38 | 39 | ctx.drawImage(image, 0, 0, image.width, image.height) 40 | const imageData = ctx.getImageData(0, 0, image.width, image.height) 41 | 42 | return { 43 | width: imageData.width, 44 | height: imageData.height, 45 | data: Uint32Array.from(imageData.data), 46 | } 47 | } 48 | 49 | main() 50 | .catch(console.error) 51 | -------------------------------------------------------------------------------- /examples/tensorflow.js/fingerpose/examples/paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechaty/getting-started/4753cac2d0fad46b8b33043218b82de615521f05/examples/tensorflow.js/fingerpose/examples/paper.png -------------------------------------------------------------------------------- /examples/tensorflow.js/fingerpose/examples/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechaty/getting-started/4753cac2d0fad46b8b33043218b82de615521f05/examples/tensorflow.js/fingerpose/examples/rock.png -------------------------------------------------------------------------------- /examples/tensorflow.js/fingerpose/examples/scissors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechaty/getting-started/4753cac2d0fad46b8b33043218b82de615521f05/examples/tensorflow.js/fingerpose/examples/scissors.png -------------------------------------------------------------------------------- /examples/tensorflow.js/fingerpose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tensorflow-js-fingerpose", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@chatie/tsconfig": "^0.10.1", 14 | "@tensorflow-models/handpose": "0.0.6", 15 | "@tensorflow/tfjs-node": "^2.7.0", 16 | "canvas": "^2.6.1", 17 | "file-box": "^0.16.2", 18 | "fingerpose": "0.0.2", 19 | "wechaty": "^0.50.7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/tensorflow.js/fingerpose/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@chatie/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | }, 6 | "exclude": [ 7 | "node_modules/", 8 | "dist/", 9 | "tests/fixtures/", 10 | ], 11 | "include": [ 12 | "app/**/*.ts", 13 | "bin/*.ts", 14 | "bot/**/*.ts", 15 | "examples/**/*.ts", 16 | "scripts/**/*.ts", 17 | "src/**/*.ts", 18 | "tests/**/*.spec.ts", 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /examples/third-parties/codesandbox/.gitignore: -------------------------------------------------------------------------------- 1 | gotty 2 | -------------------------------------------------------------------------------- /examples/third-parties/codesandbox/README.md: -------------------------------------------------------------------------------- 1 | # CodeSandBox Wechaty Getting Started 2 | 3 | See: 4 | -------------------------------------------------------------------------------- /examples/third-parties/codesandbox/ding-dong-bot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Wechaty - WeChat Bot SDK for Personal Account, Powered by TypeScript, Docker, and 💖 3 | * - https://github.com/wechaty/wechaty 4 | */ 5 | import { 6 | Contact, 7 | Message, 8 | ScanStatus, 9 | WechatyBuilder, 10 | log, 11 | } from 'wechaty' 12 | 13 | import qrTerm from 'qrcode-terminal' 14 | 15 | function onScan (qrcode: string, status: ScanStatus) { 16 | if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) { 17 | qrTerm.generate(qrcode, { small: true }) // show qrcode on console 18 | 19 | const qrcodeImageUrl = [ 20 | 'https://wechaty.js.org/qrcode/', 21 | encodeURIComponent(qrcode), 22 | ].join('') 23 | 24 | log.info('StarterBot', 'onScan: %s(%s) - %s', ScanStatus[status], status, qrcodeImageUrl) 25 | } else { 26 | log.info('StarterBot', 'onScan: %s(%s)', ScanStatus[status], status) 27 | } 28 | } 29 | 30 | function onLogin (user: Contact) { 31 | log.info('StarterBot', '%s login', user) 32 | } 33 | 34 | function onLogout (user: Contact) { 35 | log.info('StarterBot', '%s logout', user) 36 | } 37 | 38 | async function onMessage (msg: Message) { 39 | log.info('StarterBot', msg.toString()) 40 | 41 | if (msg.text() === 'ding') { 42 | await msg.say('dong') 43 | } 44 | } 45 | 46 | const bot = WechatyBuilder.build({ 47 | name: 'ding-dong-bot', 48 | /** 49 | * Specify a `puppet` for a specific protocol (Web/Pad/Mac/Windows, etc). 50 | * 51 | * You can use the following providers: 52 | * - wechaty-puppet-hostie 53 | * - wechaty-puppet-wechat 54 | * - wechaty-puppet-padplus 55 | * - etc. 56 | * 57 | * Learn more about Wechaty Puppet Providers at: 58 | * https://github.com/wechaty/wechaty-puppet/wiki/Directory 59 | */ 60 | 61 | // puppet: 'wechaty-puppet-hostie', 62 | puppet: 'wechaty-puppet-wechat4u', 63 | 64 | }) 65 | 66 | bot.on('scan', onScan) 67 | bot.on('login', onLogin) 68 | bot.on('logout', onLogout) 69 | bot.on('message', onMessage) 70 | 71 | bot.start() 72 | .then(() => log.info('StarterBot', 'Starter Bot Started.')) 73 | .catch(e => log.error('StarterBot', e)) 74 | -------------------------------------------------------------------------------- /examples/third-parties/codesandbox/install-gotty.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -e gotty ] || { 4 | wget https://github.com/yudai/gotty/releases/download/v2.0.0-alpha.3/gotty_2.0.0-alpha.3_linux_amd64.tar.gz 5 | tar zxvf ./gotty*.tar.gz 6 | rm -f gotty*.tar.gz 7 | } 8 | -------------------------------------------------------------------------------- /examples/third-parties/codesandbox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codesandbox-wechaty-getting-started", 3 | "version": "0.5.1", 4 | "description": "Wechaty is a Conversational SDK for Chatbot Makers", 5 | "type": "module", 6 | "engines": { 7 | "node": ">=16" 8 | }, 9 | "scripts": { 10 | "gotty": "bash -x install-gotty.sh", 11 | "start": "npm install && npm run gotty && pwd && ls -l && npm ls nodemon && ./gotty --ws-origin '.*' npm run bot", 12 | "bot": "cross-env NODE_OPTIONS=\"--no-warnings --loader=ts-node/esm\" WECHATY_LOG=verbose nodemon -w ding-dong-bot.ts ding-dong-bot.ts" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/wechaty/getting-started.git" 17 | }, 18 | "keywords": [], 19 | "author": "Huan LI ", 20 | "license": "Apache-2.0", 21 | "bugs": { 22 | "url": "https://github.com/wechaty/getting-started/issues" 23 | }, 24 | "homepage": "https://github.com/wechaty/getting-started#readme", 25 | "dependencies": { 26 | "@chatie/tsconfig": "^0.20.2", 27 | "cross-env": "^7.0.3", 28 | "nodemon": "^2.0.12", 29 | "qrcode-terminal": "^0.12.0", 30 | "wechaty": "^0.69.47", 31 | "wechaty-puppet-mock": "^0.28.3", 32 | "wechaty-puppet-wechat4u": "^0.19.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/third-parties/codesandbox/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "container": { 3 | "node": "16" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/third-parties/codesandbox/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@chatie/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "exclude": [ 7 | "node_modules/", 8 | "dist/", 9 | "tests/fixtures/" 10 | ], 11 | "include": [ 12 | "app/**/*.ts", 13 | "bin/*.ts", 14 | "bot/**/*.ts", 15 | "examples/**/*.ts", 16 | "scripts/**/*.ts", 17 | "src/**/*.ts", 18 | "tests/**/*.spec.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/third-parties/maodou/README.md: -------------------------------------------------------------------------------- 1 | ## Instructions for maodou-ketang-bot 2 | 3 | This is a third-party bot built using wechaty and [maodouketang courses API](https://api.maodouketang.com). 4 | 5 | * To see a preview demo of this bot, you need 6 | - simply add "毛豆课堂小助手" as your wechat friend and send msg to her. 7 | 8 | * To run a bot of yourself, you need 9 | - ```git clone``` this repo and ```cd wechaty-getting-started/examples/third-party/maodou``` 10 | - Within this directory, type ```npm install``` and ```npm start``` and you are good to go! 11 | - ```npm run dev``` if you want to see all debug logs and debug your own code 12 | 13 | To get your own authorization in fetchMaodouAPI, you need to follow these 3 steps: 14 | 1. Search a mini-program in wechat -> discover named "毛豆课堂", click it and login with your phone number. 15 | 2. Re-Login using your phone number in web page [kid.maodouketang.com](https://kid.maodouketang.com). 16 | 3. Find your auth_token in your browser->Application->Local Storage and replace authorization with auth_token value. 17 | 18 | You can also use maodouketang api to create your own reminders for your and your kid's classtime alert. 19 | 20 | Want to do more with this reminder bot? Please read this [maodou api spec](https://maodoukidclass.docs.apiary.io/). 21 | 22 | ## Speical Thanks to 23 | * [xiaoli news API](https://xiaoli.ai) from which comes this original code boilerplate 24 | * [Zixia](https://zixia.net) who inspires me to opensource this code and re-do coding after 15+ years 25 | 26 | ## Some useful referrences 27 | * [@microsoft/recognizers-text-suite](https://github.com/microsoft/Recognizers-Text) I use this nlp api now to parse for time 28 | - [@microsoft/recognizers-text-suite](https://www.npmjs.com/package/@microsoft/recognizers-text-suite) 29 | - [@microsoft/recognizers-text-date-time](https://www.npmjs.com/package/@microsoft/recognizers-text-date-time) 30 | * [bosonnlp](https://bosonnlp.com) I also use this nlp api to parse for title and location 31 | - [BosonNLP NER](http://docs.bosonnlp.com/ner.html) this api is what I only find to get location, anyone knows other good solution? 32 | * [Time-NLP](https://github.com/shinyke/Time-NLP) 33 | * [ChiTimeNLP](https://github.com/JohnnieFucker/ChiTimeNLP) I used these two packages before switching to microsoft 34 | - [chi-time-nlp npm package](https://www.npmjs.com/package/chi-time-nlp) v1.0.4 35 | - [chi-time-nlp-after npm package](https://www.npmjs.com/package/chi-time-nlp-after) v1.0.5 improved version 36 | 37 | ## Contact 38 | * http://maodou.io This is the company I work for 39 | * Wechat ID: limingth, which is also [my personal github](https://github.com/limingth) ID -------------------------------------------------------------------------------- /examples/third-parties/maodou/maodou-classes-bot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Powered by Wechaty - https://github.com/chatie/wechaty 4 | * 5 | * @author limingth 6 | * 7 | * This is a maodouketang reminder wechat bot. 8 | * It can alert you when your class time is up. 9 | * You can get alert by 4 ways: sms, call, email and wxmsg(wechat-miniapp-msg) 10 | */ 11 | import { 12 | Wechaty, 13 | Message, 14 | config, 15 | } from 'wechaty' 16 | import qrTerm from 'qrcode-terminal' 17 | 18 | // import Debug from 'debug' 19 | // const debug = Debug('maodou:api/utils/agenda.js') 20 | import debug from 'debug' 21 | import createCourse from './maodou-course-api.js' 22 | /* 23 | * Declare the Bot 24 | * 25 | */ 26 | 27 | const bot = WechatyBuilder.build({ 28 | //profile: config.default.DEFAULT_PROFILE, 29 | profile: 'maodou', 30 | }) 31 | 32 | /** 33 | * 34 | * Register event handlers for Bot 35 | * 36 | */ 37 | bot 38 | .on('logout', onLogout) 39 | .on('login', onLogin) 40 | .on('scan', onScan) 41 | .on('error', onError) 42 | .on('message', onMessage) 43 | 44 | /** 45 | * 46 | * Start the bot! 47 | * 48 | */ 49 | // getDaily() 50 | bot.start() 51 | .catch(async e => { 52 | console.error('Bot start() fail:', e); 53 | await bot.stop(); 54 | process.exit(-1) 55 | }) 56 | 57 | /** 58 | * 59 | * Define Event Handler Functions for: 60 | * `scan`, `login`, `logout`, `error`, and `message` 61 | * 62 | */ 63 | function onScan(qrcode, status) { 64 | //qrTerm.generate(qrcode, {small: true}) 65 | qrTerm.generate(qrcode) 66 | 67 | // Generate a QR Code online via 68 | // http://goqr.me/api/doc/create-qr-code/ 69 | const qrcodeImageUrl = [ 70 | 'https://wechaty.js.org/qrcode/', 71 | encodeURIComponent(qrcode), 72 | ].join('') 73 | 74 | console.log(`[${status}] ${qrcodeImageUrl}\nScan QR Code above to log in: `) 75 | } 76 | 77 | async function onLogin(user) { 78 | console.log(`${user.name()} login`) 79 | } 80 | 81 | function onLogout(user) { 82 | console.log(`${user.name()} logouted`) 83 | } 84 | 85 | function onError(e) { 86 | console.error('Bot error:', e) 87 | } 88 | 89 | function makeReport(course) { 90 | let news = '[课程创建成功通知]\n' 91 | 92 | let title = '\n标题: ' + course.title + '\n' 93 | let time = '时间: ' + new Date(course.start_time).toLocaleString() + '\n' 94 | let location = '地点: ' + course.location + '\n' 95 | let notes = '\n消息原文: \n' + course.notes + '\n' 96 | 97 | let url = '\n课程链接: https://kid.maodouketang.com/course/' + course._id + '\n' 98 | let report = news + title + time + location + notes + url + '\n- microsoft nlp powered' 99 | 100 | return report 101 | } 102 | 103 | /** 104 | * send a report 105 | */ 106 | async function sendReportToRoom(report, room_topic) { 107 | const room = await bot.Room.find({topic: room_topic}) //get the room by topic 108 | if (room) 109 | console.log('Sending Report to room ', room_topic, 'id:', room.id) 110 | else 111 | console.log('room_topic ', room_topic, '不存在') 112 | 113 | debug('report', report) 114 | room.say(report) 115 | } 116 | 117 | /** 118 | * 119 | * Dealing with Messages 120 | * 121 | */ 122 | async function onMessage(msg) { 123 | const room = msg.room() 124 | const from = msg.from() 125 | 126 | if (!from) { 127 | return 128 | } 129 | 130 | console.log((room ? '[' + await room.topic() + ']' : '') 131 | + '<' + from.name() + '>' 132 | + ':' + msg, 133 | ) 134 | 135 | if (msg.type() !== bot.Message.Type.Text) { 136 | console.log('[Bot] ==> Message discarded because it is not a text message') 137 | return 138 | } 139 | 140 | // Skip message from self 141 | if (msg.self() || from.name() === '微信团队' || from.name() === '毛豆课堂小助手' ) { 142 | console.log('[Bot] ==> Message discarded because it is from self or 毛豆课堂小助手') 143 | return 144 | } 145 | 146 | // Now we begin to parse this msg 147 | let msgText = msg.text() 148 | debug('[original]', {msgText}) 149 | 150 | const room_topic = room ? await room.topic() : null 151 | 152 | // create course using msgText, and send report to wechat admin group 153 | createCourse(msgText, function(newCourse) { 154 | debug("[newCourse]", newCourse) 155 | // get report from newCourse 156 | var report = makeReport(newCourse) 157 | console.log("[New course report]", report) 158 | 159 | // only these 2 admin groups will receive report 160 | if (room_topic === '毛豆少儿课堂产品开发组' || room_topic === 'Wechaty LiLiLi') 161 | sendReportToRoom(report, room_topic) 162 | 163 | // if this message is from a single chatter, just send report back to this chatter 164 | if (!room_topic) 165 | msg.say(report) 166 | }) 167 | } 168 | 169 | -------------------------------------------------------------------------------- /examples/third-parties/maodou/maodou-course-api.js: -------------------------------------------------------------------------------- 1 | import { parseTime, parseTitleAndLocation } from './maodou-nlp.js' 2 | 3 | import debug from 'debug' 4 | import fetch from 'node-fetch' 5 | 6 | function createCourse(originalText, createCallback) { 7 | // get rid of html tags like in case someone use emoji input 8 | var msgText = originalText.replace(/<[^>]*>?/gm, '') 9 | debug("[-emoji]", {msgText}) 10 | 11 | // get rid of blank space in the left 12 | msgText = msgText.replace(/(^\s*)/g, '') 13 | debug("[-space]", {msgText}) 14 | 15 | const time = parseTime(msgText) 16 | console.log('[parser] ==> Time: ', {time}) 17 | 18 | // now we have 'time', next we use bosonnlp to parse for 'title' and 'location' 19 | if (time) { 20 | parseTitleAndLocation(msgText, function(title, location) { 21 | console.log('[parser] ==> Title: ', {title}) 22 | console.log('[parser] ==> Location: ', {location}) 23 | 24 | // title, start_time, location, notes is 4 params to create a new maodou course 25 | const start_time = time 26 | const notes = originalText 27 | createMaodouCourse(title, start_time, location, notes, createCallback) 28 | }) 29 | } 30 | } 31 | 32 | /** 33 | * query Maodou api to create a new course 34 | * @param title: required 35 | * @param start_time, location, notes: options 36 | */ 37 | async function createMaodouCourse(title, start_time, location, notes, createMaodouCourseCallback) { 38 | debug('createCourse params:', {title}, {start_time}, {location}, {notes}) 39 | 40 | let path = '/courses' 41 | let postBody = { 42 | "title": title, 43 | "start_time": start_time, 44 | "location": location, 45 | "duration": 3600, 46 | "count": 1, 47 | "freq": "NONE", 48 | "alerts": [ 49 | // { 50 | // at: -3600, //单位s 51 | // by: 'sms', 52 | // }, 53 | { 54 | at: -1800, 55 | by: 'wxmsg', 56 | }, 57 | { 58 | at: -900, 59 | by: 'call', 60 | }, 61 | ], 62 | "notes": notes 63 | } 64 | 65 | // call maodou api 66 | await fetchMaodouAPI(path, postBody, createMaodouCourseCallback) 67 | return 68 | } 69 | 70 | /** 71 | * Fetch response from Maodou API 72 | * @param URL 73 | * @param postBody 74 | * @param fetchCallback: covert json to msg text when fetch succeeds 75 | */ 76 | async function fetchMaodouAPI(path, postBody, fetchCallback) { 77 | let resText = null 78 | const url = 'https://api.maodouketang.com/api/v1' + path 79 | const options = { 80 | method: "POST", 81 | body: JSON.stringify(postBody), // put keywords and token in the body 82 | // If you want to get alert by your own phone, replace with your own 'authorization' 83 | // To get your own 'authorization', please see it in README.md 84 | headers: { 85 | 'Content-Type': 'application/json', 86 | 'authorization': "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZDA5ZjcxYTQyOTg4YjAwMTI2ZmYxYmMiLCJvcGVuSWQiOiJvRHprWTBUTjlfTmNLdXZCYVo1SzhzeE1NZHNzIiwiaWF0IjoxNTYwOTM0MTcwLCJleHAiOjE1NjYxMTgxNzB9.-NtfK62Y1S_EHAkA2Y0j5BW4qtb7IdH2mpq85NUqPuA" 87 | } 88 | } 89 | debug('fetchMaodouAPI: ', {url}, {options}) 90 | 91 | try { 92 | let resp = await fetch( url, options ) 93 | let resp_json = await resp.json() 94 | if (resp.ok) { 95 | // status code = 200, we got it! 96 | debug('[resp_json.data]', resp_json['data']) 97 | resText = fetchCallback(resp_json['data']) 98 | } else { 99 | // status code = 4XX/5XX, sth wrong with API 100 | debug('[resp_json.msg]', resp_json['msg']) 101 | resText = 'API ERROR: ' + resp_json['msg'] 102 | } 103 | } catch (err) { 104 | debug('[err]', err) 105 | resText = 'NETWORK ERROR: ' + err 106 | } 107 | return resText 108 | } 109 | 110 | module.exports = createCourse 111 | -------------------------------------------------------------------------------- /examples/third-parties/maodou/maodou-nlp.js: -------------------------------------------------------------------------------- 1 | import debug from 'debug' 2 | import bosonnlp from 'bosonnlp' 3 | 4 | import Recognizers from '@microsoft/recognizers-text-suite' 5 | 6 | const defaultCulture = Recognizers.Culture.Chinese; 7 | 8 | //import NLP from 'chi-time-nlp' 9 | // var nlp = new NLP() 10 | 11 | var b_nlp = new bosonnlp.BosonNLP('6wXvIkZk.35344.lbaaVKiTzyh6') 12 | 13 | function getTimeInResults(results) { 14 | debug('getTimeInResult()', {results}) 15 | var result 16 | var date 17 | var time 18 | var datetime 19 | 20 | // if results have date and time, just return value 21 | result = results.find(x => x.typeName === 'datetimeV2.datetime') 22 | if (result) { 23 | if (result.resolution.values[1]) 24 | datetime = new Date(result.resolution.values[1].value) 25 | else 26 | datetime = new Date(result.resolution.values[0].value) 27 | 28 | return datetime 29 | } 30 | 31 | // first deal with time 32 | time = results.find(x => x.typeName === 'datetimeV2.time') 33 | var timeStr 34 | if (time) { 35 | timeStr = time.resolution.values[0].value 36 | if (time.resolution.values[1]) // we prefer the later time for kid's class 37 | timeStr = time.resolution.values[1].value 38 | } else { 39 | time = results.find(x => x.typeName === 'datetimeV2.timerange') 40 | if (time) { 41 | timeStr = time.resolution.values[0].start 42 | } else { 43 | // do nothing with timeStr 44 | } 45 | } 46 | debug({timeStr}) 47 | 48 | if (!timeStr) { 49 | // if results have date and time range, we return start 50 | result = results.find(x => x.typeName === 'datetimeV2.datetimerange') 51 | if (result) { 52 | datetime = new Date(result.resolution.values[0].start) 53 | return datetime 54 | } else 55 | return // undefined 56 | } 57 | 58 | // then deal with date 59 | date = results.find(x => x.typeName === 'datetimeV2.date') 60 | var dateStr 61 | if (date) { 62 | dateStr = date.resolution.values[0].value 63 | } else { 64 | date = results.find(x => x.typeName === 'datetimeV2.daterange') 65 | if (date) { 66 | dateStr = date.resolution.values[0].start 67 | } else { 68 | dateStr = new Date().toDateString() 69 | } 70 | } 71 | debug({dateStr}) 72 | 73 | datetime = new Date(dateStr + ' ' + timeStr) 74 | return datetime 75 | } 76 | 77 | function parseTime(input) { 78 | // no longer using ChiTimeNlp 79 | //const time = nlp.parse(input) 80 | //return time 81 | 82 | var results = Recognizers.recognizeDateTime(input, defaultCulture) 83 | var time = null 84 | 85 | if (results.length > 0) { 86 | // show details in results, we can not use one line to print due to values[] inside 87 | results.forEach(function (result) { 88 | debug(JSON.stringify(result, null, "\t")); 89 | }); 90 | 91 | // we only pick up those item which has datetime or time 92 | //results = results.filter(r => r.typeName === 'datetimeV2.date' || r.typeName === 'datetimeV2.datetime' || r.typeName === 'datetimeV2.time' || r.typeName === 'datetimeV2.timerange' || r.typeName === 'datetimeV2.datetimerange') 93 | //debug('results after filter', results) 94 | 95 | if (results.length > 0) { 96 | time = getTimeInResults(results) 97 | debug('time: ', time && time.toLocaleString()) 98 | } 99 | } 100 | return time 101 | } 102 | 103 | function parseTitleAndLocation(input, callback) { 104 | var title = input.substring(0, 16) 105 | var location = 'beijing' 106 | 107 | b_nlp.ner(input, function (result) { 108 | debug('result:', result); 109 | 110 | var b_result = JSON.parse(result) 111 | debug('result[0]:', b_result[0]); 112 | if (b_result[0]) { 113 | const length = b_result[0]["word"].length 114 | for (var i = 0; i < length; i++) 115 | debug('[index, word, tag] -> ', i, b_result[0]["word"][i], b_result[0]["tag"][i]) 116 | 117 | //debug('result[0]["word"]', b_result[0]["word"]) 118 | title = b_result[0]["word"].filter((x,index) => 119 | b_result[0]["tag"][index] === 'n' || 120 | b_result[0]["tag"][index] === 'nl' || 121 | b_result[0]["tag"][index] === 'nz' || 122 | b_result[0]["tag"][index] === 'v' || 123 | b_result[0]["tag"][index] === 'vi' || 124 | b_result[0]["tag"][index] === 's') 125 | .slice(0, 3) 126 | .join('') 127 | 128 | location = b_result[0]["word"].filter((x,index) => 129 | b_result[0]["tag"][index] === 'ns' || 130 | b_result[0]["tag"][index] === 'nt' || 131 | b_result[0]["tag"][index] === 'nz' || 132 | b_result[0]["tag"][index] === 'an' || 133 | b_result[0]["tag"][index] === 'n' || 134 | b_result[0]["tag"][index] === 'm' || 135 | b_result[0]["tag"][index] === 'q' || 136 | b_result[0]["tag"][index] === 's') 137 | .slice(0, 5) 138 | .join('') 139 | 140 | // [2, 9, "location"] 141 | var location_array = b_result[0].entity.filter(item => item.indexOf("location")>=0) 142 | 143 | if (location_array.length > 0) { 144 | // clear the old location value 145 | location = "" 146 | 147 | for (var i = 0; i < location_array.length; i++) { 148 | const from = location_array[i][0] 149 | const to = location_array[i][1] 150 | //debug({from}, {to}) 151 | 152 | const l = b_result[0]["word"].slice(from, to).join('') 153 | //debug(l) 154 | 155 | // make the new location value 156 | location += l 157 | } 158 | } 159 | 160 | callback(title, location) 161 | } 162 | }); 163 | } 164 | 165 | module.exports = { parseTime, parseTitleAndLocation } 166 | -------------------------------------------------------------------------------- /examples/third-parties/maodou/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maodou-classes-bot", 3 | "version": "0.1.0", 4 | "description": "maodou-classes-bot is a class reminder bot based on wechaty", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">= 10" 8 | }, 9 | "scripts": { 10 | "postinstall": "check-node-version --node \">= 10\"", 11 | "start": "node maodou-classes-bot.js", 12 | "dev": "DEBUG=maodou* node maodou-classes-bot.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/maodouio/wechaty-getting-started.git" 17 | }, 18 | "keywords": [], 19 | "author": "", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/maodouio/wechaty-getting-started/issues" 23 | }, 24 | "homepage": "https://github.com/maodouio/wechaty-getting-started#readme", 25 | "dependencies": { 26 | "@microsoft/recognizers-text-suite": "^1.1.4", 27 | "bosonnlp": "^0.1.0", 28 | "chi-time-nlp": "^1.0.4", 29 | "node-fetch": "^2.6.0", 30 | "qrcode-terminal": "^0.12.0", 31 | "wechaty": "^0.26.1", 32 | "wechaty-puppet": "^0.14.1", 33 | "wechaty-puppet-wechat": "^0.26" 34 | }, 35 | "devDependencies": { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/third-parties/xiaoli/README.md: -------------------------------------------------------------------------------- 1 | ## Instructions for xiaoli-news-bot 2 | 3 | This is a third-party bot built using wechaty and [xiaoli news API](https://xiaoli.ai). 4 | 5 | Within this directory, type ```npm install``` and ```npm start``` and you are good to go! 6 | 7 | For more information about this news bot read [this blog](https://blog.chatie.io/wechaty-xiaoli/). 8 | -------------------------------------------------------------------------------- /examples/third-parties/xiaoli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechaty-getting-started", 3 | "version": "0.1.0", 4 | "description": "Wechaty is a Wechat Bot SDK for Personal Account in Node.js", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">= 10" 8 | }, 9 | "scripts": { 10 | "postinstall": "check-node-version --node \">= 10\"", 11 | "start": "node xiaoli-news-bot.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/Chatie/wechaty-getting-started.git" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/Chatie/wechaty-getting-started/issues" 22 | }, 23 | "homepage": "https://github.com/Chatie/wechaty-getting-started#readme", 24 | "dependencies": { 25 | "qrcode-terminal": "^0.12.0", 26 | "wechaty": "^0.19.137", 27 | "node-fetch": "^2.2.0", 28 | "node-schedule": "^1.3.0" 29 | }, 30 | "devDependencies": { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/third-parties/xiaoli/xiaoli-news-bot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Powered by Wechaty - https://github.com/chatie/wechaty 4 | * 5 | * @author judaschrist 6 | * 7 | * This is a simple news wechat bot. 8 | * It can tell you the latest news about anything you're interested in. 9 | * It can also send you news briefings on a daily basis. 10 | * Read more about this bot in https://blog.chatie.io/wechaty-xiaoli/ 11 | * The bot is implemented using Wechaty and xiaoli news API - https://xiaoli.ai 12 | */ 13 | import { 14 | Wechaty, 15 | config, 16 | } from 'wechaty' 17 | import qrTerm from 'qrcode-terminal' 18 | import fetch from 'node-fetch' 19 | import schedule from 'node-schedule' 20 | 21 | /** 22 | * 23 | * Declare the Bot 24 | * 25 | */ 26 | const bot = WechatyBuilder.build({ 27 | profile: config.default.DEFAULT_PROFILE, 28 | }) 29 | 30 | /** 31 | * 32 | * Register event handlers for Bot 33 | * 34 | */ 35 | bot 36 | .on('logout', onLogout) 37 | .on('login', onLogin) 38 | .on('scan', onScan) 39 | .on('error', onError) 40 | .on('message', onMessage) 41 | 42 | /** 43 | * 44 | * Start the bot! 45 | * 46 | */ 47 | // getDaily() 48 | bot.start() 49 | .catch(async e => { 50 | console.error('Bot start() fail:', e); 51 | await bot.stop(); 52 | process.exit(-1) 53 | }) 54 | 55 | /** 56 | * 57 | * Define Event Handler Functions for: 58 | * `scan`, `login`, `logout`, `error`, and `message` 59 | * 60 | */ 61 | function onScan(qrcode, status) { 62 | qrTerm.generate(qrcode, {small: true}) 63 | 64 | // Generate a QR Code online via 65 | // http://goqr.me/api/doc/create-qr-code/ 66 | const qrcodeImageUrl = [ 67 | 'https://wechaty.js.org/qrcode/', 68 | encodeURIComponent(qrcode), 69 | ].join('') 70 | 71 | console.log(`[${status}] ${qrcodeImageUrl}\nScan QR Code above to log in: `) 72 | } 73 | 74 | async function onLogin(user) { 75 | console.log(`${user.name()} login`) 76 | schedule.scheduleJob('40 50 16 * * 1-5', sendDaily); //send daily on 16:50:40 every weekday 77 | } 78 | 79 | function onLogout(user) { 80 | console.log(`${user.name()} logouted`) 81 | } 82 | 83 | function onError(e) { 84 | console.error('Bot error:', e) 85 | } 86 | 87 | /** 88 | * send a daily 89 | */ 90 | async function sendDaily() { 91 | const room = await bot.Room.find({topic: '小桔和小理'}) //get the room by topic 92 | console.log('Sending daily to room ' + room.id) 93 | let dailyText = await getDaily() 94 | room.say(dailyText) 95 | } 96 | 97 | 98 | /** 99 | * list of the news details 100 | * @type {Array} 101 | */ 102 | let preNewsList = [] 103 | /** 104 | * 105 | * Dealing with Messages 106 | * 107 | */ 108 | async function onMessage(msg) { 109 | console.log(msg.toString()) 110 | 111 | if (msg.type() !== bot.Message.Type.Text) { 112 | console.log('Message discarded because it is not a text message') 113 | return 114 | } 115 | 116 | let msgText = msg.text() 117 | 118 | // A super naive implementation of intent detection for news query 119 | if (msgText.endsWith("最新消息") && msgText.length > 4) { 120 | respText = await searchNews(msgText.substring(0, msgText.length-4)) 121 | await msg.say(respText) 122 | } 123 | 124 | // query for news details 125 | if (msgText.startsWith('#')) { 126 | newsNum = parseInt((msgText.substring(1)), 10) - 1 127 | if (newsNum < preNewsList.length && newsNum >= 0) { 128 | await msg.say(preNewsList[newsNum]) 129 | } 130 | } 131 | 132 | } 133 | 134 | /** 135 | * query xiaoli's api for news related to the keyword 136 | * @param keyword: search keyword 137 | */ 138 | async function searchNews(keyword) { 139 | let searchURL = 'https://api.xiaoli.ai/v1/api/search/basic' 140 | let postBody = { 141 | "keywords": [keyword], 142 | "token": "45d898b459b4a739474175657556249a" 143 | } 144 | let okCallback = makeSearchResponseText 145 | let resText = await fetchXiaoliAPI(searchURL, postBody, okCallback) 146 | return resText 147 | } 148 | 149 | /** 150 | * parse the returned json for a list of news titles 151 | */ 152 | function makeSearchResponseText(json_obj) { 153 | preNewsList = [] 154 | let newsList = json_obj.contents 155 | if (newsList.length === 0) { 156 | return "暂无相关新闻" 157 | } 158 | let newsText = '' 159 | for (let i = 0; i < newsList.length; i++) { 160 | newsText += (i+1) + '. ' + newsList[i].title + '\n' 161 | preNewsList.push(newsList[i].news_abstract) // Save the news details for later queries 162 | } 163 | newsText += "\n回复\"#+数字\"(例如\"#1\")看详情" 164 | return newsText 165 | } 166 | 167 | /** 168 | * query xiaoli's api for a daily news brief 169 | */ 170 | async function getDaily() { 171 | const dailyUuid = 'e02e6f14-3212-4d44-9f3d-1d79538c38f6' 172 | let dailyURL = 'https://api.xiaoli.ai/v1/api/briefing/' + dailyUuid 173 | let postBody = { 174 | "token": "45d898b459b4a739474175657556249a" 175 | } 176 | let okCallback = makeDailyResponseText 177 | let resText = await fetchXiaoliAPI(dailyURL, postBody, okCallback) 178 | return resText 179 | } 180 | 181 | 182 | function makeDailyResponseText(json_obj) { 183 | let secList = json_obj.sections 184 | let newsText = '今日' + json_obj.title + '\n\n' 185 | for (let i = 0; i < Math.min(secList.length, 5); i++) { 186 | newsText += secList[i].title + '\n' 187 | let newsList = secList[i].contents 188 | for (let j = 0; j < Math.min(newsList.length, 3); j++) { 189 | newsText += (j+1) + '. ' + newsList[j].title + '\n' 190 | } 191 | newsText += '\n' 192 | } 193 | return newsText 194 | } 195 | 196 | 197 | /** 198 | * Fetch response from xiaoli API 199 | * @param URL 200 | * @param postBody 201 | * @param okCallback: covert json to msg text when fetch succeeds 202 | */ 203 | async function fetchXiaoliAPI(URL, postBody, okCallback) { 204 | let resText = null 205 | try { 206 | let resp = await fetch( 207 | URL, 208 | { 209 | method: "POST", 210 | body: JSON.stringify(postBody), // put keywords and token in the body 211 | } 212 | ) 213 | let resp_json = await resp.json() 214 | if (resp.ok) { 215 | // status code = 200, we got it! 216 | resText = okCallback(resp_json['data']) 217 | } else { 218 | // status code = 4XX/5XX, sth wrong with API 219 | resText = 'API ERROR: ' + resp_json['msg'] 220 | } 221 | } catch (err) { 222 | resText = 'NETWORK ERROR: ' + err 223 | } 224 | return resText 225 | } 226 | -------------------------------------------------------------------------------- /examples/tutorials/google-cloud-shell-tutorial.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Getting Started with Wechaty 4 | 5 | 7 | 8 | 9 | ## Let's get started! 10 | 11 | Get your up and running quickly with Wechaty by this interactive tutorial. 12 | 13 | ![Wechaty][wechaty_logo_url] 14 | 15 | [wechaty_logo_url]: https://wechaty.js.org/img/wechaty-logo.svg 16 | 17 | This guide will show you how to setup and run our Wechaty ding-dong BOT. It'll also walk you through install all dependencies, run your bot up, and add a new feature to it. 18 | 19 | Click the **Start** button to move to the next step. 20 | 21 | ## What is Wechaty? 22 | 23 | Before you get started, let's briefly go over what Wechaty can do. 24 | 25 | Wechaty is a Conversatioanl RPA SDK for Chatbot Makers. It's well designed with an easy to use API, supports all OSs including Linux, OSX, Win32, Docker, and lots of IMs including WeChat, Whatsapp, WeCom, Lark, Gitter, etc. 26 | 27 | ![Wechaty][wechaty_flyer_url] 28 | 29 | [wechaty_flyer_url]: https://wechaty.js.org/assets/2021/01-wechaty-0.56-released/wechaty-flyer-2020.webp 30 | 31 | As a developer, you can use Wechaty to easily build your bot, effectively manage message sending/receiving, room creating/inviting, contact friendship, and delightful add artificial intelligence between users and your bot. 32 | 33 | This repository should work out-of-the-box, and is the best start point for Wechaty beginners. 34 | 35 | Continue on to the **Next** step to start setting up your chatbot. 36 | 37 | ## Setting up developing environments 38 | 39 | 1. Install [Node.js](https://nodejs.org) v16+ 40 | 1. Git clone [wechaty/getting-started](https://github.com/wechaty/getting-started) repo 41 | 1. Install system dependency packages 42 | 43 | ### 1. Install Node.js 44 | 45 | Node.js has been pre-installed in Google Cloud Shell. 46 | 47 | However, we need to upgrade Node.js to version 16+ 48 | 49 | ```sh 50 | nvm install 16 51 | ``` 52 | 53 | You should get a `v16.13.0` output by running command `node --version` 54 | 55 | ### 2. Git clone `wechaty/getting-started` repo 56 | 57 | Git Repo has been already cloned in Google Cloud Shell already. 58 | 59 | ### 3. Install system dependency packages 60 | 61 | The `puppeteer` needs `libgbm1` and `libatk-bridge2.0` to be presented in the system, we need to install it first, run: 62 | 63 | ```sh 64 | sudo apt install -y libgbm1 libxkbcommon-x11-0 65 | sudo apt-get install -y libatk-bridge2.0-0 66 | ``` 67 | 68 | ## NPM Install 69 | 70 | Let's install NPM packages, run: 71 | 72 | ```sh 73 | npm install 74 | ``` 75 | 76 | The NPM system should help you to install all the dependencies by Wechaty, it will take 1 - 3 minutes. 77 | 78 | After all dependencies have been resolved, we will be able to start our ding-dong BOT. 79 | 80 | Click **Next**. 81 | 82 | ## NPM Start 83 | 84 | Let's start our ding-dong BOT, it's source code [examples/ding-dong-bot.ts] should have already opened in the editor. 85 | 86 | If it's not opened yet, click Open to open it now. 87 | 88 | Our `npm start` script is defined as `ts-node examples/ding-dong-bot.ts`. (`ts-node` is the `node` for TypeScript) 89 | 90 | ### 1. Get a Whatsapp login QR Code 91 | 92 | To start our bot with Whatsapp, we need to define `WECHATY_PUPPET` as `wechaty-puppet-whatsapp`, run: 93 | 94 | ```sh 95 | export WECHATY_PUPPET=wechaty-puppet-whatsapp 96 | npm start 97 | ``` 98 | 99 | You should see our bot start generating logs and after a while, a QR Code will be printed. 100 | 101 | You can use your Whatsapp to scan it to login your bot now. 102 | 103 | ### 2. Get a WeChat login QR Code 104 | 105 | To start our bot with WeChat, we can define `WECHATY_PUPPET` as `wechaty-puppet-wechat`, run: 106 | 107 | ```sh 108 | export WECHATY_PUPPET=wechaty-puppet-wechat 109 | npm start 110 | ``` 111 | 112 | You should see our bot start generating logs and after a while, a QR Code will be printed. 113 | 114 | You can use your WeChat app to scan it to login your bot now. 115 | 116 | ### 3. Play with your bot 117 | 118 | Please play with your bot for a while now. **Next** we will add our first new feature to the bot. 119 | 120 | ## Add a new feature 121 | 122 | We received a new feature request! 123 | 124 | _“reply `dongdong` for `dingding`”_ 125 | 126 | ### 1. Requirement analysis 127 | 128 | If you have finished the previous step, and login-ed successfully, then your bot will be able to reply a `dong` for any message send it as a `ding`, no matter which IM(Whatsapp or WeChat) your bot are working with. 129 | 130 | Now we get a new feature request from our client: reply `dongdong` for `dingding`. 131 | 132 | It will be a hard work but we will make it! 133 | 134 | ### 2. Code explaination 135 | 136 | In our `examples/ding-dong-bot.ts`, you can find the onMessage function: 137 | 138 | ```ts 139 | async function onMessage (msg: Message) { 140 | log.info('StarterBot', msg.toString()) 141 | 142 | if (msg.text() === 'ding') { 143 | await msg.say('dong') 144 | } 145 | } 146 | ``` 147 | 148 | Let's start working on it now. 149 | 150 | ### 3. Developing new code 151 | 152 | We need to check the `msg.text()` for `dingding` when receiving messages, and use `msg.say('dongdong')` to replay! 153 | 154 | ```ts 155 | if (msg.text() === 'dingding') { 156 | await msg.say('dongdong') 157 | } 158 | ``` 159 | 160 | It looks great! 161 | 162 | ### 4. Final `onMessage` function 163 | 164 | Let's put out new code into the `onMessage` function: 165 | 166 | ```ts 167 | async function onMessage (msg: Message) { 168 | log.info('StarterBot', msg.toString()) 169 | 170 | if (msg.text() === 'ding') { 171 | await msg.say('dong') 172 | } 173 | if (msg.text() === 'dingding') { 174 | await msg.say('dongdong') 175 | } 176 | } 177 | ``` 178 | 179 | That's it! We have added 3 lines of code to implment our new feature. Do not forget to save the file changes to the disk. 180 | 181 | **Next** up, starting our braning new ding-dong BOT! 182 | 183 | ## Test new feature 184 | 185 | We will start our ding-dong BOT again. 186 | 187 | ### 1. Select puppet to use by WCHATY_PUPPET 188 | 189 | You can set `WECHATY_PUPPET` to either `wechaty-puppet-whatapp` or `wechaty-puppet-wechat`, depends where you want to test the bot. 190 | 191 | ### 2. Run 192 | 193 | If you decide to use Whatsapp, run: 194 | 195 | ```sh 196 | export WECHATY_PUPPET=wechaty-puppet-whatsapp 197 | npm start 198 | ``` 199 | 200 | The ding-dong BOT should start outputing log messages, after a while shows you a QR Code for login. 201 | 202 | Scan it on your phone, and then try to send `dingding` to the bot, you should receive `dongdong` instantly! 203 | 204 | Enjoy your bot, and click **Next** when you like. 205 | 206 | ## Congratulations 207 | 208 | 209 | 210 | You're all set! 211 | 212 | You have finished the Wechaty Getting Started Tutorial, and be able to run Wechaty in TypeScript, receiving and replying messages on Whatsapp and WeChat! 213 | 214 | Congratulations to be a Wechaty developer now! 215 | 216 | You can now start adding more features to our ding-dong BOT and have the bot working for your users with ease. 217 | 218 | For a complete list of Wechaty Examples, refer to the [Wechaty Examples Directory](https://github.com/wechaty/getting-started/blob/master/examples/README.md). 219 | 220 | ## Conclusion 221 | 222 | Done! 223 | 224 | Learn more about Wechaty from our [docs](https://wechaty.js.org/docs/) and [blog](https://wechaty.js.org/blog/), and [join our Gitter](https://gitter.im/wechaty/wechaty) network if you aren’t already a member! 225 | 226 | Wechaty © 2016 - 2021 227 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechaty-getting-started", 3 | "version": "1.18.12", 4 | "description": "Wechaty conversational SDK getting start template & examples", 5 | "type": "module", 6 | "engines": { 7 | "node": ">=16", 8 | "npm": ">=7" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "lint": "eslint \"examples/*.ts\" \"tests/*.ts\"", 13 | "postinstall": "check-node-version --node \">=16\" --npm \">=7\"", 14 | "cqrs": "cross-env NODE_OPTIONS=\"--no-warnings --loader=ts-node/esm\" node examples/cqrs/ding-dong-bot.ts ", 15 | "start": "cross-env NODE_OPTIONS=\"--no-warnings --loader=ts-node/esm\" node examples/ding-dong-bot.ts", 16 | "start:js": "node examples/ding-dong-bot.js", 17 | "start:walnut": "cross-env WECHATY_LOG=verbose WECHATY_PUPPET=wechaty-puppet-walnut npm start", 18 | "start:service": "cross-env WECHATY_LOG=verbose WECHATY_PUPPET=wechaty-puppet-service npm start", 19 | "start:wechat:web": "cross-env WECHATY_LOG=verbose WECHATY_PUPPET=wechaty-puppet-wechat npm start", 20 | "start:wechat:padlocal": "cross-env WECHATY_LOG=verbose WECHATY_PUPPET=wechaty-puppet-padlocal npm start", 21 | "start:whatsapp:web": "cross-env WECHATY_LOG=verbose WECHATY_PUPPET=wechaty-puppet-whatsapp npm start", 22 | "test": "npm-run-all lint test:smoke", 23 | "test:smoke": "cross-env NODE_OPTIONS=\"--no-warnings --loader=ts-node/esm\" tap \"tests/**/*.spec.ts\"" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/wechaty/getting-started.git" 28 | }, 29 | "keywords": [], 30 | "author": "Huan LI ", 31 | "license": "Apache-2.0", 32 | "bugs": { 33 | "url": "https://github.com/wechaty/getting-started/issues" 34 | }, 35 | "homepage": "https://github.com/wechaty/getting-started#readme", 36 | "dependencies": { 37 | "dotenv": "^16.0.0", 38 | "npm-run-all": "^4.1.5", 39 | "qrcode-terminal": "^0.12.0", 40 | "wechaty": "^1.18.1", 41 | "wechaty-cqrs": "^0.7.5", 42 | "wechaty-plugin-contrib": "^1.11.1", 43 | "wechaty-puppet-oicq": "^1.10.2", 44 | "wechaty-puppet-walnut": "^1.11.6", 45 | "wechaty-puppet-wechat": "^1.18.1", 46 | "wechaty-puppet-wechat4u": "~1.11.1", 47 | "wechaty-puppet-whatsapp": "^1.19.16" 48 | }, 49 | "devDependencies": { 50 | "@chatie/eslint-config": "^1.0.4", 51 | "@chatie/git-scripts": "^0.6.2", 52 | "@chatie/tsconfig": "^4.6.3", 53 | "check-node-version": "^4.2.1", 54 | "is-pr": "^2.0.0", 55 | "ts-node": "^10.9.1", 56 | "wechaty-puppet-mock": "^1.18.2" 57 | }, 58 | "git": { 59 | "scripts": { 60 | "pre-push": "npx git-scripts-pre-push" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/wechaty-puppet-mock.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | 3 | import { test } from 'tstest' 4 | import { WechatyBuilder } from 'wechaty' 5 | 6 | import { 7 | mock, 8 | PuppetMock, 9 | } from 'wechaty-puppet-mock' 10 | 11 | test('wechaty-puppet-mock', async t => { 12 | const mocker = new mock.Mocker() 13 | mocker.use(mock.SimpleEnvironment()) 14 | const puppetMock = new PuppetMock({ mocker }) 15 | 16 | const bot = WechatyBuilder.build({ 17 | puppet: puppetMock, 18 | }) 19 | 20 | const timer = setTimeout(() => { 21 | console.error('Smoke testing timeout after 2 minutes.') 22 | process.exit(1) 23 | }, 120 * 1000) 24 | 25 | const future = new Promise(resolve => bot.once('scan', resolve)) 26 | await bot.start() 27 | await future 28 | await bot.stop() 29 | 30 | clearTimeout(timer) 31 | t.pass(`Puppet ${bot.puppet} v${bot.puppet.version()} smoke testing passed.`) 32 | }) 33 | -------------------------------------------------------------------------------- /tests/wechaty-puppet-service.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | 3 | import { test } from 'tstest' 4 | import { WechatyBuilder } from 'wechaty' 5 | 6 | import 'dotenv/config.js' 7 | 8 | test('wechaty-puppet-service', async t => { 9 | const bot = WechatyBuilder.build({ 10 | puppet: 'wechaty-puppet-service', 11 | /** 12 | * Huan(202108): our puppet service token is no-TLS for now. 13 | * FIXME: enable TLS in the future 14 | */ 15 | puppetOptions: { tls: { disable: true } }, 16 | }) 17 | 18 | const timer = setTimeout(() => { 19 | console.error('Smoke testing timeout after 2 minutes.') 20 | process.exit(1) 21 | }, 120 * 1000) 22 | 23 | const future = new Promise(resolve => bot.once('scan', resolve)) 24 | await bot.start() 25 | await future 26 | await bot.stop() 27 | 28 | clearTimeout(timer) 29 | t.pass(`Puppet ${bot.puppet} v${bot.puppet.version()} smoke testing passed.`) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/wechaty-puppet-wechat.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | 3 | import { test } from 'tstest' 4 | import { WechatyBuilder } from 'wechaty' 5 | 6 | test('wechaty-puppet-wechat', async t => { 7 | const optionsWeChat = { 8 | puppet: 'wechaty-puppet-wechat', 9 | puppetOptions: { 10 | launchOptions: { 11 | ignoreDefaultArgs: [ '--disable-extensions' ], 12 | }, 13 | }, 14 | } as const 15 | 16 | const bot = WechatyBuilder.build(optionsWeChat) 17 | 18 | const timer = setTimeout(() => { 19 | console.error('Smoke testing timeout after 2 minutes.') 20 | process.exit(1) 21 | }, 120 * 1000) 22 | 23 | const future = new Promise(resolve => bot.once('scan', resolve)) 24 | await bot.start() 25 | await future 26 | await bot.stop() 27 | 28 | clearTimeout(timer) 29 | t.pass(`Puppet ${bot.puppet} v${bot.puppet.version()} smoke testing passed.`) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/wechaty-puppet-wechat4u.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | 3 | import { test } from 'tstest' 4 | import { WechatyBuilder } from 'wechaty' 5 | 6 | test('wechaty-puppet-wechat4u', async t => { 7 | const bot = WechatyBuilder.build({ puppet: 'wechaty-puppet-wechat4u' }) 8 | 9 | const timer = setTimeout(() => { 10 | console.error('Smoke testing timeout after 2 minutes.') 11 | process.exit(1) 12 | }, 120 * 1000) 13 | 14 | const future = new Promise(resolve => bot.once('scan', resolve)) 15 | await bot.start() 16 | await future 17 | await bot.stop() 18 | 19 | clearTimeout(timer) 20 | t.pass(`Puppet ${bot.puppet} v${bot.puppet.version()} smoke testing passed.`) 21 | }) 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@chatie/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "exclude": [ 7 | "node_modules/", 8 | "dist/", 9 | "tests/fixtures/" 10 | ], 11 | "include": [ 12 | "examples/*.ts", 13 | "scripts/**/*.ts", 14 | "src/**/*.ts", 15 | "tests/**/*.ts" 16 | ], 17 | } 18 | --------------------------------------------------------------------------------