chore: import upstream snapshot with attribution
Coverage / build (push) Waiting to run
Docker / Build (push) Waiting to run
Docker / Publish (push) Blocked by required conditions
Node.js CI / build (16, macos-latest) (push) Waiting to run
Node.js CI / build (16, ubuntu-latest) (push) Waiting to run
Node.js CI / build (16, windows-latest) (push) Waiting to run
NPM / Build (22) (push) Waiting to run
NPM / Pack (push) Blocked by required conditions
NPM / Publish (push) Blocked by required conditions

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:42 +08:00
commit d5f55b5f9c
197 changed files with 21977 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
languages:
Ruby: false
JavaScript: true
PHP: false
Python: false
exclude_paths:
- "/doc/"
- "**/*.spec.ts"
+6
View File
@@ -0,0 +1,6 @@
dist
docs
node_modules
*.memory-card.json
t/
t.*
+19
View File
@@ -0,0 +1,19 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = 0
trim_trailing_whitespace = false
# 4 tab indentation
[Makefile]
indent_style = tab
indent_size = 4
+8
View File
@@ -0,0 +1,8 @@
const rules = {
'no-use-before-define': 'off',
}
module.exports = {
extends: '@chatie',
rules,
}
+5
View File
@@ -0,0 +1,5 @@
#
# https://help.github.com/articles/about-codeowners/
#
/ @wechaty/contributors
+132
View File
@@ -0,0 +1,132 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<huan@chatie.io>
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+8
View File
@@ -0,0 +1,8 @@
# These are supported funding model platforms
# github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
# patreon: # Replace with a single Patreon username
open_collective: wechaty
# ko_fi: # Replace with a single Ko-fi username
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
# custom: # Replace with a single custom sponsorship URL
@@ -0,0 +1,90 @@
---
name: Wechaty Bug Report
about: Create a bug report for a bug you found in wechaty
---
> ImportantPlease file the issue follow the template, or we won't be able to help you to solve the problem.
## 0. Report Issue Guide
1. Please run the following command and check whether the problem has been fixed
```sh
rm -rf package-lock.json
rm -rf node_modules
npm install
```
2. Please search in [FAQ List](https://wechaty.js.org/faq) first, and make sure your problem has not been solved before.
3. Please search in the issue first, and make sure your problem had not been reported before
## 1. Versions
> What is your wechaty version?
Answer:
> Which puppet are you using for wechaty? (puppeteer/padlocal/service...)
Answer:
> What is your wechaty-puppet-XXXpadchat/puppeteer/ version?
Answer:
> What is your node version? (run `node --version`)
Answer:
> What os are you using
Answer:
## 2. Describe the bug
> Give a clear and concise description of what the bug is.
## 3. To Reproduce
> This part is very important: if you can not provide any reproduce steps, then the problem will be very hard to be recognized.
**[How to create a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example)**
Steps to reproduce the behavior:
1. run '...'
2. ...
3. ...
## 4. Expected behavior
> Give a clear and concise description of what you expected to happen.
## 5. Actual behavior
> If applicable, add screenshots to help explain your problem. But the log message must be in plain text instead of screenshot.
## 6. Full Output Logs
> 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)
**We need full log instead of log screenshot or log fragments!**
<details>
<summary>
Show Logs
</summary>
```shell
$ WECHATY_LOG=silly node yourbot.js
-> Make sure to copy/paste the full (instead of only fragments) log message in text format (instead of image/screenshot) at here
```
</details>
## 7. Additional context
> Add any other context about the problem here.
@@ -0,0 +1,19 @@
---
name: Wechaty Feature Request
about: Suggest a feature for wechaty
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
[enhancement]
@@ -0,0 +1,11 @@
---
name: Wechaty Question
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/wechaty
---
🚨 The issue tracker is not for questions 🚨
If you have a question, please ask it on https://stackoverflow.com/questions/tagged/wechaty
[question]
+12
View File
@@ -0,0 +1,12 @@
## Checklist
- [ ] Commit Messages follow the [Conventional Commits](https://conventionalcommits.org/) pattern
- A feature commit message is prefixed "feat:"
- A bugfix commit message is prefixed "fix:"
- [ ] Tests for the changes have been added
- [ ] CI has been passed. (GitHub actions all turns green)
- [ ] CLA has been signed
## Description
> please describe the changes that you are making, with the related issue number.
+8
View File
@@ -0,0 +1,8 @@
template: |
## Whats Changed
$CHANGES
## Contributors
$CONTRIBUTORS
+26
View File
@@ -0,0 +1,26 @@
name: Coverage
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: |
echo See: https://github.com/wechaty/wechaty/issues/2242
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v2
# - run: npm install
# - run: |
# npm test
# npm run coverage
# env:
# CI: true
# - name: Coveralls
# uses: coverallsapp/github-action@master
# with:
# github-token: ${{ secrets.GITHUB_TOKEN }}
+55
View File
@@ -0,0 +1,55 @@
name: Docker
on: [push, pull_request]
# push:
# branches:
# - main
# - 'v[0-9]+*'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: reviewdog/action-shellcheck@v1
- name: Install Bats
run: |
curl -sSL -o /tmp/bats_v0.4.0.tar.gz https://github.com/sstephenson/bats/archive/v0.4.0.tar.gz
tar -xf /tmp/bats_v0.4.0.tar.gz
sudo bats-0.4.0/install.sh /usr/local
- name: Build the Docker image
run: ./scripts/docker.sh build
- name: Test the Docker image
run: ./scripts/docker.sh test
# https://www.prestonlamb.com/blog/creating-a-docker-image-with-github-actions
publish:
name: Publish
needs: [build]
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/v'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
install: true
version: latest
- name: Builder instance name
run: echo ${{ steps.buildx.outputs.name }}
- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Login to DockerHub Registry
run: echo ${{ secrets.DOCKER_PERSONAL_ACCESS_TOKEN }} | docker login -u zixia --password-stdin
- name: Deploy to Docker Hub
run: ./scripts/docker.sh deploy
+36
View File
@@ -0,0 +1,36 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
strategy:
matrix:
os:
- macos-latest
- windows-latest
- ubuntu-latest
node-version:
- 16
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: package.json
- run: npm install
- run: npm run build --if-present
- run: npm test
env:
CI: true
+88
View File
@@ -0,0 +1,88 @@
name: NPM
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
node-version:
- 22
steps:
- uses: actions/checkout@v4
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
cache-dependency-path: package.json
- name: Install Dependencies
run: |
node --version
npm --version
npm install
- name: Test
run: npm test
pack:
name: Pack
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: package.json
- run: npm install
- name: Generate Package JSON
run: ./scripts/generate-package-json.sh
- run: ./scripts/npm-pack-testing.sh
env:
WECHATY_PUPPET_PADPLUS_TOKEN: ${{ secrets.WECHATY_PUPPET_PADPLUS_TOKEN }}
publish:
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/v'))
name: Publish
needs: [build, pack]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org/
cache: npm
cache-dependency-path: package.json
- run: npm install
- name: Generate Package JSON
run: ./scripts/generate-package-json.sh
- run: ./scripts/package-publish-config-tag.sh
- run: npm run dist
- name: Check Branch
id: check-branch
run: |
if [[ ${{ github.ref }} =~ ^refs/heads/(main|v[0-9]+\.[0-9]+.*)$ ]]; then
echo ::set-output name=match::true
fi # See: https://stackoverflow.com/a/58869470/1123955
- name: Is A Publish Branch
if: steps.check-branch.outputs.match == 'true'
run: |
NAME=$(npx pkg-jq -r .name)
VERSION=$(npx pkg-jq -r .version)
if npx version-exists "$NAME" "$VERSION"
then echo "$NAME@$VERSION exists on NPM, skipped."
else echo "Publishing..." && npm publish
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Is Not A Publish Branch
if: steps.check-branch.outputs.match != 'true'
run: echo 'Not A Publish Branch'
+77
View File
@@ -0,0 +1,77 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
node_modules
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
npm-shrinkwrap.json
.*.swp
# TAP Coverage
coverage
.nyc_output
# Wechaty session file
demo.wechaty.json
# Cloud 9
.c9/
# Other staff
0
t/
t.*
.DS_Store
/dist/
yarn.lock
.yarn/
.yarn-cache/
.v8flags.*
# why? forgot... 2017-05-18 by huan
/tests/fixtures/docker/package.json
tags
*.memory-card.json
*.wechaty.json
/package
/wechaty-*.*.*.tgz
*.bak
package-lock.json
.babel.json
.idea
examples/.config
examples/.pki
.config/
log
/*.jpg
+15
View File
@@ -0,0 +1,15 @@
{
"default": true,
"no-trailing-punctuation": {
"punctuation": ".,;:!"
},
"MD013": false,
"MD033": {
"allowed_elements": ["dl", "dt", "dd", "code", "var", "cite"]
},
"first-line-h1": false,
"no-hard-tabs": true,
"no-trailing-spaces": {
"br_spaces": 2
}
}
+3
View File
@@ -0,0 +1,3 @@
{
"reporter": ["lcov", "text-summary"]
}
Vendored Executable
+99
View File
@@ -0,0 +1,99 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"editor.fontFamily": "Consolas, 'Courier New', monospace",
"editor.fontLigatures": true,
"editor.tokenColorCustomizations": {
"textMateRules": [
{
"scope": [
//following will be in italics (=Pacifico)
"comment",
// "entity.name.type.class", //class names
"keyword", //import, export, return…
"support.class.builtin.js", //String, Number, Boolean…, this, super
"storage.modifier", //static keyword
"storage.type.class.js", //class keyword
"storage.type.function.js", // function keyword
"storage.type.js", // Variable declarations
"keyword.control.import.js", // Imports
"keyword.control.from.js", // From-Keyword
"entity.name.type.js", // new … Expression
"keyword.control.flow.js", // await
"keyword.control.conditional.js", // if
"keyword.control.loop.js", // for
"keyword.operator.new.js", // new
],
"settings": {
"fontStyle": "italic",
},
},
{
"scope": [
//following will be excluded from italics (My theme (Monokai dark) has some defaults I don't want to be in italics)
"invalid",
"keyword.operator",
"constant.numeric.css",
"keyword.other.unit.px.css",
"constant.numeric.decimal.js",
"constant.numeric.json",
"entity.name.type.class.js"
],
"settings": {
"fontStyle": "",
},
}
]
},
"files.exclude": {
"dist/": true,
"doc/": true,
"node_modules/": true,
"package/": true,
},
"alignment": {
"operatorPadding": "right",
"indentBase": "firstline",
"surroundSpace": {
"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.
"assignment": [1, 1], // The same as above.
"arrow": [1, 1], // The same as above.
"comment": 2, // Special how much space to add between the trailing comment and the code.
// If this value is negative, it means don't align the trailing comment.
}
},
"editor.formatOnSave": false,
"python.pythonPath": "python3",
"eslint.validate": [
"javascript",
"typescript",
],
"cSpell.words": [
"Gitter",
"Wechaty",
"appid",
"botie",
"huan",
"ioscat",
"ipad",
"lcov",
"lijiarui",
"padchat",
"padplus",
"padpro",
"pagepath",
"portfinder",
"qrcode",
"removee",
"ruirui",
"thumbnailurl",
"wechat",
"weixin",
"wxwork",
"zbeekman",
"zixia"
],
"typescript.format.semicolons": "remove",
}
+2
View File
@@ -0,0 +1,2 @@
Huan <zixia@zixia.net> https://github.com/huan
Rui <ruiruibupt@gmail.com> https://github.com/lijiarui
+1191
View File
File diff suppressed because it is too large Load Diff
+91
View File
@@ -0,0 +1,91 @@
# Guidelines for Contributing
Thank you for your time on Wechaty.
## How to [contribute](https://github.com/wechaty/wechaty/blob/master/CONTRIBUTING.md)
* Write a blog about Wechaty
* Create a tutorial for Wechaty
* Help expand the [wiki](https://github.com/wechaty/wechaty/wiki)
* Answer questions on the [issue](https://github.com/wechaty/wechaty/issues)
* Review [pull requests](https://github.com/wechaty/wechaty/pulls)
* Start [hacking on Wechaty](https://github.com/wechaty/wechaty/blob/master/CONTRIBUTING.md)
### Why contribute
As an open source product, Chatie thrives from contributions of community members. Whatever your skill set is, there is a lot you can do to help us make Chatie better!
So start forking!
### Not sure where to start?
It's a myth that writing code is the only way to contribute to open source. Chatie community is open to new ideas and there are so many different ways to make valuable contributions. We have some ideas of how you can get started!
#### 1. Contribute Code
- Check out GitHub issues with the tags `good first issue`, `pull request welcome` or `help wanted`
- Write code examples for documentation
- Report a bug and work on resolving it
- Collaborate with others on building new features
**Tips:**
If you want to add new features or change the API, please submit an issue first to make sure no one else is already working on the same thing and discuss the implementation and API details with maintainers and users by creating an issue. When everything is settled down, you can submit a pull request.
Make sure to add tests for your features and bugfixes and update the documentation (see below) before submitting your code!
#### 2. Contribute Support
- Fix typos in documentation
- Translate documentation to your local language
- Write tutorials and blog posts, see more: https://github.com/chatie/blog
- Answer questions on the Wechaty Developers Home or Github issues
- Organize Chatie meetups or user groups in your area
Contact rui@chatie.io to learn more
## Link to Wechaty
Contribute by marketing: Add **Powered by Wechaty** Badge to your Project Homepage:
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-green.svg)](https://github.com/wechaty/wechaty)
### Markdown
```markdown
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-green.svg)](https://github.com/wechaty/wechaty)
```
### Html
```html
<a href="https://github.com/wechaty/wechaty" target="_blank">
<img src="https://img.shields.io/badge/Powered%20By-Wechaty-green.svg" alt="Powered by Wechaty" border="0">
</a>
```
## Bug Report
Contribute by feedback, make code robust.
## Feature Request
Contribute by make product suggestion.
## Pull Request
Please:
1. submit issue to describe the problem to get start
1. then send pull request as you need(with unit test & pass linting)
```bash
$ npm run lint
$ npm run test
```
1. and do not forget to reference the issue
I built wechaty with pleasure because it can help others. help from you for wechaty will be very appreciated by the community.
Cheers!
Huan
+28
View File
@@ -0,0 +1,28 @@
# DEVELOPMENT
Tips:
- [Mashup Font of FiraCode and Script12](https://github.com/kencrocken/FiraCodeiScript)
- [Monospaced font with programming ligatures](https://github.com/tonsky/FiraCode)
- [Combined programming mono font with italics and font ligatures made from Fira Code and Pacifico for Italics](https://github.com/kosimst/Firicico)
## FiraCode
`Font Book` -> New Collection -> Downloaded -> Save Fira Code TTFs in it.
`.vscode/settings.json`
```json
, "editor.fontFamily": "'Fira Code iScript', 'Fira Code', 'Courier New', Consolas, monospace"
, "editor.fontLigatures": true
```
## Generate Changelog
```shell
npm run changelog
```
1. `403 - You have triggered an abuse detection mechanism. Please wait a few minutes before you try again. // See: https://developer.github.com/v3/#abuse-rate-limits'`
See: <https://github.com/github-changelog-generator/github-changelog-generator/issues/722#issuecomment-526980247>
+104
View File
@@ -0,0 +1,104 @@
FROM debian:bullseye
LABEL maintainer="Huan LI (李卓桓) <zixia@zixia.net>"
ENV DEBIAN_FRONTEND noninteractive
ENV WECHATY_DOCKER 1
ENV LC_ALL C.UTF-8
ENV NODE_ENV $NODE_ENV
ENV NPM_CONFIG_LOGLEVEL warn
# Instal the 'apt-utils' package to solve the error 'debconf: delaying package configuration, since apt-utils is not installed'
# https://peteris.rocks/blog/quiet-and-unattended-installation-with-apt-get/
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
apt-utils \
autoconf \
automake \
bash \
build-essential \
ca-certificates \
chromium \
coreutils \
curl \
ffmpeg \
figlet \
git \
gnupg2 \
jq \
libgconf-2-4 \
libtool \
libxtst6 \
moreutils \
python-dev \
shellcheck \
sudo \
tzdata \
vim \
wget \
&& apt-get purge --auto-remove \
&& rm -rf /tmp/* /var/lib/apt/lists/*
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - \
&& apt-get update && apt-get install -y --no-install-recommends nodejs \
&& apt-get purge --auto-remove \
&& rm -rf /tmp/* /var/lib/apt/lists/*
WORKDIR /wechaty
COPY package.json .
RUN npm install \
&& rm -fr /tmp/* ~/.npm
COPY . .
RUN ./scripts/generate-package-json.sh && rm -f src/package-json.spec.ts
RUN npm test \
&& npm run dist \
&& npm link
# Pre-install all puppets.
# Must be placed after `npm link`, or it will be all deleted by `npm link`
RUN npm run puppet-install \
&& sudo rm -fr /tmp/* ~/.npm
#
# Enable ES Modules support #2232
# See: https://github.com/wechaty/wechaty/issues/2232
#
RUN echo '{"type": "module"}' > /package.json
# Loading from node_modules Folders: https://nodejs.org/api/modules.html
# If it is not found there, then it moves to the parent directory, and so on, until the root of the file system is reached.
RUN mkdir /node_modules \
&& ln -sfv /usr/lib/node_modules/* /node_modules/ \
&& ln -sfv /wechaty/node_modules/* /node_modules/ \
&& /wechaty/bin/clean-json.js /wechaty/tsconfig.json \
| jq 'del(."ts-node")' > /tsconfig.json \
&& echo 'Linked Wechaty & tsconfig.json to Global'
WORKDIR /bot
ENTRYPOINT [ "/wechaty/bin/entrypoint.sh" ]
CMD [ "" ]
#
# https://docs.docker.com/docker-cloud/builds/advanced/
# http://label-schema.org/rc1/
#
LABEL \
org.label-schema.license="Apache-2.0" \
org.label-schema.build-date="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
org.label-schema.version="$DOCKER_TAG" \
org.label-schema.schema-version="$(wechaty-version)" \
org.label-schema.name="Wechaty" \
org.label-schema.description="Wechat for Bot" \
org.label-schema.usage="https://github.com/wechaty/wechaty/wiki/Docker" \
org.label-schema.url="https://www.chatie.io" \
org.label-schema.vendor="Chatie" \
org.label-schema.vcs-ref="$SOURCE_COMMIT" \
org.label-schema.vcs-url="https://github.com/wechaty/wechaty" \
org.label-schema.docker.cmd="docker run -ti --rm wechaty/wechaty <code.js>" \
org.label-schema.docker.cmd.test="docker run -ti --rm wechaty/wechaty test" \
org.label-schema.docker.cmd.help="docker run -ti --rm wechaty/wechaty help" \
org.label-schema.docker.params="WECHATY_TOKEN=token token from https://www.chatie.io, WECHATY_LOG=verbose Set Verbose Log, TZ='Asia/Shanghai' TimeZone"
+85
View File
@@ -0,0 +1,85 @@
#
# Wechaty Docker
# https://github.com/wechaty/wechaty
#
# FROM alpine
#
# Docker image for Alpine Linux with latest ShellCheck, a static analysis tool for shell scripts.
# https://hub.docker.com/r/nlknguyen/alpine-shellcheck/
# FROM nlknguyen/alpine-shellcheck
FROM mhart/alpine-node:7
LABEL maintainer="Huan LI <zixia@zixia.net>"
RUN apk update && apk upgrade \
&& apk add \
bash \
ca-certificates \
chromium-chromedriver \
chromium \
coreutils \
curl \
ffmpeg \
figlet \
jq \
moreutils \
ttf-freefont \
udev \
vim \
xauth \
xvfb \
&& rm -rf /tmp/* /var/cache/apk/*
RUN mkdir /wechaty
WORKDIR /wechaty
# npm `chromedriver` not support alpine linux
# https://github.com/giggio/node-chromedriver/issues/70
COPY package.json .
RUN sed -i '/chromedriver/d' package.json \
&& npm --silent --progress=false install > /dev/null \
&& rm -fr /tmp/* ~/.npm
# Loading from node_modules Folders: https://nodejs.org/api/modules.html
# If it is not found there, then it moves to the parent directory, and so on, until the root of the file system is reached.
COPY . .
RUN sed -i '/chromedriver/d' package.json \
&& npm run dist \
&& npm --silent --progress=false link \
\
&& mkdir /bot \
\
&& ( mkdir /node_modules && cd /node_modules \
&& ln -s /wechaty . \
&& ln -s /wechaty/node_modules/* . \
) \
&& ln -s /wechaty/tsconfig.json / \
&& echo "export * from 'wechaty'" > /index.ts \
\
&& echo 'Linked wechaty to global'
VOLUME [ "/bot" ]
ENTRYPOINT [ "/wechaty/bin/entrypoint.sh" ]
CMD [ "" ]
#
# https://docs.docker.com/docker-cloud/builds/advanced/
# http://label-schema.org/rc1/
#
LABEL org.label-schema.license="ISC" \
org.label-schema.build-date="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
org.label-schema.version="$DOCKER_TAG" \
org.label-schema.schema-version="$(wechaty-version)" \
org.label-schema.name="Wechaty" \
org.label-schema.description="Wechat for Bot" \
org.label-schema.usage="https://github.com/wechaty/wechaty/wiki/Docker" \
org.label-schema.url="https://www.chatie.io" \
org.label-schema.vendor="AKA Mobi" \
org.label-schema.vcs-ref="$SOURCE_COMMIT" \
org.label-schema.vcs-url="https://github.com/wechaty/wechaty" \
org.label-schema.docker.cmd="docker run -ti --rm wechaty/wechaty <code.js>" \
org.label-schema.docker.cmd.test="docker run -ti --rm wechaty/wechaty test" \
org.label-schema.docker.cmd.help="docker run -ti --rm wechaty/wechaty help" \
org.label-schema.docker.params="WECHATY_TOKEN=token token from https://www.chatie.io"
#RUN npm test
+15
View File
@@ -0,0 +1,15 @@
FROM wechaty/wechaty:next
ONBUILD ARG NODE_ENV
ONBUILD ENV NODE_ENV $NODE_ENV
ONBUILD WORKDIR /bot
ONBUILD COPY package.json .
ONBUILD RUN jq 'del(.dependencies.wechaty)' package.json | sponge package.json \
&& npm install \
&& sudo rm -fr /tmp/* ~/.npm
ONBUILD COPY . .
ONBUILD RUN npm run build --if-present
CMD [ "npm", "start" ]
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Huan LI (李卓桓) <https://github.com/huan> and
Wechaty Contributors <https://github.com/wechaty>.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+8
View File
@@ -0,0 +1,8 @@
Wechaty is a Conversational SDK for Chatbot Makers.
Copyright 2016-now Huan (李卓桓) and Wechaty Community Contributors.
This product includes software developed at
The Wechaty Organization (https://github.com/wechaty).
This software contains code derived from the Stackoverflow,
including various modifications by GitHub.
+569
View File
@@ -0,0 +1,569 @@
# Wechaty [![Join Wechaty Discord Developer Community](https://img.shields.io/discord/916984413944967180?logo=discord&style=flat)](https://discord.gg/7q8NBZbQzt) [![NPM Version](https://img.shields.io/npm/v/wechaty?color=brightgreen)](https://www.npmjs.com/package/wechaty) [![NPM](https://github.com/wechaty/wechaty/workflows/NPM/badge.svg)](https://github.com/wechaty/wechaty/actions?query=workflow%3ANPM) [![Docker](https://github.com/wechaty/wechaty/workflows/Docker/badge.svg)](https://github.com/wechaty/wechaty/actions?query=workflow%3ADocker)
[![Wechaty](https://wechaty.js.org/img/wechaty-logo.svg)](https://wechaty.js.org)
[Ship.Fail](https://ship.fail) [PreAngel](https://PreAngel.AI)
[![Downloads](https://img.shields.io/npm/dm/wechaty.svg?style=flat-square)](https://www.npmjs.com/package/wechaty)
[![GitHub stars](https://img.shields.io/github/stars/wechaty/wechaty.svg?label=github%20stars)](https://github.com/wechaty/wechaty)
[![Docker Pulls](https://img.shields.io/docker/pulls/wechaty/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/wechaty/wechaty/)
[![ES Modules](https://img.shields.io/badge/ES-Modules-orange)](https://github.com/Chatie/tsconfig/issues/16)
[![TypeScript](https://img.shields.io/badge/--3178C6?logo=typescript&logoColor=ffffff)](https://wechaty.js.org/docs/polyglot/typescript/)
[![JavaScript](https://img.shields.io/badge/--F7DF1E?logo=javascript&logoColor=000)](https://wechaty.js.org/docs/polyglot/typescript/)
[![Python](https://img.shields.io/badge/--3670A0?logo=python&logoColor=ffdd54)](https://wechaty.js.org/docs/polyglot/python/)
[![Go](https://img.shields.io/badge/--00ADD8?logo=go&logoColor=white)](https://wechaty.js.org/docs/polyglot/go/)
[![Java](https://img.shields.io/badge/--red?logo=java&logoColor=white)](https://wechaty.js.org/docs/polyglot/java/)
[![.NET](https://img.shields.io/badge/--5C2D91?logo=dotnet&logoColor=white)](https://wechaty.js.org/docs/polyglot/dotnet/)
[![PHP](https://img.shields.io/badge/--777BB4?logo=php&logoColor=white)](https://wechaty.js.org/docs/polyglot/php/)
[![Rust](https://img.shields.io/badge/--000000?logo=rust&logoColor=white)](https://wechaty.js.org/docs/polyglot/rust/)
[![Scala](https://img.shields.io/badge/--DC322F?logo=scala&logoColor=white)](https://wechaty.js.org/docs/polyglot/scala/)
[![WeChat](https://img.shields.io/badge/--07C160?logo=wechat&logoColor=white)](https://wechaty.js.org/docs/puppet-providers/wechat)
[![Whatsapp](https://img.shields.io/badge/--25D366?logo=whatsapp&logoColor=white)](https://wechaty.js.org/docs/puppet-providers/whatsapp)
<!--
[![TikTok](https://img.shields.io/badge/--000000?logo=tiktok&logoColor=white)](https://wechaty.js.org/docs/puppet-providers/tiktok)
[![Line](https://img.shields.io/badge/--00C300?logo=line&logoColor=white)](https://wechaty.js.org/docs/puppet-providers/line)
[![Telegram](https://img.shields.io/badge/--2CA5E0?logo=telegram&logoColor=white)](https://wechaty.js.org/docs/puppet-providers/telegram)
[![Gitter](https://badges.gitter.im/wechaty/wechaty.svg)](https://gitter.im/wechaty/wechaty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Gitter room](https://badgen.net/gitter/members/wechaty/wechaty)](https://gitter.im/wechaty/wechaty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Telegram Wechaty Channel](https://img.shields.io/badge/chat-on%20telegram-blue)](https://t.me/wechaty)
-->
## Elevator Pitch
Wechaty is a conversational RPA SDK that simplifies the process of building chatbots. It's like a Swiss Army knife for chatbot development, providing a universal interface to various messaging platforms such as WhatsApp, WeChat, and more. With Wechaty, you write your bot code once, and it runs on any of the supported platforms. This means you can focus on creating engaging conversational experiences without worrying about the underlying complexities of each platform's API. It's open-source, easy to use, and backed by a vibrant community that's ready to help you bring your chatbot ideas to life.
## Connecting Chatbots
Wechaty is an RPA (Robotic Process Automation) SDK for Chatbot Makers which can help you create a bot in 6 lines of [JavaScript](https://GitHub.com/Wechaty/wechaty), [Python](https://GitHub.com/Wechaty/python-wechaty/), [Go](https://GitHub.com/Wechaty/go-wechaty/), and [Java](https://GitHub.com/Wechaty/java-wechaty/), with cross-platform support including [Linux, Windows, MacOS](https://github.com/wechaty/wechaty/actions?query=workflow%3ANPM), and [Docker](https://github.com/wechaty/wechaty/actions?query=workflow%3ADocker).
:spider_web: <https://wechaty.js.org>
:octocat: <https://github.com/Wechaty/wechaty>
:beetle: <https://github.com/Wechaty/wechaty/issues>
:book: <https://github.com/Wechaty/wechaty-getting-started>
:whale: <https://hub.docker.com/r/wechaty/wechaty>
## Breaking News
- [重磅:绕过登录限制,wechaty免费版web协议重放荣光, @gengchen528, Apr 13, 2021](https://wechaty.js.org/2021/04/13/wechaty-uos-web/)
## Voice of Developers
> "Wechaty is a great solution; I believe there would be much more users who recognize it." [link](https://github.com/Wechaty/wechaty/pull/310#issuecomment-285574472)
> &mdash; <cite>@Gcaufy, Tencent Engineer, Author of [WePY](https://github.com/Tencent/wepy)</cite>
>
> "太好用,好用的想哭"
> &mdash; <cite>@xinbenlv, Google Engineer, Founder of HaoShiYou.org</cite>
>
> ”好用到哭“——你们对得起这个评价! [link](https://github.com/bigbrother666sh/shezhangbujianle#%E8%87%B4%E6%95%AC)
> &mdash; <cite>[@bigbrother666sh](https://github.com/bigbrother666sh), creator of《社长不见了》剧本杀 NPC DM</cite>
>
> "最好的微信开发库" [link](http://weibo.com/3296245513/Ec4iNp9Ld?type=comment)
> &mdash; <cite>@Jarvis, Baidu Engineer</cite>
>
> "Wechaty让运营人员更多的时间思考如何进行活动策划、留存用户,商业变现" [link](http://mp.weixin.qq.com/s/dWHAj8XtiKG-1fIS5Og79g)
> &mdash; <cite>@lijiarui, Founder & CEO of Juzi.BOT.</cite>
>
> "If you know js ... try Wechaty. It's easy to use."
> &mdash; <cite>@Urinx Uri Lee, Author of [WeixinBot(Python)](https://github.com/Urinx/WeixinBot)</cite>
>
> "Wechaty is a good project; I hope it can continue! Therefore, I became a contributor in open collective."
> &mdash; <cite>[@Simple](https://github.com/mrwhh)</cite>
See more at [Wiki:Voice Of Developer](https://github.com/Wechaty/wechaty/wiki/Voice%20Of%20Developer)
### Join Us on Discord
[![Join Wechaty Discord Developer Community](https://img.shields.io/discord/916984413944967180?logo=discord&style=flat)](https://discord.gg/7q8NBZbQzt)
[![Wechaty Discord](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscord.com%2Fapi%2Finvites%2F7q8NBZbQzt%3Fwith_counts%3Dtrue&query=%24.approximate_member_count&logo=discord&logoColor=white&label=Users%20Total&color=green&style=flat)](https://discord.gg/7q8NBZbQzt)
Wechaty is used in many ChatBot projects by thousands of developers. To talk with other developers, scan the QR Code below and join our **Wechaty Developer Community**.
![Wechaty Discord Community QR Code](https://wechaty.js.org/img/discord-qrcode.svg)
Scan now because other Wechaty developers want to talk with you, too!
### Resource
Wechaty has already held lots of talks and got a lot of blogs in the past years; here are all of the wechaty resources:
- :video_camera: [Youtube Playlist: Watch all of the talk videos related with Wechaty](https://www.youtube.com/playlist?list=PL8hd9KDTdarDXf_Rxtr8meKhxtgcXMInh)
- :page_with_curl: [Full Docs](https://wechaty.js.org/)
- :bulb: [Blog: See how developers use wechaty to build fantastic projects!](https://wechaty.github.io/)
- :beginner: [Wechaty-Getting-Started:Wechaty Starter Project Template that Works Out-of-the-Box](https://github.com/wechaty/wechaty-getting-started)
- :tada: [Wechaty Contributor List: Thanks for their contribution!](https://github.com/wechaty/wechaty/wiki/Contributors)
- :gift: [Juzibot Support Wechaty: Know everything about Wechaty](https://github.com/juzibot/Welcome/wiki/Everything-about-Wechaty)
## :rocket: The World's Shortest ChatBot Code: 6 lines of JavaScript
```javascript
import { WechatyBuilder } from 'wechaty'
const wechaty = WechatyBuilder.build() // get a Wechaty instance
wechaty
.on('scan', (qrcode, status) => console.log(`Scan QR Code to login: ${status}\nhttps://wechaty.js.org/qrcode/${encodeURIComponent(qrcode)}`))
.on('login', user => console.log(`User ${user} logged in`))
.on('message', message => console.log(`Message: ${message}`))
wechaty.start()
```
> **Notice: Wechaty requires Node.js version >= 16**
This bot can log all messages to the console after login by the scan.
You can find Wechaty's Official Examples at [examples/ding-dong-bot.ts](examples/ding-dong-bot.ts) and more from our [Example Directory](https://github.com/Wechaty/wechaty-getting-started/blob/main/examples/).
## :checkered_flag: Requirements
1. Node.js version 16+
1. NPM version 7+
1. TypeScript version 4.4+
## Getting Started
[![node](https://img.shields.io/node/v/wechaty.svg?maxAge=604800)](https://nodejs.org/)
- Wechaty Starter Repository - <https://github.com/wechaty/wechaty-getting-started>
We have a Wechaty starter repository for beginners with the simplest setting. It will be **work** out-of-the-box after you `clone` & `npm install` & `npm start`.
If you are new to Wechaty and want to try it the first time, we'd like to strong recommend you starting from this repository, and using it as your starter template for your project.
Otherwise, please saved the above _The World's Shortest ChatBot Code: 6 lines of JavaScript_ example to a file named `bot.js` before you can use either NPM or Docker to run it.
### 1. Npm
[![NPM Version](https://img.shields.io/npm/v/wechaty?color=brightgreen&label=wechaty%40latest)](https://www.npmjs.com/package/wechaty)
[![npm (tag)](https://img.shields.io/npm/v/wechaty/next?color=yellow&label=wechaty%40next)](https://www.npmjs.com/package/wechaty?activeTab=versions)
[![Downloads](https://img.shields.io/npm/dm/wechaty.svg?style=flat-square)](https://www.npmjs.com/package/wechaty)
[![install size](https://packagephobia.now.sh/badge?p=wechaty)](https://packagephobia.now.sh/result?p=wechaty)
```shell
npm init
npm install wechaty
# create your first bot.js file, you can copy/paste from the above "The World's Shortest ChatBot Code: 6 lines of JavaScript"
# then:
node bot.js
```
### 2. Docker
[![Docker Pulls](https://img.shields.io/docker/pulls/wechaty/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/wechaty/wechaty/)
[![Docker Layers](https://images.microbadger.com/badges/image/wechaty/wechaty.svg)](https://microbadger.com/#/images/wechaty/wechaty)
- Wechaty Starter Repository for Docker - <https://github.com/wechaty/docker-wechaty-getting-started>
> Wechaty Docker supports both JavaScript and TypeScript. To use TypeScript just write in TypeScript and save with extension name `.ts`, no need to compile because we use `ts-node` to run it.
2.1. Run JavaScript
```shell
# for JavaScript
docker run -ti --rm --volume="$(pwd)":/bot wechaty/wechaty bot.js
```
2.2. Run TypeScript
```shell
# for TypeScript
docker run -ti --rm --volume="$(pwd)":/bot wechaty/wechaty bot.ts
```
> Learn more about Wechaty Docker at [Wiki:Docker](https://github.com/Wechaty/wechaty/wiki/Docker).
### 3. Switch Protocol(Puppet)
Wechaty is very powerful that it can run over different protocols. You can specify the protocol by set the environment variable `WECHATY_PUPPET` to different puppet provider.
If you cannot use Web protocol, you can apply other protocols following the instruction here: <https://github.com/wechaty/wechaty/wiki/Support-Developers> We provide free token to support developers build a valuable WeChat chatbot.
Currently we support the following [puppet providers](https://wechaty.js.org/docs/puppet-services/) :
| Protocol | Puppet Provider | Environment Variable |
| --- | --- | --- |
| Web | [PuppetPuppeteer](https://github.com/wechaty/wechaty-puppet-puppeteer) | `export WECHATY_PUPPET=wechaty-puppet-puppeteer` |
| Windows | [PuppetWorkPro](https://github.com/RPAChat/workpro-getting-started) | `export WECHATY_PUPPET=wechaty-puppet-service` |
| Mock | [PuppetMock](https://github.com/wechaty/wechaty-puppet-mock) | `export WECHATY_PUPPET=wechaty-puppet-mock` |
| Web | [PuppetWechat4u](https://github.com/wechaty/wechaty-puppet-wechat4u) | `export WECHATY_PUPPET=wechaty-puppet-wechat4u` |
| iPad | [PuppetRock](https://github.com/wechaty/puppet-service-providers) | `export WECHATY_PUPPET=wechaty-puppet-service` |
| iPad | [PuppetPadLocal](https://github.com/wechaty/puppet-service-providers) | `export WECHATY_PUPPET=wechaty-puppet-service` |
| Windows | [PuppetDonut](https://github.com/wechaty/puppet-service-providers) | `export WECHATY_PUPPET=wechaty-puppet-service` |
| iPad | ~~PuppetPadpro~~ **DEPRECATED** | `export WECHATY_PUPPET=wechaty-puppet-padpro` |
| iPad | ~~PuppetPadchat~~ **DEPRECATED** | `export WECHATY_PUPPET=wechaty-puppet-padchat` |
| iPad | ~~PuppetPadplus~~ **DEPRECATED** | `export WECHATY_PUPPET=wechaty-puppet-padplus` |
| Mac | ~~PuppetMacpro~~ **DEPRECATED** | `export WECHATY_PUPPET=wechaty-puppet-macpro` |
| Windows | ~~PuppetWxwork~~ **DEPRECATED** | `export WECHATY_PUPPET=wechaty-puppet-service` |
Learn more about Wechaty Puppet from the Puppet Wiki:
1. Puppet Directory: <https://github.com/Wechaty/wechaty-puppet/wiki/Directory>
1. Puppet Compatibility: <https://github.com/Wechaty/wechaty-puppet/wiki/Compatibility>
## :guitar: API
Read the Full Documentation at [Wechaty Official API Reference](https://wechaty.github.io/wechaty/)
### 1 Class `Wechaty`
Main bot class.
A `Bot` is a Wechaty instance that control a specific [wechaty-puppet](https://wechaty.js.org/docs/specifications/puppet/).
- `new Wechaty(options?: WechatyOptions)`
1. `options.name?: string` the name of this bot(optional)
2. `options.puppet?: string` select which puppet provider we want to use. must be one of the:
1. [wechaty-puppet-wechat](https://github.com/Wechaty/wechaty-puppet-wechat) - Angular Hook for Web Wechat
2. [wechaty-puppet-wechat4u](https://github.com/Wechaty/wechaty-puppet-wechat4u) - HTTP API for Web Wechat
3. [wechaty-puppet-padpro](https://github.com/botorange/wechaty-puppet-padpro) - iPad App Protocol
4. [wechaty-puppet-ioscat](https://github.com/linyimin-bupt/wechaty-puppet-ioscat) - iPhone App Hook
5. [wechaty-puppet-mock](https://github.com/Wechaty/wechaty-puppet-mock) - Mock for Testing
3. `options.puppetOptions?: PuppetOptions` options for the puppet provider.
| Wechaty | API | Description |
| :--- | :--- | :--- |
| event | [`login`](https://wechaty.github.io/wechaty/#Wechaty+on) | emit after bot login full successful |
| event | [`logout`](https://wechaty.github.io/wechaty/#Wechaty+on) | emit after the bot log out |
| event | [`friendship`](https://wechaty.github.io/wechaty/#Wechaty+on) | emit when someone sends bot a friend request|
| event | [`message`](https://wechaty.github.io/wechaty/#Wechaty+on) | emit when there's a new message |
| event | [`room-join`](https://wechaty.github.io/wechaty/#Wechaty+on) | emit when anyone join any room |
| event | [`room-topic`](https://wechaty.github.io/wechaty/#Wechaty+on) | emit when someone change room topic |
| event | [`room-leave`](https://wechaty.github.io/wechaty/#Wechaty+on) | emit when anyone leave the room |
| event | [`room-invite`](https://wechaty.github.io/wechaty/#Wechaty+on) | emit when there is a room invitation |
| event | [`scan`](https://wechaty.github.io/wechaty/#Wechaty+on) | emit when the bot needs to show you a QR Code for scanning |
| method | [`start(): Promise<void>`](https://wechaty.github.io/wechaty/#Wechaty+start) | start the bot |
| method | [`stop(): Promise<void>`](https://wechaty.github.io/wechaty/#Wechaty+stop) | stop the bot |
| method | [`logonoff(): boolean`](https://wechaty.github.io/wechaty/#Wechaty+logonoff) | bot login status |
| method | [`logout(): Promise<void>`](https://wechaty.github.io/wechaty/#Wechaty+logout) | logout the bot |
| method | [`currentUser(): ContactSelf`](https://wechaty.github.io/wechaty/#Wechaty+currentUser) | get the login-ed bot contact |
| method | [`say(text: string): Promise<void>`](https://wechaty.github.io/wechaty/#Wechaty+say) | let bot say `text` to itself |
### 2 Class `Contact`
All wechat contacts(friends/non-friends) will be encapsulated as a Contact.
| Contact | API | Description |
| :--- | :--- | :--- |
| static | [`find(query: string): Promise<null \| Contact>`](https://wechaty.github.io/wechaty/#Contact.find) | find contact by name or alias, if the result more than one, return the first one. |
| static | [`findAll(query: string): Promise<Contact[]>`](https://wechaty.github.io/wechaty/#Contact.findAll) | find contact by `name` or `alias` |
| static | [`load(query: string): Contact`](https://wechaty.github.io/wechaty/#Contact.load) | get contact by id |
| property | `id: readonly string` | get contact id |
| method | [`sync(): Promise<void>`](https://wechaty.github.io/wechaty/#Contact+sync) | force reload data for contact , sync data from lowlevel API again|
| method | [`say(text: string): Promise<void \| Message>`](https://wechaty.github.io/wechaty/#Contact+say) | send text, Contact, or file to contact, return the message which the bot sent (only `puppet-padplus` supported). |
| method | [`self(): boolean`](https://wechaty.github.io/wechaty/#Contact+self) | check if contact is self |
| method | [`name(): string`](https://wechaty.github.io/wechaty/#Contact+name) | get the name from a contact |
| method | [`alias(): Promise<string>`](https://wechaty.github.io/wechaty/#Contact+alias) | get the alias for a contact |
| method | [`alias(newAlias: string): Promise<void>`](https://wechaty.github.io/wechaty/#Contact+alias) | set or delete the alias for a contact |
| method | [`friend(): boolean`](https://wechaty.github.io/wechaty/#Contact+friend) | check if contact is friend |
| method | [`type(): ContactType`](https://wechaty.github.io/wechaty/#Contact+type) | return the type of the Contact |
| method | [`province(): string`](https://wechaty.github.io/wechaty/#Contact+province) | get the region 'province' from a contact |
| method | [`city(): string`](https://wechaty.github.io/wechaty/#Contact+city) | get the region 'city' from a contact |
| method | [`avatar(): Promise<FileBox>`](https://wechaty.github.io/wechaty/#Contact+avatar) | get avatar picture file stream |
| method | [`gender(): ContactGender`](https://wechaty.github.io/wechaty/#Contact+gender) | get gender from a contact |
#### 2.1 Class `ContactSelf`
Class `ContactSelf` is extended from `Contact`.
| ContactSelf | API | Description |
| :--- | :--- | :--- |
| method | [`avatar(file: FileBox): Promise<void>`](https://wechaty.github.io/wechaty/#ContactSelf+avatar) | set avatar for bot |
| method | [`qrcode(): Promise<string>`](https://wechaty.github.io/wechaty/#ContactSelf+qrcode) | get qrcode for bot |
| method | [`signature(text: string): Promise<void>`](https://wechaty.github.io/wechaty/#ContactSelf+signature) | set signature for bot |
#### 2.2 Class `Friendship`
Send, receive friend request, and friend confirmation events.
| Friendship | API | Description |
| :--- | :--- | :--- |
| static | [`add(contact: Contact, hello?: string): Promise<void>`](https://wechaty.github.io/wechaty/#Friendship.add) | send a friend invitation to contact |
| method | [`accept(): Promise<void>`](https://wechaty.github.io/wechaty/#Friendship+accept) | accept Friend Request |
| method | [`hello(): string`](https://wechaty.github.io/wechaty/#Friendship+hello) | get the hello string from a friendship invitation |
| method | [`contact(): Contact`](https://wechaty.github.io/wechaty/#Friendship+contact) | get the contact from friendship |
| method | [`type(): FriendshipType`](https://wechaty.github.io/wechaty/#Friendship+type) | return the Friendship Type(unknown, confirm, receive, verify) |
### 3 Class `Message`
All wechat messages will be encapsulated as a Message.
| Message | API | Description |
| :--- | :--- | :--- |
| static | [`find(query: string): Promise<null \| Message>`](https://wechaty.github.io/wechaty/#Message.find) | find message in cache and return the first one |
| static | [`findAll(query: string): Promise<Message[]>`](https://wechaty.github.io/wechaty/#Message.findAll) | find messages in cache, return a message list |
| method | [`from(): Contact`](https://wechaty.github.io/wechaty/#Message+from) | get the sender from a message |
| method | [`to(): Contact`](https://wechaty.github.io/wechaty/#Message+to) | get the destination of the message |
| method | [`room(): null \| Room`](https://wechaty.github.io/wechaty/#Message+room) | get the room from the message.(If the message is not in a room, then will return `null`) |
| method | [`text(): string`](https://wechaty.github.io/wechaty/#Message+text) | get the text content of the message |
| method | [`say(text: string): Promise<void \| Message>`](https://wechaty.github.io/wechaty/#Message+say) | reply a Text, Media File , or contact message to the sender, return the message which the bot sent (only `puppet-padplus` supported). |
| method | [`type(): MessageType`](https://wechaty.github.io/wechaty/#Message+type) | get the type from the message |
| method | [`self(): boolean`](https://wechaty.github.io/wechaty/#Message+self) | check if a message is sent by self |
| method | [`mention(): Contact[]`](https://wechaty.github.io/wechaty/#Message+mention) | get message mentioned contactList. |
| method | [`mentionSelf(): boolean`](https://wechaty.github.io/wechaty/#Message+mentionSelf) | check if a message is mention self |
| method | [`forward(to: Contact): Promise<void>`](https://wechaty.github.io/wechaty/#Message+forward) | Forward the received message |
| method | [`age(): number`](https://wechaty.github.io/wechaty/#Message+age) | the number of seconds since it has been created |
| method | [`date(): Date`](https://wechaty.github.io/wechaty/#Message+date) | the time it was created |
| method | [`toFileBox(): Promise<FileBox>`](https://wechaty.github.io/wechaty/#Message+toFileBox) | extract the Media File from the Message, and put it into the FileBox. |
| method | [`toContact(): Promise<Contact>`](https://wechaty.github.io/wechaty/#Message+toContact) | get Share Card of the Message |
### 4 Class `Room`
All wechat rooms(groups) will be encapsulated as a Room.
| Room | API | Description |
| :--- | :--- | :--- |
| static | [`create(contactList: Contact[], topic?: string): Promise<Room>`](https://wechaty.github.io/wechaty/#Room.create) | create a new room |
| static | [`find(query: string): Promise<null \| Room>`](https://wechaty.github.io/wechaty/#Room.find) | Try to find a room by filter. If get many, return the first one. |
| static | [`findAll(query: string): Promise<Room[]>`](https://wechaty.github.io/wechaty/#Room.findAll) | Find all contacts in a room |
| static | [`load(query: string): Room`](https://wechaty.github.io/wechaty/#Room.load) | load room by room id |
| property | `id: readonly string` | |
| event | [`join`](https://wechaty.github.io/wechaty/#Room+on) | emit when anyone join any room |
| event | [`topic`](https://wechaty.github.io/wechaty/#Room+on) | emit when someone change room topic |
| event | [`leave`](https://wechaty.github.io/wechaty/#Room+on) | emit when anyone leave the room |
| event | [`invite`](https://wechaty.github.io/wechaty/#Room+on) | emit when receive a room invitation |
| method | [`sync(): <Promise<void>`](https://wechaty.github.io/wechaty/#Room+sync) | force reload data for room, sync data from lowlevel API again.
| method | [`say(text: string): Promise<void \| Message>`](https://wechaty.github.io/wechaty/#Room+say) | Send text,media file, contact card, or text with mention @mention contact inside Room, return the message which the bot sent (only `puppet-padplus` supported). |
| method | [`add(contact: Contact): Promise<void>`](https://wechaty.github.io/wechaty/#Room+add) | Add contact in a room |
| method | [`del(contact: Contact): Promise<void>`](https://wechaty.github.io/wechaty/#Room+del) | Delete a contact from the room |
| method | [`quit(): Promise<void>`](https://wechaty.github.io/wechaty/#Room+quit) | Bot quit the room itself |
| method | [`topic(): Promise<string>`](https://wechaty.github.io/wechaty/#Room+topic) | GET topic from the room |
| method | [`topic(newTopic: string): Promise<void>`](https://wechaty.github.io/wechaty/#Room+topic) | SET topic from the room |
| method | [`announce(text: string): Promise<void>`](https://wechaty.github.io/wechaty/#Room+announce) | SET/GET announce from the room |
| method | [`qrcode(): Promise<string>`](https://wechaty.github.io/wechaty/#Room+qrcode) | Get QR Code of the Room from the room, which can be used as scan and join the room. |
| method | [`alias(contact: Contact): Promise<string>`](https://wechaty.github.io/wechaty/#Room+alias) | Return contact's roomAlias in the room |
| method | [`roomAlias(contact: Contact): Promise<string \| null>`](https://wechaty.github.io/wechaty/#Room+roomAlias) | Same as function alias |
| method | [`has(contact: Contact): Promise<boolean>`](https://wechaty.github.io/wechaty/#Room+has) | Check if the room has member `contact` |
| method | [`memberAll(query?: string): Promise<Contact[]>`](https://wechaty.github.io/wechaty/#Room+memberAll) | Find all contacts or with specific name in a room |
| method | [`member(query: string): Promise<null \| Contact>`](https://wechaty.github.io/wechaty/#Room+member) | Find all contacts in a room, if get many, return the first one. |
| method | [`memberList():Promise<Contact[]>`](https://wechaty.github.io/wechaty/#Room+memberList) | get all room member from the room |
| method | [`owner(): null \| Contact`](https://wechaty.github.io/wechaty/#Room+owner) | Get room's owner from the room. |
#### 4.1 Class `RoomInvitation`
Accept room invitation
| RoomInvitation | API | Description |
| :--- | :--- | :--- |
| method | [`accept(): Promise<void>`](https://wechaty.github.io/wechaty/#RoomInvitation+accept) | accept Room Invitation |
| method | [`inviter(): Contact`](https://wechaty.github.io/wechaty/#RoomInvitation+inviter) | get the inviter from room invitation |
| method | [`roomTopic(): Promise<string>`](https://wechaty.github.io/wechaty/#RoomInvitation+inviter) | get the room topic from room invitation |
| method | [`date(): Promise<Date>`](https://wechaty.github.io/wechaty/#RoomInvitation+date) | the time it was created |
| method | [`age(): Promise<number>`](https://wechaty.github.io/wechaty/#RoomInvitation+age) | the number of seconds since it has been created |
## TEST
[![NPM](https://github.com/wechaty/wechaty/workflows/NPM/badge.svg)](https://github.com/wechaty/wechaty/actions?query=workflow%3ANPM)
[![Docker](https://github.com/wechaty/wechaty/workflows/Docker/badge.svg)](https://github.com/wechaty/wechaty/actions?query=workflow%3ADocker)
[![Coverage Status](https://coveralls.io/repos/github/wechaty/wechaty/badge.svg?branch=main)](https://coveralls.io/github/wechaty/wechaty?branch=main)
[![Known Vulnerabilities](https://snyk.io/test/github/Wechaty/wechaty/badge.svg)](https://snyk.io/test/github/Wechaty/wechaty)
Wechaty is fully automatically tested by unit and integration tests, with Continuous Integration & Continuous Delivery (CI/CD) support powered by CI like Travis, Shippable and Appveyor.
To test Wechaty, run:
```shell
npm test
```
Get to know more about the tests from [Wiki:Tests](https://github.com/Wechaty/wechaty/wiki/Tests)
## CREATING WECHATY PLUGIN
Creating and publishing a Wechaty Plugin is simple. Simply expose your module as a function that takes 1 parameter: wechaty. When your plugin is imported by Wechaty, it will pass itself in as the argument, and so you are free to add any configuration that Wechaty supports.
```ts
import { WechatyPlugin } from 'wechaty'
export default const MyPlugin: WechatyPlugin = (wechaty: Wechaty) => {
// ...
}
```
The `config` exist so the user can pass in customizations to your Plugin. In documenting your Wechaty Plugin, you would lay out your supported config for the user.
See:
1. [Wechaty Plugin Support with KickOut Example #1939](https://github.com/wechaty/wechaty/issues/1939)
1. [Wechaty Plugins Contrib](https://github.com/wechaty/wechaty-plugin-contrib)
## :pencil: RELEASE NOTES
- [Latest Release](https://github.com/Wechaty/wechaty/releases/latest)(All releases [here](https://github.com/Wechaty/wechaty/releases))
- [Changelog](https://github.com/Wechaty/wechaty/blob/main/CHANGELOG.md)
### :saxophone: Views Since Feb 15, 2019
[![HitCount](http://hits.dwyl.io/wechaty/wechaty.svg)](http://hits.dwyl.io/wechaty/wechaty)
## :sparkling_heart: POWERED BY WECHATY
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-brightgreen.svg)](https://wechaty.js.org)
### :sparkles: Wechaty Badge
```markdown
[![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-brightgreen.svg)](https://wechaty.js.org)
```
Get more embed html/markdown code from [Wiki:Badge](https://github.com/wechaty/wechaty/wiki/Badge)
### :star2: Projects Using Wechaty
1. [一个用CNN深度神经网络给图片评分的wechaty项目](https://github.com/huyingxi/wechaty_selfie)
2. [Relay between Telegram and WeChat](https://github.com/Firaenix/TeleChatRelay)
3. [A chat bot managing the HaoShiYou wechat groups run by volunteers of haoshiyou.org](https://github.com/xinbenlv/haoshiyou-bot)
4. [An interactive chat bot to manage a TODO list](https://github.com/coderbunker/candobot)
5. [Forward WeChat messages to telegram](https://github.com/luosheng/Wegram)
6. [koa与wechaty实现的微信小助手,可定时提醒与发消息设定定时任务](https://github.com/gengchen528/wechat-assistant)
7. [Wechaty Pay - 让线上没有难做的生意](https://github.com/coderwhocode/wechaty-pay)
8. [开源社的微信机器人项目](https://github.com/kaiyuanshe/wechat-robot)
Pull Request is welcome to add yours!
Learn more about Projects Using Wechaty at [Wiki:Projects Using Wechaty](https://github.com/wechaty/wechaty/wiki/Projects-Using-Wechaty)
## :innocent: Find a Good Server
The best practice for running Wechaty Docker/NPM is using a VPS(Virtual Private Server) outside of China, which can save you hours of time because `npm install` and `docker pull` will run smoothly without any problem.
The following VPS providers are used by the Wechaty team, and they worked perfectly in production. You can use the following link to get one in minutes. Also, doing this can support Wechaty because you are referred by us.
| Location | Price | Ram | Payment | Provider |
| --- | --- | --- | --- | --- |
| Singapore | $5 | 512MB | Paypal | [DigitalOcean](https://m.do.co/c/01a54778df5c) |
| Japan | $5 | 1GB | Paypal | [Linode](https://www.linode.com/?r=5fd2b713d711746bb5451111df0f2b6d863e9f63) |
| Korea | $10 | 1GB | Alipay, Paypal | [Netdedi](https://www.netdedi.com/?affid=35) |
| Singapore | $3.5 | 512MB | Alipay, Wechat | [Vultr](https://www.vultr.com/?ref=6986613) |
## :notes: See Also
- [RelatedProject](https://github.com/Wechaty/wechaty/wiki/RelatedProject)
## :poop: The Story
In 2017 ...
Huan's daily life/work depends on too much chat on wechat.
- Almost 14,000 wechat friends in May 2014, before wechat restricts a total number of friends to 5,000.
- Almost 400 wechat rooms, and most of them have more than 400 members.
Can you imagine that? He was dying...
So a tireless bot working for me 24x7 on wechat, monitoring/filtering the most important message is badly needed. For example, it highlights discussion which contains the KEYWORDS which he want to follow up(especially in a noisy room). ;-)
At last, It's built for huan's personal study purpose of Automatically Testing.
## Stargazers over time
[![Stargazers over time](https://starchart.cc/wechaty/wechaty.svg)](https://starchart.cc/wechaty/wechaty)
## :two_hearts: Contributors
[![GitHub issues](https://img.shields.io/github/issues/wechaty/wechaty.svg)](https://github.com/Wechaty/wechaty/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/wechaty/wechaty.svg)](https://github.com/Wechaty/wechaty/pulls)
[![Open Collective Backers](https://opencollective.com/wechaty/backer/badge.svg?label=open%20collective%20backers&color=blue)](https://opencollective.com/wechaty/)
[![Open Collective Sponsors](https://opencollective.com/wechaty/sponsors/badge.svg?label=open%20collective%20sponsors&color=blue)](https://opencollective.com/wechaty/)
[![contributor](https://sourcerer.io/fame/huan/wechaty/wechaty/images/0)](https://sourcerer.io/fame/huan/wechaty/wechaty/links/0)
[![contributor](https://sourcerer.io/fame/huan/wechaty/wechaty/images/1)](https://sourcerer.io/fame/huan/wechaty/wechaty/links/1)
[![contributor](https://sourcerer.io/fame/huan/wechaty/wechaty/images/2)](https://sourcerer.io/fame/huan/wechaty/wechaty/links/2)
[![contributor](https://sourcerer.io/fame/huan/wechaty/wechaty/images/3)](https://sourcerer.io/fame/huan/wechaty/wechaty/links/3)
[![contributor](https://sourcerer.io/fame/huan/wechaty/wechaty/images/4)](https://sourcerer.io/fame/huan/wechaty/wechaty/links/4)
[![contributor](https://sourcerer.io/fame/huan/wechaty/wechaty/images/5)](https://sourcerer.io/fame/huan/wechaty/wechaty/links/5)
[![contributor](https://sourcerer.io/fame/huan/wechaty/wechaty/images/6)](https://sourcerer.io/fame/huan/wechaty/wechaty/links/6)
[![contributor](https://sourcerer.io/fame/huan/wechaty/wechaty/images/7)](https://sourcerer.io/fame/huan/wechaty/wechaty/links/7)
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
-----
[![Contribute](https://opencollective.com/wechaty/contributors.svg?width=890&button=false)](https://github.com/Wechaty/wechaty/graphs/contributors)
## :sunglasses: Backers
[![Backers on Open Collective](https://opencollective.com/wechaty/backers/badge.svg)](#sunglasses-backers)
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/wechaty#backer)]
[![Open Collective Wechaty](https://opencollective.com/wechaty/backers.svg?width=890)](https://opencollective.com/wechaty#backers)
## :smirk: Sponsors
[![Sponsors on Open Collective](https://opencollective.com/wechaty/sponsors/badge.svg)](#smirk-sponsors)
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/wechaty#sponsor)]
[![Wechaty Sponsor](https://opencollective.com/wechaty/sponsor.svg?width=890)](https://opencollective.com/wechaty/#sponsor)
## Multi-language Wechaty
[![Python Wechaty](https://img.shields.io/badge/Wechaty-Python-blue)](https://github.com/wechaty/python-wechaty)
[![Go Wechaty](https://img.shields.io/badge/Wechaty-Go-7de)](https://github.com/wechaty/go-wechaty)
[![Java(Kotlin) Wechaty](https://img.shields.io/badge/Wechaty-Java-f80)](https://github.com/wechaty/java-wechaty)
[![Scala Wechaty](https://img.shields.io/badge/Wechaty-Scala-890)](https://github.com/wechaty/scala-wechaty)
[![PHP Wechaty](https://img.shields.io/badge/Wechaty-PHP-99c)](https://github.com/wechaty/php-wechaty)
[![.NET(C#) Wechatyin](https://img.shields.io/badge/Wechaty-.NET-629)](https://github.com/wechaty/dotnet-wechaty)
- [Wechaty](https://github.com/wechaty/wechaty) - RPA SDK for Chatot Makers (TypeScript)
- [Python Wechaty](https://github.com/wechaty/python-wechaty) - RPA SDK for Chatbot Makers written in Python
- [Go Wechaty](https://github.com/wechaty/go-wechaty) - RPA SDK for Chatbot Makers written in Go
- [Java Wechaty](https://github.com/wechaty/java-wechaty) - RPA SDK for Chatbot Makers written in Java(Kotlin)
- [Scala Wechaty](https://github.com/wechaty/scala-wechaty) - RPA SDK for Chatbot Makers written in Scala
- [PHP Wechaty](https://github.com/wechaty/php-wechaty) - RPA SDK for Chatbot Makers written in PHP
- [.Net(C#) Wechaty](https://github.com/wechaty/dotnet-wechaty) - RPA SDK for Chatbot Makers written in .NET(C#)
## History
### main v1.11 (Nov 22, 2021)
Working on reduxify the Wechaty ecosystem for applying the CQRS pattern.
### v1.10 (Nov 21, 2021)
Second beta release of Wechaty, with all ecosystem npm modules with version v1.10 (wechaty-puppet, wechaty-puppet-service, etc)
### v1.0 (Sep 2021)
- Release v1.0 of Wechaty is the first beta release of Wechaty.
### v0.69
1. v0.69: Supports ES Modules (with CJS dual support) ([#2232](https://github.com/wechaty/wechaty/issues/2232))
### v0.68 (Aug 27, 2021)
1. TLS support ([#2231](https://github.com/wechaty/wechaty/issues/2231))
1. The latest CommonJS version
## Creators
1. [Huan](https://github.com/huan) [(LinkedIn)](http://linkedin.com/in/huan42), 🐧 Tencent TVP of Chatbot・🤖 Chatie Architect・⭐️ GitHub Star・🚀 YC W19・🌐 Microsoft RD & AI MVP・🦾 Google ML GDE ・🤠 Serial Entrepreneur・🔥 Burner
1. [Rui (李佳芮)](https://pre-angel.com/peoples/jiarui-li/), Microsoft AI MVP & RD, Co-founder & CEO of [Juzi.BOT](https://www.juzibot.com/) (YC W19 Alumni)
[![Profile of Huan Li on StackOverflow](https://stackoverflow.com/users/flair/1123955.png)](https://stackoverflow.com/users/1123955/huan)
## Cite Wechaty
To cite this project in publications:
```bibtex
@misc{Wechaty,
author = {Huan Li, Rui Li},
title = {Wechaty: Conversational SDK for Chatbot Makers},
year = {2016},
publisher = {GitHub},
journal = {GitHub Repository},
howpublished = {\url{https://github.com/wechaty/wechaty}},
}
```
## Copyright & License
- Code & Docs © 2016-now Huan, Rui, and Wechaty Community Contributors
- Code released under the Apache-2.0 License
- Docs released under Creative Commons
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`wechaty/wechaty`
- 原始仓库:https://github.com/wechaty/wechaty
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+29
View File
@@ -0,0 +1,29 @@
{
"name": "wechaty",
"description": "Wechat for Bot. Get your Heroku cloud bot by deploy me",
"repository": "https://github.com/wechaty/wechaty",
"logo": "https://raw.githubusercontent.com/chatie/wechaty/master/image/wechaty-icon.png",
"keywords": ["wechaty", "wechat", "bot", "chatbot", "framework", "cloudbot"],
"env": {
"WECHATY_LOG": {
"description": "Don't touch this. unless you know what this mean",
"value": "info"
},
"WECHATY_PUPPET": {
"description": "Puppet Name",
"value": "default"
},
"WECHATY_TOKEN": {
"description": "Cloud access token for Wechaty.io",
"value": "wechaty"
},
},
"engines": {
"node": "10"
},
"buildpacks": [
{
"url": "https://github.com/heroku/heroku-buildpack-google-chrome"
}
]
}
+29
View File
@@ -0,0 +1,29 @@
# http://www.appveyor.com/docs/appveyor-yml
# Test against these versions of Io.js and Node.js.
environment:
matrix:
- nodejs_version: "10"
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
# chocolatey install jq - https://chocolatey.org/packages/jq
- choco install jq
# install modules
- npm install
# Post-install test scripts.
test_script:
- node --version
- npm --version
# https://bugs.chromium.org/p/chromium/issues/detail?id=158372#c6
- wmic datafile where name="C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" get Version /value
- npm run test:unit
# Don't actually build.
build: off
# Set build version format here instead of in the admin panel.
version: "{build}"
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env node
import fs from 'fs'
import JSON5 from 'json5'
const file = process.argv[2]
// console.error('Converting file: ', file)
const json = JSON5.parse(
fs.readFileSync(file, 'utf8')
)
console.info(
JSON.stringify(json, null, ' ')
)
Executable
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env node
/**
* https://github.com/wechaty/wechaty
*
* Author: Huan <zixia@zixia.net>
* License: Apache-2.0
*
* CLI Apps in TypeScript with `cmd-ts` (Part 1)
* Using `cmd-ts` to easily build a type-safe TypeScript CLI app
*
* https://gal.hagever.com/posts/type-safe-cli-apps-in-typescript-with-cmd-ts-part-1/
*/
/* eslint-disable sort-keys */
import 'dotenv/config.js'
import {
binary,
run,
subcommands,
} from 'cmd-ts'
import { VERSION } from '../src/config.js'
import * as cmds from '../src/cli/mod.js'
const wechatyCli = subcommands({
name: 'wechaty',
description: 'Wechaty CLI Utility',
version: VERSION,
cmds,
})
run(
binary(wechatyCli),
process.argv,
).catch(console.error)
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env node
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import os from 'os'
import { config } from '../src/config.js'
import { Doctor } from '../src/doctor.js'
import { WechatyBuilder } from '../src/wechaty-builder.js'
const wechaty = WechatyBuilder.singleton()
const doctor = new Doctor()
async function main () {
let ipcTestResult: string
try {
await doctor.testTcp()
ipcTestResult = 'PASS'
} catch (err) {
console.info(err)
ipcTestResult = 'FAIL. Please check your tcp network, Wechaty need to listen on localhost and connect to it.'
}
console.info(`
#### Wechaty Doctor
1. Wechaty version: ${wechaty.version()}
2. ${os.type()} ${os.arch()} version ${os.release()} memory ${Math.floor(os.freemem() / 1024 / 1024)}/${Math.floor(os.totalmem() / 1024 / 1024)} MB
3. Docker: ${config.docker}
4. Node version: ${process.version}
5. Tcp IPC TEST: ${ipcTestResult}
`)
}
main()
.catch(err => console.error('main() exception: %s', err))
+338
View File
@@ -0,0 +1,338 @@
#!/usr/bin/env bash
#
# Wechaty - Connect ChatBots
#
# https://github.com/wechaty/wechaty
#
set -e
export HOME=/bot
export PATH=$PATH:/wechaty/bin:/wechaty/node_modules/.bin
function wechaty::banner() {
echo
figlet " Wechaty "
echo ____________________________________________________
echo " https://www.chatie.io"
}
function wechaty::errorBotNotFound() {
local file=$1
echo "Container ERROR: can not found bot file: $HOME/$file"
echo "Container PWD: $(pwd)"
echo "Container HOME: $HOME"
echo "Container LS $HOME: $(ls -l $HOME)"
figlet " Troubleshooting "
cat <<'TROUBLESHOOTING'
Troubleshooting:
1. Did you bind the current directory into container?
check your `docker run ...` command, if there's no `volumn` arg,
then you need to add it so that we can bind the volume of /bot:
`--volume="$(pwd)":/bot`
this will let the container visit your current directory.
2. Are you sure your .js/.ts files aren't .js.txt/.ts.txt?
this could be a problem on new Windows installs (file
extensions hidden by default).
if you still have issue, please have a look at
https://github.com/wechaty/wechaty/issues/66
and do a search in issues, that might be help.
TROUBLESHOOTING
}
function wechaty::printEnv () {
num=$(env | grep -c WECHATY)
echo "WECHATY Environment Variables: $num"
env | grep WECHATY
}
function wechaty::errorCtrlC () {
# http://www.tldp.org/LDP/abs/html/exitcodes.html
# 130 Script terminated by Control-C Ctl-C Control-C is fatal error signal 2, (130 = 128 + 2, see above)
echo ' Script terminated by Control-C '
figlet ' Ctrl + C '
}
function wechaty::pressEnterToContinue() {
local -i timeoutSecond=${1:-30}
local message=${2:-'Press ENTER to continue ... '}
read -r -t "$timeoutSecond" -p "$message" || true
echo
}
function wechaty::diagnose() {
local -i ret=$1 && shift
local file=$1 && shift
echo "ERROR: Bot exited with code $ret"
figlet ' BUG REPORT '
wechaty::pressEnterToContinue 30
echo
echo "### 1. source code of $file"
echo
cat "$HOME/$file" || echo "ERROR: file not found"
echo
echo
echo "### 2. directory structor of $HOME"
echo
ls -l "$HOME"
echo
echo '### 3. package.json'
echo
cat "$HOME"/package.json || echo "No package.json"
echo
echo "### 4. directory structor inside $HOME/node_modules"
echo
ls "$HOME"/node_modules || echo "No node_modules"
echo
echo '### 5. wechaty doctor'
echo
wechaty-doctor
figlet " Submit a ISSUE "
echo _____________________________________________________________
echo '####### please paste all the above diagnose messages #######'
echo
echo 'Wechaty Issue https://github.com/wechaty/wechaty/issues'
echo
wechaty::pressEnterToContinue
}
function wechaty::runBot() {
local botFile=$1
if [ ! -f "$HOME/$botFile" ]; then
wechaty::errorBotNotFound "$botFile"
return 1
fi
echo "Working directory: $HOME"
cd "$HOME"
[ -f package.json ] && {
# echo "Install dependencies modules ..."
#
# NPM module install will have problem in China.
# i.e. chromedriver need to visit a google host to download binarys.
#
echo "Please make sure you had installed all the NPM modules which is depended on your bot script."
# yarn < /dev/null || return $? # yarn will close stdin??? cause `read` command fail after yarn
#
# Issue https://github.com/wechaty/wechaty/issues/1478
# As a conclusion: we should better not to link the local node_modules to the Docker global.
#
# wechaty::linkBotNodeModules
}
# echo -n "Linking Wechaty module to bot ... "
# npm link wechaty < /dev/null > /dev/null 2>&1
# echo "linked. "
# npm --progress=false install @types/node > /dev/null
#
# Huan(202108) node_arg: --es-module-specifier-resolution=node
#
# Make .js extension default for ESM imports
# See: https://github.com/nodejs/node/issues/30927#issuecomment-575998045 \
#
local -i ret=0
case "$botFile" in
*.js | *.cjs | *.mjs)
echo "Executing node $*"
node \
--es-module-specifier-resolution=node \
"$@" \
&
;;
*.ts)
echo "Executing node --loader=ts-node/esm $*"
node \
--es-module-specifier-resolution=node \
--no-warnings \
--loader=ts-node/esm \
"$@" \
&
;;
*)
echo "ERROR: wechaty::runBot() neither .{js,cjs,mjs} nor .ts"
exit 1 &
esac
wait "$!" || ret=$? # fix `can only `return' from a function or sourced script` error
case "$ret" in
0)
;;
130)
wechaty::errorCtrlC
;;
*)
wechaty::diagnose "$ret" "$@"
;;
esac
return "$ret"
}
# Issue https://github.com/wechaty/wechaty/issues/1478
# To Be Tested:
function wechaty::linkBotNodeModules() {
for localModule in /bot/node_modules/*; do
[ -e "$localModule" ] || continue
module=${localModule//\/bot\/node_modules\//}
globalModule="/node_modules/$module"
if [ ! -e "$globalModule" ]; then
ln -sf "$localModule" /node_modules/
# else
# echo "$globalModule exists"
fi
done
}
function wechaty::io-client() {
figlet " Chatie.io "
figlet " Authing By:"
echo
echo "WECHATY_TOKEN=$WECHATY_TOKEN "
echo
pushd /wechaty
npm run io-client
popd
}
function wechaty::help() {
figlet " Docker Usage: "
cat <<HELP
Usage: wechaty [ mybot.js | mybot.ts | COMMAND ]
Run a JavaScript/TypeScript <Bot File>, or a <Wechaty Command>.
<Bot File>:
mybot.js: a JavaScript program for your bot.
mybot.ts: a TypeScript program for your bot.
<Commands>:
demo Run Wechaty DEMO
doctor Print Diagnose Report
test Run Unit Test
Learn more at:
https://github.com/wechaty/wechaty/wiki/Docker
HELP
}
function main() {
# issue #84
echo -e 'nameserver 114.114.114.114\nnameserver 1.1.1.1\nnameserver 8.8.8.8' | sudo tee -a /etc/resolv.conf > /dev/null
wechaty::banner
figlet Connecting
figlet ChatBots
wechaty::printEnv
VERSION=$(WECHATY_LOG=WARN wechaty-version 2>/dev/null || echo '0.0.0(unknown)')
echo
echo -n "Starting Docker Container for Wechaty v$VERSION with "
echo -n "Node.js $(node --version) ..."
echo
local -i ret=0
local defaultArg=help
if [ -n "$WECHATY_TOKEN" ]; then
defaultArg=io-client
fi
case "${1:-${defaultArg}}" in
#
# 1. Get a shell
#
shell | sh | bash)
/bin/bash -s || ret=$?
;;
#
# 2. Run a bot
#
*.ts | *.js | *.cjs | *.mjs)
# set -e will not work inside wechaty::runBot because of
# http://stackoverflow.com/a/4073372/1123955
wechaty::runBot "$@" || ret=$?
;;
#
# 3. If there's additional `npm` arg...
#
npm)
shift
npm "$@" || ret=$?
;;
help|version)
wechaty::help
;;
io-client)
wechaty::io-client
;;
test)
# WECHATY_LOG=silent npm run test:unit
if [ -f "$HOME"/package.json ]; then
pushd "$HOME"
else
pushd /wechaty
fi
WECHATY_LOG=silent npm run test
popd
;;
#
# 4. Default to execute npm run ...
#
*)
[ "$1" = "run" ] && shift
npm run "$@" || ret=$?
;;
esac
wechaty::banner
figlet " Exit $ret "
return $ret
}
main "$@"
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import 'dotenv/config.js'
import {
log,
} from 'wechaty-puppet'
import {
config,
} from '../src/config.js'
import {
IoClient,
IoClientOptions,
} from '../src/io-client.js'
import { WechatyBuilder } from '../src/wechaty-builder.js'
const welcome = `
| __ __ _ _
| \\ \\ / /__ ___| |__ __ _| |_ _ _
| \\ \\ /\\ / / _ \\/ __| '_ \\ / _\` | __| | | |
| \\ V V / __/ (__| | | | (_| | |_| |_| |
| \\_/\\_/ \\___|\\___|_| |_|\\__,_|\\__|\\__, |
| |___/
=============== Powered by Wechaty ===============
-------- https://www.chatie.io --------
My super power: download cloud bot from www.chatie.io
__________________________________________________
`
async function main () {
const token = config.token
if (!token) {
throw new Error('token not found: please set WECHATY_TOKEN in environment before run io-client')
}
console.info(welcome)
log.info('Client', 'Starting for WECHATY_TOKEN: %s', token)
const wechaty = WechatyBuilder.build({ name: token })
let port
if (process.env['WECHATY_PUPPET_SERVER_PORT']) {
port = parseInt(process.env['WECHATY_PUPPET_SERVER_PORT'])
}
const options: IoClientOptions = {
token,
wechaty,
}
if (port) {
options.port = port
}
const client = new IoClient(options)
client.start()
.catch(onError.bind(client))
}
async function onError (
this : IoClient,
e : Error,
) {
log.error('Client', 'start() fail: %s', e)
await this.quit()
process.exit(-1)
}
main()
.catch(console.error)
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env node
import { OFFICIAL_PUPPET_DEPENDENCIES } from '../src/puppet-config.js'
async function main () {
const puppetNameList = Object.keys(OFFICIAL_PUPPET_DEPENDENCIES)
const publicNameList = puppetNameList.filter(name => /^[^@]/.test(name))
const urlPath = publicNameList.join('-vs-')
const url = 'https://www.npmtrends.com/' + urlPath
console.info(url)
}
main()
.catch(console.error)
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import spawn from 'cross-spawn'
import { log } from 'wechaty-puppet'
import {
OFFICIAL_PUPPET_DEPENDENCIES,
OfficialPuppetNpmName,
} from '../src/puppet-config.js'
/**
* Install all `wechaty-puppet-*` modules from `puppet-config.ts`
*/
async function main (): Promise<void> {
log.info('PuppetInstall', 'main() installing ...')
const skipList = [
'@juzibot/wechaty-puppet-donut', // windows puppet
'@juzibot/wechaty-puppet-wxwork', // wxwork puppet
]
const moduleList: string[] = []
for (const puppetModuleName of Object.keys(OFFICIAL_PUPPET_DEPENDENCIES)) {
const version = OFFICIAL_PUPPET_DEPENDENCIES[puppetModuleName as OfficialPuppetNpmName]
if (version === '0.0.0' || skipList.includes(puppetModuleName)) {
continue
}
moduleList.push(`${puppetModuleName}@${version}`)
}
const args = [
'install',
'--no-save',
...moduleList,
]
log.info('PuppetInstall', `installing ... Shell: "npm install ${args.join(' ')}"\n`)
const result = spawn('npm', args, { stdio: 'inherit' })
await new Promise(resolve => result.once('exit', resolve))
log.info('PuppetInstall', 'main() installing ... done')
}
main().catch(e => {
console.error(e)
process.exit(1)
})
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env node
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { VERSION } from '../src/config.js'
console.info(VERSION)
+1
View File
@@ -0,0 +1 @@
theme: jekyll-theme-minimal
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

+218
View File
@@ -0,0 +1,218 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="258.000000pt" height="258.000000pt" viewBox="0 0 550.000000 550.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.16, written by Peter Selinger 2001-2019
</metadata>
<g transform="translate(0.000000,550.000000) scale(0.100000,-0.100000)"
fill="#44a838" stroke="none">
<path d="M180 4810 l0 -480 480 0 480 0 0 480 0 480 -480 0 -480 0 0 -480z
m829 23 c0 -175 -3 -330 -6 -345 l-5 -27 -339 0 -339 0 0 344 0 345 344 0 345
0 0 -317z"/>
<path d="M460 4805 l0 -205 205 0 205 0 0 205 0 205 -205 0 -205 0 0 -205z"/>
<path d="M1280 5150 l0 -140 135 0 135 0 0 70 0 70 -65 0 -65 0 0 70 0 70 -70
0 -70 0 0 -140z"/>
<path d="M1690 5150 l0 -140 135 0 134 0 6 -32 c3 -18 4 -44 1 -58 -2 -14 -5
-28 -5 -32 -1 -5 -154 -8 -341 -8 l-340 0 0 -275 0 -275 70 0 70 0 0 135 0
135 270 0 270 0 6 -25 c3 -14 4 -35 0 -48 -3 -12 -3 -32 0 -43 4 -16 12 -19
45 -16 21 2 50 1 64 -2 23 -6 25 -10 25 -71 l0 -65 -71 0 -70 0 2 58 c3 68 -4
83 -34 78 -12 -3 -39 -5 -59 -5 l-38 -1 0 -135 0 -135 135 0 135 0 0 -70 0
-70 -205 0 -205 0 -2 206 -3 206 -65 1 -65 2 -3 -207 -2 -208 70 0 70 0 0
-135 0 -135 -70 0 -70 0 0 70 0 70 -65 0 -65 0 0 -70 0 -70 -70 0 -70 0 0 135
0 135 -70 0 -70 0 0 70 0 70 -64 0 -64 0 -5 -37 c-3 -21 -5 -51 -4 -68 2 -30
2 -30 70 -33 l67 -3 0 -64 0 -65 -135 0 -135 0 0 65 0 65 -70 0 -70 0 0 70 0
70 -70 0 -70 0 0 -135 0 -135 -65 0 -65 0 0 65 0 65 -70 0 -70 0 0 -135 0
-135 135 0 135 0 0 70 0 70 70 0 70 0 0 -209 0 -209 -57 0 c-32 -1 -64 -1 -70
-1 -9 -1 -13 -21 -13 -66 l0 -65 -65 0 -65 0 0 -140 0 -140 -70 0 -69 0 -3
-67 -3 -68 -65 -1 -65 -1 -3 -137 -3 -136 70 0 70 0 3 138 3 137 108 3 c60 2
120 1 133 -2 l24 -6 0 -270 0 -270 -135 0 -135 0 0 65 0 65 -70 0 -70 0 0 -65
0 -65 70 0 70 0 0 -140 0 -140 -70 0 -70 0 0 -135 0 -135 70 0 70 0 0 -70 0
-70 -70 0 -70 0 0 -70 0 -70 70 0 70 0 0 -65 0 -65 -70 0 -70 0 0 -70 0 -70
480 0 480 0 0 70 0 70 140 0 140 0 0 135 0 135 65 0 65 0 0 -205 0 -205 70 0
70 0 0 -70 0 -70 70 0 70 0 0 -65 c0 -45 -4 -65 -12 -67 -7 0 -38 -2 -68 -2
l-55 -1 -3 -67 -3 -67 138 -3 138 -3 2 -135 2 -135 65 -3 66 -3 0 -64 0 -65
-135 0 -135 0 0 65 0 65 -70 0 -70 0 0 70 0 70 -70 0 -70 0 0 -70 0 -70 -65 0
-65 0 0 70 0 70 -70 0 -70 0 0 -70 0 -70 70 0 70 0 0 -65 0 -65 135 0 135 0 0
-140 0 -140 70 0 70 0 0 70 0 71 68 -3 67 -3 2 -35 c1 -19 2 -50 2 -67 l1 -33
340 0 340 0 0 140 0 140 -68 0 -69 0 1 63 1 62 68 3 67 3 0 70 0 70 -67 -3
-68 -3 2 -67 1 -68 -139 0 -139 0 0 70 0 70 70 0 69 0 3 138 3 137 130 0 130
0 3 -67 3 -68 69 0 70 0 0 -70 0 -70 135 0 135 0 0 -70 0 -70 70 0 70 0 0 -65
0 -65 70 0 70 0 0 65 0 65 135 0 135 0 0 -65 0 -65 -64 0 c-42 0 -65 -4 -68
-12 -3 -7 -4 -39 -3 -71 l2 -57 -209 0 -208 0 0 70 0 70 -64 0 c-75 0 -83 10
-71 86 l6 44 -70 0 -71 0 0 -135 0 -135 135 0 135 0 0 -70 0 -70 345 0 345 0
0 70 0 70 135 0 135 0 0 -70 0 -70 70 0 70 0 0 70 0 70 70 0 70 0 0 -70 0 -71
68 3 67 3 0 65 0 65 138 3 137 3 0 69 0 70 135 0 135 0 0 65 0 65 70 0 70 0 0
-65 0 -65 70 0 70 0 0 135 0 135 -140 0 -140 0 0 -70 0 -70 -65 0 -65 0 0 140
0 140 205 0 205 0 0 205 0 205 -70 0 -70 0 0 70 0 70 70 0 70 0 0 135 0 135
-70 0 -70 0 0 70 0 70 70 0 71 0 -3 66 -3 66 -67 2 -68 2 0 -68 0 -68 -70 0
-69 0 -3 66 -3 66 -130 2 -130 1 -3 68 -3 67 136 0 135 0 0 -64 0 -64 33 -5
c17 -3 48 -5 67 -4 l35 2 3 68 3 67 69 0 70 0 0 140 0 140 -70 0 -70 0 0 -140
0 -140 -70 0 -70 0 0 70 0 70 -272 -2 -273 -3 0 -65 0 -65 68 -3 67 -3 0 -71
0 -71 -135 6 -135 7 0 -140 0 -140 -140 0 -140 0 0 -205 0 -205 -65 0 -65 0 0
205 0 205 -70 0 -70 0 0 -70 0 -70 -70 0 -70 0 0 -65 0 -65 70 0 70 0 0 -140
0 -140 135 0 135 0 0 -205 0 -205 -65 0 -65 0 0 -70 0 -70 65 0 65 0 0 -65 0
-65 -135 0 -135 0 0 205 0 205 70 0 71 0 -3 67 -3 68 -65 0 -65 0 -3 -67 -3
-68 -69 0 -70 0 0 -70 0 -70 -135 0 -135 0 0 -70 0 -70 -70 0 -70 0 0 70 0 70
-70 0 -70 0 0 205 0 205 -205 0 -205 0 0 70 0 70 70 0 70 0 0 135 0 135 70 0
71 0 -2 -132 -1 -133 66 -3 66 -3 0 136 0 135 70 0 70 0 0 -65 0 -66 136 3
136 3 2 200 1 200 68 3 68 3 -3 66 -3 66 -59 0 c-70 -1 -81 -12 -74 -84 l5
-49 -68 0 -69 0 0 70 0 69 68 3 67 3 -4 63 -3 62 71 0 71 0 0 -65 c0 -45 4
-65 13 -66 6 0 63 0 125 0 62 0 119 -2 127 -6 11 -4 15 -22 15 -69 l0 -64 65
0 65 0 0 68 0 67 -63 1 -62 1 -3 67 -3 66 -69 0 -70 0 0 70 0 70 70 0 70 0 0
70 0 70 135 0 135 0 0 69 0 69 68 -2 67 -2 3 -67 3 -67 204 0 205 0 0 135 0
135 70 0 70 0 0 -68 0 -67 133 0 132 0 3 68 3 67 69 0 70 0 0 410 c0 321 -3
410 -12 412 -7 0 -38 2 -68 2 l-55 1 -3 138 -3 137 71 0 70 0 0 70 0 70 -70 0
-70 0 0 65 0 65 70 0 70 0 0 70 0 70 -140 0 -140 0 0 -410 0 -410 -65 0 -65 0
0 135 0 135 -70 0 -70 0 0 70 0 70 -70 0 -70 0 0 70 0 69 -67 3 -68 3 0 60 0
60 138 3 137 3 0 -66 0 -65 70 0 70 0 0 135 0 135 -204 0 c-231 0 -219 5 -211
-85 l6 -55 -211 0 -210 0 0 70 0 70 -65 0 -65 0 0 135 c0 121 2 135 18 136 9
1 31 3 47 4 60 5 65 0 65 -71 l0 -64 70 0 70 0 0 135 0 135 -70 0 -70 0 0 140
0 140 70 0 70 0 0 65 0 65 -70 0 -70 0 0 140 0 140 -135 0 -135 0 0 -70 0 -70
70 0 70 0 0 -135 0 -135 -140 0 -140 0 0 -140 0 -140 140 0 141 0 -3 -67 -3
-68 -65 0 -65 0 -3 -137 -3 -138 -69 0 -70 0 0 -70 0 -71 -67 3 -68 3 0 135 0
135 68 3 67 3 0 67 0 67 -65 0 -65 0 -2 -40 c-1 -22 -1 -52 0 -67 l2 -28 -70
0 -70 0 0 135 0 135 -70 0 -70 0 0 -135 0 -135 -70 0 -70 0 0 65 c0 36 -3 65
-7 65 -64 4 -123 4 -124 0 0 -3 -1 -63 -2 -135 l-2 -130 -67 -3 -68 -3 0 136
c0 74 -3 135 -7 136 -71 4 -110 5 -120 1 -10 -3 -13 -40 -13 -138 l0 -134 -69
0 -69 0 0 138 c0 155 11 138 -87 134 l-45 -2 0 -65 0 -65 -70 0 -70 0 0 135 0
135 70 0 70 0 0 70 0 70 65 0 65 0 1 55 c1 30 2 61 3 68 1 9 37 13 139 15
l137 3 0 -71 0 -70 -70 0 -70 0 0 -70 0 -70 70 0 70 0 0 70 0 71 68 -3 c51 -2
67 -7 68 -18 1 -8 2 -39 2 -67 l2 -53 65 0 65 0 0 140 0 140 -70 0 -69 0 5
160 c4 89 3 181 -1 205 l-6 45 -199 0 c-149 0 -199 -3 -203 -12 -2 -7 -3 -39
-2 -70 l3 -58 -69 0 -69 0 0 70 0 70 -70 0 -70 0 0 -70 0 -70 -70 0 -70 0 0
-70 0 -70 -64 0 c-61 0 -64 1 -70 26 -4 15 -7 72 -6 128 1 55 1 107 1 114 -1
9 -35 12 -136 12 l-135 0 0 -140z m1100 -70 l0 -70 -135 0 c-153 0 -150 1
-141 -87 l4 -43 -139 0 -139 0 0 -140 0 -140 -70 0 -70 0 0 70 0 70 -65 0
c-55 0 -65 3 -65 18 -1 9 -2 41 -4 70 l-3 52 68 0 69 0 0 65 0 65 208 2 207 3
-2 59 c-2 32 0 62 3 67 3 5 66 9 140 9 l134 0 0 -70z m550 -820 l0 -70 -70 0
-70 0 0 70 0 70 70 0 70 0 0 -70z m-823 -205 l2 -130 66 -3 65 -3 0 -69 0 -70
135 0 c135 0 142 2 136 34 -1 6 1 32 4 58 l7 48 64 0 64 0 0 65 0 64 -67 3
-68 3 0 55 c-1 80 -1 80 71 80 l64 0 0 -70 0 -70 70 0 70 0 0 -65 0 -65 -70 0
-70 0 0 -70 0 -70 -68 0 -69 0 1 -67 1 -68 -67 -3 -67 -3 -3 -67 -3 -67 -67
-1 -68 -2 0 140 0 139 -67 -3 -67 -3 2 -125 c1 -69 2 -128 2 -132 0 -4 28 -8
63 -10 l62 -3 3 -42 3 -43 135 0 c120 0 134 2 135 18 1 9 1 49 1 89 -1 112 0
113 74 113 l63 0 3 -67 3 -67 65 -1 65 0 3 -41 c3 -39 5 -42 36 -47 45 -7 97
-43 127 -86 21 -31 24 -50 27 -138 l4 -103 38 0 c45 0 51 -11 49 -83 -2 -40
-5 -47 -23 -48 -12 -1 -25 -2 -31 -4 -5 -1 -16 -3 -22 -3 -10 -2 -13 -49 -13
-202 0 -110 3 -200 8 -201 54 -6 72 -6 72 -1 1 4 2 66 3 137 l2 130 68 3 67 3
0 134 0 134 -67 3 -68 3 -1 120 c-1 66 -2 128 -3 138 -1 14 -11 17 -66 17
l-65 0 0 70 0 70 119 0 c66 0 126 -3 135 -6 14 -5 16 -33 16 -210 l0 -204 140
0 140 0 0 205 c0 157 -3 205 -12 206 -7 1 -22 3 -33 4 -11 1 -37 3 -57 3 l-38
2 0 135 0 135 -70 0 -70 0 0 -70 0 -71 -67 3 -67 3 -2 68 -3 67 70 0 69 0 0
135 0 135 140 0 140 0 0 -65 0 -65 65 0 65 0 0 -70 0 -70 70 0 70 0 0 70 0 70
70 0 70 0 0 -70 0 -70 135 0 135 0 0 -70 0 -70 70 0 70 0 0 -135 0 -135 70 0
70 0 0 -70 0 -70 65 0 65 0 0 70 0 70 70 0 70 0 0 -140 0 -140 -205 0 -204 0
-3 -67 -3 -68 -67 1 -68 2 -2 -207 -3 -206 -130 0 -130 0 -3 67 -3 67 67 3 66
3 0 120 c-1 114 5 157 21 156 4 -1 32 -1 62 -1 l55 0 0 65 0 65 -205 0 -204 0
-3 -67 -3 -68 -67 -3 -68 -3 0 -134 0 -135 70 0 71 0 -3 -67 -3 -68 -133 0
-132 0 0 -67 0 -68 -70 0 -70 0 0 65 0 65 -32 0 c-18 1 -79 2 -135 3 l-101 2
-4 -60 c-2 -33 -2 -63 0 -67 2 -5 32 -8 68 -8 l64 0 0 -70 0 -71 -67 3 -68 3
-1 45 c-3 96 0 90 -43 90 l-39 0 -4 -102 c-4 -116 -20 -153 -87 -199 l-36 -24
-375 -5 -375 -5 2 -107 2 -108 -69 0 -70 0 0 -70 0 -70 -70 0 -70 0 0 -70 0
-70 135 0 c101 0 135 3 136 13 1 6 2 37 3 67 l1 55 68 3 67 3 0 69 0 70 70 0
70 0 0 -70 0 -70 -70 0 -70 0 0 -135 0 -135 -205 0 -205 0 0 -70 0 -70 -70 0
-70 0 0 70 0 70 70 0 70 0 0 65 0 64 -137 3 -138 3 0 65 0 65 138 3 137 3 0
69 0 70 -205 0 -205 0 0 -70 0 -70 -140 0 -140 0 0 138 0 138 -65 -1 -65 0 0
-67 0 -68 -70 0 -70 0 0 -140 0 -140 -70 0 -70 0 0 -65 0 -65 -69 0 -68 0 1
65 1 65 68 0 67 0 0 70 0 70 -135 0 -135 0 0 205 0 205 135 0 135 0 0 70 0 70
-135 0 -135 0 0 205 0 205 -70 0 -70 0 0 70 0 70 70 0 70 0 0 135 0 135 -70 0
-70 0 2 207 3 208 45 3 c84 6 90 1 90 -73 l0 -65 68 0 67 0 1 -70 0 -70 -68 0
-68 0 0 -70 0 -70 64 0 63 0 6 -38 c4 -20 4 -50 2 -64 l-5 -27 137 -3 138 -3
3 -67 3 -68 69 0 70 0 0 135 0 135 -70 0 -70 0 0 70 0 70 205 0 205 0 0 70 0
70 70 0 70 0 0 65 c0 36 -3 65 -7 66 -5 0 -35 1 -68 2 l-60 2 -3 138 -3 137
71 0 70 0 0 70 0 70 135 0 135 0 0 65 0 65 70 0 70 0 0 70 0 71 138 -3 137 -3
2 -130z m961 -137 l-3 -133 -67 -3 -68 -3 0 136 0 135 71 0 70 0 -3 -132z
m-2338 -208 l0 -70 140 0 140 0 0 70 0 70 65 0 65 0 0 -70 0 -70 -65 0 -65 0
0 -68 0 -68 40 4 c22 2 51 1 65 -2 23 -6 25 -10 25 -71 l0 -65 -65 0 -64 0 -3
68 -3 67 -55 0 c-81 0 -80 1 -80 -71 l0 -65 -137 3 -138 3 -3 44 c-6 82 -1 89
70 89 l63 -1 3 67 3 66 -136 0 -135 0 0 70 0 70 135 0 135 0 0 -70z m0 -550
l0 -70 -65 0 c-36 0 -65 3 -66 8 -4 30 -4 109 -1 120 3 8 26 12 68 12 l64 0 0
-70z m3980 -270 l0 -70 -205 0 -205 0 0 70 0 70 205 0 205 0 0 -70z m-4390
-550 l0 -70 -70 0 -70 0 0 70 0 70 70 0 70 0 0 -70z m0 -274 l0 -64 -97 -7
c-54 -3 -146 -5 -205 -3 l-108 3 0 67 0 68 205 0 205 0 0 -64z m3980 -205 l0
-141 -70 0 -70 0 0 -135 0 -135 -69 0 -68 0 1 133 1 132 68 3 67 3 0 134 c0
150 -7 139 88 144 l52 2 0 -140z m-1510 64 l0 -65 -70 0 -70 0 0 -140 0 -140
-64 0 c-66 0 -78 8 -71 46 2 11 4 97 4 192 l1 172 135 0 135 0 0 -65z m1780
-135 l0 -70 70 0 70 0 0 -135 0 -135 -135 0 -135 0 0 -70 0 -70 65 0 65 0 0
-70 0 -70 -65 0 -65 0 0 -135 0 -135 -70 0 -70 0 0 340 0 340 135 0 135 0 0
70 0 70 -65 0 -65 0 0 70 0 70 65 0 65 0 0 -70z m-4390 -140 l0 -70 140 0 140
0 0 -65 0 -65 -205 0 -205 0 0 65 0 65 -70 0 -70 0 0 70 0 70 135 0 135 0 0
-70z m3710 -135 l0 -65 -70 0 -70 0 0 65 0 65 70 0 70 0 0 -65z m-2332 -114
c7 -93 10 -91 -143 -91 l-135 0 0 70 0 71 138 -3 137 -3 3 -44z m544 -103 c2
-7 4 -38 4 -68 l-1 -55 -67 -3 -67 -3 -3 -67 -3 -67 -55 0 c-80 -1 -80 -1 -80
71 l0 64 70 0 70 0 0 70 0 70 64 0 c42 0 65 -4 68 -12z m2058 -193 l0 -205
-205 0 -205 0 0 205 0 205 205 0 205 0 0 -205z m-139 -352 c1 -5 3 -33 4 -63
1 -30 3 -58 4 -62 0 -5 30 -8 66 -8 l65 0 0 70 0 70 70 0 70 0 0 -70 0 -70
-70 0 -70 0 0 -65 0 -65 -65 0 -65 0 -1 -62 c-1 -35 -1 -66 0 -70 0 -5 -30 -8
-69 -8 l-70 0 0 205 0 205 65 0 c36 0 66 -3 66 -7z m-1914 -335 l1 -68 -69 0
-69 0 0 70 0 71 68 -3 67 -3 2 -67z"/>
<path d="M2240 3915 l0 -135 -135 0 c-101 0 -135 -3 -135 -12 0 -7 -2 -67 -3
-134 l-2 -121 129 -6 c72 -3 134 -3 138 -1 4 3 8 34 8 70 l0 64 70 0 70 0 0
70 0 70 65 0 65 0 1 33 c0 17 1 49 3 70 l3 37 -69 0 -68 0 0 65 0 65 -70 0
-70 0 0 -135z"/>
<path d="M3890 3710 l0 -70 65 0 65 0 0 70 0 70 -65 0 -65 0 0 -70z"/>
<path d="M4162 3573 l3 -68 55 -1 c30 0 61 -1 68 -2 8 -1 12 -23 12 -67 l0
-65 -140 0 -140 0 0 -140 0 -140 70 0 70 0 0 70 0 70 205 0 205 0 0 -70 0 -70
70 0 70 0 0 70 0 70 -70 0 -69 0 -3 138 -3 137 -58 1 c-31 1 -61 4 -65 8 -4 4
-7 33 -7 64 l0 57 -138 3 -138 3 3 -68z"/>
<path d="M2240 3464 l0 -44 70 0 70 0 0 40 c0 25 -5 40 -12 41 -7 0 -39 2 -70
4 l-58 4 0 -45z"/>
<path d="M1969 3353 c-1 -10 0 -33 1 -50 4 -73 4 -73 -146 -73 l-134 0 0 -70
0 -70 70 0 70 0 0 -65 0 -65 80 -1 c44 -1 87 -1 95 -1 45 0 44 -6 47 170 3
154 5 169 26 199 12 17 22 34 22 37 0 3 -29 6 -65 6 -55 0 -65 -3 -66 -17z"/>
<path d="M2466 3131 c-20 -22 -15 -56 9 -65 12 -4 15 -28 15 -114 l0 -109 -55
-38 c-117 -80 -180 -222 -160 -359 l7 -46 444 0 444 0 6 26 c12 47 -4 162 -30
219 -27 60 -91 134 -142 166 l-34 21 0 113 c0 88 3 115 15 125 38 31 -1 94
-44 71 -30 -17 -32 -48 -5 -70 24 -19 25 -23 22 -123 -2 -62 -7 -102 -13 -101
-5 2 -37 12 -70 24 -81 29 -225 29 -307 1 -31 -11 -59 -18 -62 -15 -3 4 -6 48
-6 98 0 79 3 94 20 110 25 23 26 61 1 75 -27 14 -35 13 -55 -9z m162 -527 c42
-29 21 -102 -31 -105 -39 -2 -52 2 -69 23 -23 26 -23 40 3 72 24 30 63 34 97
10z m302 -4 c42 -42 13 -100 -51 -102 -61 -2 -89 73 -42 110 25 18 70 15 93
-8z"/>
<path d="M1560 2952 c-6 -2 -10 -33 -10 -68 l0 -64 -65 0 -65 0 0 -140 0 -140
65 0 65 0 0 -65 0 -65 -65 0 -65 0 0 65 c0 36 -3 66 -7 66 -25 4 -110 5 -120
1 -9 -3 -13 -26 -13 -68 l0 -64 -70 0 -70 0 0 65 c0 36 -3 65 -7 66 -5 0 -34
2 -65 3 l-58 3 0 -53 c0 -29 -3 -59 -6 -68 -5 -14 4 -16 65 -16 l71 0 0 -70 0
-70 70 0 70 0 0 -70 0 -70 -70 0 -70 0 0 -67 0 -68 -67 0 -67 0 -2 -67 -2 -68
139 0 139 0 0 135 0 135 135 0 135 0 0 -64 c0 -73 1 -74 80 -72 l55 1 3 68 3
67 134 0 134 0 7 -45 c3 -25 4 -53 1 -64 -4 -16 4 -19 77 -25 44 -3 107 -6
139 -6 l57 0 0 -65 0 -65 70 0 71 0 -3 68 -3 67 -67 3 -68 3 0 38 c0 36 -2 39
-34 44 -71 11 -142 82 -153 153 l-6 34 -178 0 -179 0 0 -70 0 -70 -70 0 -70 0
0 140 0 140 70 0 70 0 0 135 0 136 137 -3 137 -3 1 -60 c0 -58 1 -60 29 -67
54 -12 56 -7 56 138 l0 134 -180 0 -180 0 0 64 0 64 -60 4 c-33 1 -65 2 -70 0z
m130 -202 l0 -70 -70 0 -70 0 0 70 0 70 70 0 70 0 0 -70z m-270 -410 l0 -70
-70 0 -70 0 0 70 0 70 70 0 70 0 0 -70z"/>
<path d="M3755 2952 c-3 -3 -5 -34 -5 -69 l0 -63 70 0 70 0 0 65 c0 36 -3 65
-7 66 -42 4 -124 4 -128 1z"/>
<path d="M1008 2803 c-2 -10 -2 -42 0 -70 l4 -53 64 0 64 0 0 70 0 70 -64 0
c-52 0 -64 -3 -68 -17z"/>
<path d="M2270 2345 l0 -25 455 0 455 0 0 25 0 25 -455 0 -455 0 0 -25z"/>
<path d="M1690 1928 l0 -68 70 0 70 0 0 65 c0 73 9 65 -92 70 l-48 2 0 -69z"/>
<path d="M4302 1103 l3 -67 63 0 62 -1 0 52 c0 28 3 58 6 67 5 14 -4 16 -65
16 l-72 0 3 -67z"/>
<path d="M3060 5220 l0 -70 70 0 70 0 0 -70 0 -70 70 0 70 0 0 -65 0 -65 -70
0 -70 0 0 -70 0 -70 70 0 70 0 0 70 0 69 68 3 c67 3 67 3 68 33 0 17 1 45 2
63 l2 32 65 0 65 0 0 140 0 140 -135 0 -135 0 0 -70 0 -70 -70 0 -70 0 0 70 0
70 -70 0 -70 0 0 -70z"/>
<path d="M4300 4810 l0 -480 480 0 480 0 0 480 0 480 -480 0 -480 0 0 -480z
m818 -2 l-3 -343 -336 -3 -337 -2 -5 21 c-9 32 -9 654 -1 662 4 4 160 7 346 7
l338 0 -2 -342z"/>
<path d="M4570 4805 l0 -205 205 0 205 0 0 205 0 205 -205 0 -205 0 0 -205z"/>
<path d="M180 3435 l0 -205 70 0 70 0 0 135 c0 101 3 135 13 137 38 4 56 6 90
7 l37 1 0 65 0 65 -140 0 -140 0 0 -205z"/>
<path d="M4160 2200 l0 -70 70 0 70 0 0 70 0 70 -70 0 -70 0 0 -70z"/>
<path d="M1280 1240 l0 -70 70 0 70 0 0 70 0 70 -70 0 -70 0 0 -70z"/>
<path d="M3340 1240 l0 -70 135 0 135 0 0 70 0 70 -135 0 -135 0 0 -70z"/>
<path d="M180 690 l0 -480 480 0 480 0 0 480 0 480 -480 0 -480 0 0 -480z
m814 344 c10 -4 13 -78 14 -304 2 -164 -1 -317 -4 -340 l-7 -40 -338 0 -339 0
0 338 c0 186 3 342 6 345 8 7 649 8 668 1z"/>
<path d="M460 695 l0 -205 205 0 205 0 0 205 0 205 -205 0 -205 0 0 -205z"/>
<path d="M3206 1032 c-3 -3 -6 -34 -6 -69 l0 -63 139 0 138 0 -1 65 -1 65
-100 1 c-55 0 -114 2 -132 4 -17 2 -34 1 -37 -3z"/>
<path d="M1420 280 l0 -70 65 0 65 0 0 70 0 70 -65 0 -65 0 0 -70z"/>
<path d="M4980 280 l0 -70 140 0 140 0 0 70 0 70 -140 0 -140 0 0 -70z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

+6
View File
@@ -0,0 +1,6 @@
__ __ _ _
\ \ / /__ ___| |__ __ _| |_ _ _
\ \ /\ / / _ \/ __| '_ \ / _` | __| | | |
\ V V / __/ (__| | | | (_| | |_| |_| |
\_/\_/ \___|\___|_| |_|\__,_|\__|\__, |
|___/
+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1920.000000pt" height="540.000000pt" viewBox="0 0 1920.000000 540.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.16, written by Peter Selinger 2001-2019
</metadata>
<g transform="translate(0.000000,540.000000) scale(0.100000,-0.100000)"
fill="#ffffff" stroke="none">
<path d="M2710 4733 c-36 -63 -73 -129 -83 -146 l-19 -30 -73 73 -74 74 -23
-20 c-13 -11 -52 -46 -87 -77 l-63 -56 -77 59 c-42 33 -85 75 -95 95 -34 62
-126 115 -200 115 -139 0 -263 -140 -241 -273 15 -87 83 -172 159 -197 l36
-12 0 -531 0 -532 -73 -39 c-441 -240 -796 -650 -941 -1086 -75 -225 -79 -259
-83 -718 l-5 -412 2279 0 2279 0 14 178 c18 248 9 582 -20 717 -106 497 -421
930 -904 1244 -61 39 -114 71 -118 71 -4 0 -8 250 -8 555 l0 555 37 15 c60 25
99 61 127 117 44 87 32 184 -32 258 -51 60 -104 84 -182 84 -47 0 -76 -6 -106
-22 -78 -41 -134 -128 -134 -210 0 -31 -22 -53 -52 -52 -7 1 -53 64 -102 141
l-88 140 -70 -123 c-39 -68 -77 -134 -85 -147 l-15 -23 -72 71 -73 72 -112
-55 -113 -55 -54 34 c-30 19 -59 35 -65 35 -5 0 -46 -21 -90 -46 -45 -25 -85
-42 -89 -37 -5 4 -36 77 -71 161 -34 83 -65 152 -68 152 -3 0 -35 -53 -71
-117z m134 -108 c32 -80 59 -145 61 -145 1 0 42 22 91 50 49 27 96 49 104 49
8 0 38 -15 65 -33 l50 -33 110 54 109 54 81 -81 81 -80 79 140 c44 77 82 140
85 140 3 0 18 -21 35 -48 16 -26 53 -84 82 -129 l53 -82 37 16 c26 10 38 12
41 4 16 -47 72 -108 125 -135 l57 -29 0 -523 0 -524 -57 26 c-366 164 -699
238 -1078 237 -381 -1 -704 -67 -1057 -217 l-38 -16 0 509 0 509 34 12 c84 28
156 125 159 218 2 45 0 45 79 -16 l57 -44 84 75 83 76 80 -80 80 -80 44 78
c85 151 111 193 118 193 4 0 33 -66 66 -145z m-419 -2509 c149 -39 248 -151
247 -281 0 -67 -11 -102 -48 -156 -82 -120 -297 -177 -431 -115 -62 29 -123
90 -155 154 -34 69 -32 169 4 238 33 63 114 137 171 157 53 19 147 21 212 3z
m1546 -15 c122 -47 199 -150 199 -269 0 -78 -16 -118 -75 -180 -65 -70 -145
-105 -257 -110 -76 -4 -92 -1 -140 20 -157 73 -226 246 -155 390 33 67 109
140 170 162 59 22 185 15 258 -13z"/>
<path d="M13340 4062 c-46 -81 -85 -149 -87 -151 -2 -3 -37 28 -78 69 l-75 74
-27 -25 c-16 -14 -54 -48 -86 -77 l-58 -52 -96 77 -96 76 -144 -71 c-79 -40
-145 -72 -147 -72 -2 0 -7 19 -10 43 -19 123 -144 220 -263 203 -84 -11 -166
-73 -197 -148 -38 -90 -5 -216 73 -278 l41 -33 0 -1284 0 -1283 110 0 110 0 0
578 c0 635 2 657 61 773 39 78 144 186 216 223 154 78 354 72 497 -14 74 -44
158 -137 197 -215 56 -116 59 -145 59 -772 l0 -573 105 0 106 0 -4 628 c-3
696 -1 671 -73 817 -87 179 -255 315 -453 366 -106 28 -287 28 -392 1 -92 -25
-195 -76 -264 -131 l-50 -40 -3 466 c-2 437 -1 468 15 477 33 18 83 80 95 116
10 33 20 40 156 105 80 39 151 70 157 69 6 -1 53 -34 103 -74 l92 -72 86 77
87 77 78 -78 c43 -43 80 -76 82 -73 2 2 38 64 79 137 42 74 78 130 81 125 3
-4 30 -71 62 -148 31 -77 58 -141 60 -143 2 -2 43 19 93 47 49 28 96 51 104
51 7 0 37 -16 65 -35 29 -19 55 -35 59 -35 4 0 54 27 111 61 l103 61 81 -77
82 -76 74 140 c41 78 77 141 81 141 4 0 44 -54 90 -120 46 -66 87 -120 92
-120 4 0 38 13 74 29 l65 30 38 -49 c21 -27 42 -49 47 -50 4 0 53 29 107 65
55 36 102 65 105 65 3 0 44 -30 91 -66 47 -36 87 -63 89 -61 2 2 30 66 62 142
31 76 62 140 67 141 6 2 52 -48 104 -111 52 -63 97 -113 100 -111 3 2 34 20
69 40 l62 38 65 -22 c72 -24 95 -41 95 -71 0 -34 38 -95 84 -136 l45 -40 3
-1079 c3 -994 5 -1084 21 -1138 55 -185 180 -302 365 -342 44 -9 132 -14 255
-14 l187 0 0 99 0 99 -197 4 c-231 5 -274 17 -343 96 -70 79 -70 75 -70 719
l0 573 305 0 305 0 0 105 0 105 -305 0 -305 0 0 391 0 391 44 39 c136 120 90
343 -85 404 -23 8 -56 15 -73 15 -96 0 -205 -79 -228 -165 -7 -25 -17 -45 -24
-45 -6 0 -41 11 -78 25 l-66 25 -57 -35 c-32 -19 -62 -35 -68 -35 -5 0 -57 57
-114 125 -58 69 -108 122 -111 118 -4 -5 -31 -66 -60 -138 -29 -71 -56 -138
-62 -148 -8 -15 -18 -10 -86 43 -43 33 -81 60 -85 60 -4 0 -52 -30 -107 -66
l-100 -67 -22 29 c-13 16 -32 39 -43 52 l-21 24 -62 -31 c-34 -17 -65 -31 -70
-31 -4 0 -50 61 -102 136 -80 114 -96 133 -106 119 -6 -8 -42 -74 -78 -145
-37 -72 -70 -130 -73 -130 -3 0 -38 30 -79 66 l-73 66 -101 -61 c-56 -34 -105
-61 -110 -61 -5 0 -34 16 -64 35 -30 19 -60 35 -66 35 -6 0 -47 -21 -91 -46
-45 -25 -84 -43 -87 -40 -3 4 -36 78 -72 166 l-66 159 -84 -147z"/>
<path d="M9393 2959 c-215 -21 -397 -106 -547 -256 -122 -122 -209 -274 -252
-443 -29 -114 -27 -376 4 -483 97 -339 362 -588 707 -663 109 -23 335 -24 429
-1 276 69 504 254 625 508 54 112 72 183 78 307 l6 102 -112 0 -111 0 0 -49
c0 -307 -240 -593 -557 -665 -89 -20 -235 -20 -328 -1 -234 49 -442 244 -517
485 -20 64 -23 96 -23 220 0 124 3 156 23 220 32 100 64 161 128 247 145 190
353 284 600 270 165 -9 290 -59 400 -160 45 -41 53 -52 42 -63 -7 -6 -251
-173 -543 -369 -291 -196 -534 -361 -539 -365 -7 -7 68 -121 114 -173 5 -6
911 600 1282 858 17 11 14 18 -31 81 -98 140 -268 273 -431 337 -124 49 -300
71 -447 56z"/>
<path d="M11410 2960 c-150 -15 -311 -72 -427 -151 -169 -115 -299 -300 -355
-504 -18 -67 -21 -108 -22 -250 0 -145 3 -183 22 -260 86 -340 357 -589 709
-650 56 -10 152 -15 279 -15 l194 0 0 109 0 109 -222 5 c-248 5 -291 13 -418
75 -104 51 -222 168 -273 272 -60 122 -72 184 -71 355 0 134 3 158 27 230 15
44 43 109 64 144 54 90 166 192 267 241 118 58 186 70 424 70 l202 0 0 115 0
115 -162 -1 c-90 -1 -197 -5 -238 -9z"/>
<path d="M14600 2959 c-197 -23 -380 -116 -517 -263 -79 -84 -116 -138 -163
-239 -60 -127 -81 -221 -87 -382 -6 -164 10 -276 58 -408 62 -172 220 -363
378 -457 203 -121 487 -152 695 -76 79 28 204 102 270 158 l46 40 0 134 0 134
-77 -78 c-169 -169 -315 -230 -528 -219 -167 8 -286 65 -415 196 -143 145
-210 315 -210 532 0 216 77 401 225 544 202 195 472 238 720 116 176 -87 315
-278 359 -493 13 -64 16 -160 16 -573 l0 -495 105 0 105 0 0 514 c0 553 -3
589 -54 736 -66 192 -221 375 -408 483 -73 42 -217 86 -319 97 -104 11 -95 11
-199 -1z"/>
<path d="M6003 2263 c3 -603 5 -674 21 -727 64 -215 217 -366 427 -423 87 -23
271 -23 359 0 133 36 235 106 300 207 18 27 33 50 35 50 2 0 15 -20 29 -44 36
-61 126 -140 199 -175 93 -43 158 -56 292 -55 101 1 132 5 201 27 198 65 338
215 397 426 20 73 21 100 25 729 l3 652 -110 0 -111 0 0 -630 c0 -421 -4 -646
-11 -682 -31 -148 -137 -261 -283 -303 -85 -25 -214 -17 -290 18 -73 34 -159
116 -190 181 -44 95 -46 124 -46 786 l0 630 -110 0 -110 0 0 -644 c0 -613 -1
-647 -20 -708 -36 -117 -102 -192 -218 -246 -51 -24 -69 -27 -167 -27 -103 0
-114 2 -175 31 -36 17 -84 48 -107 68 -59 51 -110 159 -123 256 -5 41 -10 344
-10 673 l0 597 -106 0 -105 0 4 -667z"/>
<path d="M16970 2372 c0 -326 4 -592 10 -641 23 -176 86 -313 202 -436 99
-104 225 -172 356 -190 l52 -7 0 -269 0 -269 115 0 115 0 0 269 0 269 67 12
c197 34 365 167 462 364 77 158 74 127 78 829 l4 627 -106 0 -105 0 0 -564 c0
-624 -3 -661 -61 -784 -39 -81 -140 -187 -216 -226 -154 -77 -350 -75 -492 6
-75 43 -160 133 -199 209 -62 125 -62 124 -62 771 l0 588 -110 0 -110 0 0
-558z"/>
<path d="M770 755 l0 -145 2300 0 2300 0 0 145 0 145 -2300 0 -2300 0 0 -145z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1920.000000pt" height="540.000000pt" viewBox="0 0 1920.000000 540.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.16, written by Peter Selinger 2001-2019
</metadata>
<g transform="translate(0.000000,540.000000) scale(0.100000,-0.100000)"
fill="#44a838" stroke="none">
<path d="M2710 4733 c-36 -63 -73 -129 -83 -146 l-19 -30 -73 73 -74 74 -23
-20 c-13 -11 -52 -46 -87 -77 l-63 -56 -77 59 c-42 33 -85 75 -95 95 -34 62
-126 115 -200 115 -139 0 -263 -140 -241 -273 15 -87 83 -172 159 -197 l36
-12 0 -531 0 -532 -73 -39 c-441 -240 -796 -650 -941 -1086 -75 -225 -79 -259
-83 -718 l-5 -412 2279 0 2279 0 14 178 c18 248 9 582 -20 717 -106 497 -421
930 -904 1244 -61 39 -114 71 -118 71 -4 0 -8 250 -8 555 l0 555 37 15 c60 25
99 61 127 117 44 87 32 184 -32 258 -51 60 -104 84 -182 84 -47 0 -76 -6 -106
-22 -78 -41 -134 -128 -134 -210 0 -31 -22 -53 -52 -52 -7 1 -53 64 -102 141
l-88 140 -70 -123 c-39 -68 -77 -134 -85 -147 l-15 -23 -72 71 -73 72 -112
-55 -113 -55 -54 34 c-30 19 -59 35 -65 35 -5 0 -46 -21 -90 -46 -45 -25 -85
-42 -89 -37 -5 4 -36 77 -71 161 -34 83 -65 152 -68 152 -3 0 -35 -53 -71
-117z m134 -108 c32 -80 59 -145 61 -145 1 0 42 22 91 50 49 27 96 49 104 49
8 0 38 -15 65 -33 l50 -33 110 54 109 54 81 -81 81 -80 79 140 c44 77 82 140
85 140 3 0 18 -21 35 -48 16 -26 53 -84 82 -129 l53 -82 37 16 c26 10 38 12
41 4 16 -47 72 -108 125 -135 l57 -29 0 -523 0 -524 -57 26 c-366 164 -699
238 -1078 237 -381 -1 -704 -67 -1057 -217 l-38 -16 0 509 0 509 34 12 c84 28
156 125 159 218 2 45 0 45 79 -16 l57 -44 84 75 83 76 80 -80 80 -80 44 78
c85 151 111 193 118 193 4 0 33 -66 66 -145z m-419 -2509 c149 -39 248 -151
247 -281 0 -67 -11 -102 -48 -156 -82 -120 -297 -177 -431 -115 -62 29 -123
90 -155 154 -34 69 -32 169 4 238 33 63 114 137 171 157 53 19 147 21 212 3z
m1546 -15 c122 -47 199 -150 199 -269 0 -78 -16 -118 -75 -180 -65 -70 -145
-105 -257 -110 -76 -4 -92 -1 -140 20 -157 73 -226 246 -155 390 33 67 109
140 170 162 59 22 185 15 258 -13z"/>
<path d="M13340 4062 c-46 -81 -85 -149 -87 -151 -2 -3 -37 28 -78 69 l-75 74
-27 -25 c-16 -14 -54 -48 -86 -77 l-58 -52 -96 77 -96 76 -144 -71 c-79 -40
-145 -72 -147 -72 -2 0 -7 19 -10 43 -19 123 -144 220 -263 203 -84 -11 -166
-73 -197 -148 -38 -90 -5 -216 73 -278 l41 -33 0 -1284 0 -1283 110 0 110 0 0
578 c0 635 2 657 61 773 39 78 144 186 216 223 154 78 354 72 497 -14 74 -44
158 -137 197 -215 56 -116 59 -145 59 -772 l0 -573 105 0 106 0 -4 628 c-3
696 -1 671 -73 817 -87 179 -255 315 -453 366 -106 28 -287 28 -392 1 -92 -25
-195 -76 -264 -131 l-50 -40 -3 466 c-2 437 -1 468 15 477 33 18 83 80 95 116
10 33 20 40 156 105 80 39 151 70 157 69 6 -1 53 -34 103 -74 l92 -72 86 77
87 77 78 -78 c43 -43 80 -76 82 -73 2 2 38 64 79 137 42 74 78 130 81 125 3
-4 30 -71 62 -148 31 -77 58 -141 60 -143 2 -2 43 19 93 47 49 28 96 51 104
51 7 0 37 -16 65 -35 29 -19 55 -35 59 -35 4 0 54 27 111 61 l103 61 81 -77
82 -76 74 140 c41 78 77 141 81 141 4 0 44 -54 90 -120 46 -66 87 -120 92
-120 4 0 38 13 74 29 l65 30 38 -49 c21 -27 42 -49 47 -50 4 0 53 29 107 65
55 36 102 65 105 65 3 0 44 -30 91 -66 47 -36 87 -63 89 -61 2 2 30 66 62 142
31 76 62 140 67 141 6 2 52 -48 104 -111 52 -63 97 -113 100 -111 3 2 34 20
69 40 l62 38 65 -22 c72 -24 95 -41 95 -71 0 -34 38 -95 84 -136 l45 -40 3
-1079 c3 -994 5 -1084 21 -1138 55 -185 180 -302 365 -342 44 -9 132 -14 255
-14 l187 0 0 99 0 99 -197 4 c-231 5 -274 17 -343 96 -70 79 -70 75 -70 719
l0 573 305 0 305 0 0 105 0 105 -305 0 -305 0 0 391 0 391 44 39 c136 120 90
343 -85 404 -23 8 -56 15 -73 15 -96 0 -205 -79 -228 -165 -7 -25 -17 -45 -24
-45 -6 0 -41 11 -78 25 l-66 25 -57 -35 c-32 -19 -62 -35 -68 -35 -5 0 -57 57
-114 125 -58 69 -108 122 -111 118 -4 -5 -31 -66 -60 -138 -29 -71 -56 -138
-62 -148 -8 -15 -18 -10 -86 43 -43 33 -81 60 -85 60 -4 0 -52 -30 -107 -66
l-100 -67 -22 29 c-13 16 -32 39 -43 52 l-21 24 -62 -31 c-34 -17 -65 -31 -70
-31 -4 0 -50 61 -102 136 -80 114 -96 133 -106 119 -6 -8 -42 -74 -78 -145
-37 -72 -70 -130 -73 -130 -3 0 -38 30 -79 66 l-73 66 -101 -61 c-56 -34 -105
-61 -110 -61 -5 0 -34 16 -64 35 -30 19 -60 35 -66 35 -6 0 -47 -21 -91 -46
-45 -25 -84 -43 -87 -40 -3 4 -36 78 -72 166 l-66 159 -84 -147z"/>
<path d="M9393 2959 c-215 -21 -397 -106 -547 -256 -122 -122 -209 -274 -252
-443 -29 -114 -27 -376 4 -483 97 -339 362 -588 707 -663 109 -23 335 -24 429
-1 276 69 504 254 625 508 54 112 72 183 78 307 l6 102 -112 0 -111 0 0 -49
c0 -307 -240 -593 -557 -665 -89 -20 -235 -20 -328 -1 -234 49 -442 244 -517
485 -20 64 -23 96 -23 220 0 124 3 156 23 220 32 100 64 161 128 247 145 190
353 284 600 270 165 -9 290 -59 400 -160 45 -41 53 -52 42 -63 -7 -6 -251
-173 -543 -369 -291 -196 -534 -361 -539 -365 -7 -7 68 -121 114 -173 5 -6
911 600 1282 858 17 11 14 18 -31 81 -98 140 -268 273 -431 337 -124 49 -300
71 -447 56z"/>
<path d="M11410 2960 c-150 -15 -311 -72 -427 -151 -169 -115 -299 -300 -355
-504 -18 -67 -21 -108 -22 -250 0 -145 3 -183 22 -260 86 -340 357 -589 709
-650 56 -10 152 -15 279 -15 l194 0 0 109 0 109 -222 5 c-248 5 -291 13 -418
75 -104 51 -222 168 -273 272 -60 122 -72 184 -71 355 0 134 3 158 27 230 15
44 43 109 64 144 54 90 166 192 267 241 118 58 186 70 424 70 l202 0 0 115 0
115 -162 -1 c-90 -1 -197 -5 -238 -9z"/>
<path d="M14600 2959 c-197 -23 -380 -116 -517 -263 -79 -84 -116 -138 -163
-239 -60 -127 -81 -221 -87 -382 -6 -164 10 -276 58 -408 62 -172 220 -363
378 -457 203 -121 487 -152 695 -76 79 28 204 102 270 158 l46 40 0 134 0 134
-77 -78 c-169 -169 -315 -230 -528 -219 -167 8 -286 65 -415 196 -143 145
-210 315 -210 532 0 216 77 401 225 544 202 195 472 238 720 116 176 -87 315
-278 359 -493 13 -64 16 -160 16 -573 l0 -495 105 0 105 0 0 514 c0 553 -3
589 -54 736 -66 192 -221 375 -408 483 -73 42 -217 86 -319 97 -104 11 -95 11
-199 -1z"/>
<path d="M6003 2263 c3 -603 5 -674 21 -727 64 -215 217 -366 427 -423 87 -23
271 -23 359 0 133 36 235 106 300 207 18 27 33 50 35 50 2 0 15 -20 29 -44 36
-61 126 -140 199 -175 93 -43 158 -56 292 -55 101 1 132 5 201 27 198 65 338
215 397 426 20 73 21 100 25 729 l3 652 -110 0 -111 0 0 -630 c0 -421 -4 -646
-11 -682 -31 -148 -137 -261 -283 -303 -85 -25 -214 -17 -290 18 -73 34 -159
116 -190 181 -44 95 -46 124 -46 786 l0 630 -110 0 -110 0 0 -644 c0 -613 -1
-647 -20 -708 -36 -117 -102 -192 -218 -246 -51 -24 -69 -27 -167 -27 -103 0
-114 2 -175 31 -36 17 -84 48 -107 68 -59 51 -110 159 -123 256 -5 41 -10 344
-10 673 l0 597 -106 0 -105 0 4 -667z"/>
<path d="M16970 2372 c0 -326 4 -592 10 -641 23 -176 86 -313 202 -436 99
-104 225 -172 356 -190 l52 -7 0 -269 0 -269 115 0 115 0 0 269 0 269 67 12
c197 34 365 167 462 364 77 158 74 127 78 829 l4 627 -106 0 -105 0 0 -564 c0
-624 -3 -661 -61 -784 -39 -81 -140 -187 -216 -226 -154 -77 -350 -75 -492 6
-75 43 -160 133 -199 209 -62 125 -62 124 -62 771 l0 588 -110 0 -110 0 0
-558z"/>
<path d="M770 755 l0 -145 2300 0 2300 0 0 145 0 145 -2300 0 -2300 0 0 -145z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

+2215
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
# EXAMPLES
The Wechaty Official Example List Directory has been move to:
<https://github.com/wechaty/getting-started/tree/master/examples>
Please use [wechaty-getting-started](https://github.com/wechaty/getting-started) repository as the starter template for your project because it can work out-of-the-box with lots of examples.
+205
View File
@@ -0,0 +1,205 @@
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {
WechatyBuilder,
ScanStatus,
Message,
Contact,
} from '../src/mods/mod.js' // from 'wechaty'
import qrTerm from 'qrcode-terminal'
import { FileBox } from 'file-box'
/**
*
* 1. Declare your Bot!
*
*/
const options = {
name : 'ding-dong-bot',
/**
* You can specify different puppet for different IM protocols.
* Learn more from https://wechaty.js.org/docs/puppet-providers/
*/
// puppet: 'wechaty-puppet-whatsapp'
/**
* You can use wechaty puppet provider 'wechaty-puppet-service'
* which can connect to Wechaty Puppet Services
* for using more powerful protocol.
* Learn more about services (and TOKEN)from https://wechaty.js.org/docs/puppet-services/
*/
// puppet: 'wechaty-puppet-service'
// puppetOptions: {
// token: 'xxx',
// }
}
const bot = WechatyBuilder.build(options)
/**
*
* 2. Register event handlers for Bot
*
*/
bot
.on('logout', onLogout)
.on('login', onLogin)
.on('scan', onScan)
.on('error', onError)
.on('message', onMessage)
/**
*
* 3. Start the bot!
*
*/
.start()
.catch(async e => {
console.error('Bot start() fail:', e)
await bot.stop()
process.exit(-1)
})
/**
*
* 4. You are all set. ;-]
*
*/
/**
*
* 5. Define Event Handler Functions for:
* `scan`, `login`, `logout`, `error`, and `message`
*
*/
function onScan (qrcode: string, status: ScanStatus) {
if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) {
qrTerm.generate(qrcode)
const qrcodeImageUrl = [
'https://wechaty.js.org/qrcode/',
encodeURIComponent(qrcode),
].join('')
console.info('onScan: %s(%s) - %s', ScanStatus[status], status, qrcodeImageUrl)
} else {
console.info('onScan: %s(%s)', ScanStatus[status], status)
}
// console.info(`[${ScanStatus[status]}(${status})] ${qrcodeImageUrl}\nScan QR Code above to log in: `)
}
function onLogin (user: Contact) {
console.info(`${user.name()} login`)
}
function onLogout (user: Contact) {
console.info(`${user.name()} logged out`)
}
function onError (e: Error) {
console.error('Bot error:', e)
/*
if (bot.isLoggedIn) {
bot.say('Wechaty error: ' + e.message).catch(console.error)
}
*/
}
/**
*
* 6. The most important handler is for:
* dealing with Messages.
*
*/
async function onMessage (msg: Message) {
console.info(msg.toString())
if (msg.self()) {
console.info('Message discarded because its outgoing')
return
}
if (msg.age() > 2 * 60) {
console.info('Message discarded because its TOO OLD(than 2 minutes)')
return
}
if (msg.type() !== bot.Message.Type.Text
|| !/^(ding|ping|bing|code)$/i.test(msg.text())
) {
console.info('Message discarded because it does not match ding/ping/bing/code')
return
}
/**
* 1. reply 'dong'
*/
await msg.say('dong')
console.info('REPLY: dong')
/**
* 2. reply image(qrcode image)
*/
const fileBox = FileBox.fromUrl('https://wechaty.github.io/wechaty/images/bot-qr-code.png')
await msg.say(fileBox)
console.info('REPLY: %s', fileBox.toString())
/**
* 3. reply 'scan now!'
*/
await msg.say([
'Join Wechaty Developers Community\n\n',
'Scan now, because other Wechaty developers want to talk with you too!\n\n',
'(secret code: wechaty)',
].join(''))
}
/**
*
* 7. Output the Welcome Message
*
*/
const welcome = `
| __ __ _ _
| \\ \\ / /__ ___| |__ __ _| |_ _ _
| \\ \\ /\\ / / _ \\/ __| '_ \\ / _\` | __| | | |
| \\ V V / __/ (__| | | | (_| | |_| |_| |
| \\_/\\_/ \\___|\\___|_| |_|\\__,_|\\__|\\__, |
| |___/
=============== Powered by Wechaty ===============
-------- https://github.com/wechaty/wechaty --------
Version: ${bot.version()}
I'm a bot, my superpower is talk in Wechat.
If you send me a 'ding', I will reply you a 'dong'!
__________________________________________________
Hope you like it, and you are very welcome to
upgrade me to more superpowers!
Please wait... I'm trying to login in...
`
console.info(welcome)
+92
View File
@@ -0,0 +1,92 @@
import PUPPET from 'wechaty-puppet'
import { FileBox } from 'file-box'
import {
WechatyBuilder,
} from '../src/mods/mod.js'
const bot = WechatyBuilder.build({
name : 'video-post-bot',
})
async function testVideoPost () {
const contact = await bot.Contact.find({ id: 'xxx' })
const room = await bot.Room.find({ id: 'xxx' })
if (!contact || !room) {
return
}
const post = await bot.Post.builder()
.add('hello, world')
.add(FileBox.fromQRCode('qr'))
.add(await bot.UrlLink.create('https://wechaty.js.org'))
.build()
await bot.say(post)
bot.on('message', async message => {
// if (message.type() !== message.type.Post) {
// return
// }
// forward video post
await message.forward(contact)
/**
* Video Post
*/
const post2 = await message.toPost()
const counter = post2.counter()
console.info('total tap(like) number:', counter.taps && counter.taps[PUPPET.types.Tap.Like])
console.info('total children number:', counter.descendant)
/**
* Comment
*/
// reply comment
await post2.reply('xxxx')
await post2.reply(FileBox.fromQRCode('qrimage'))
for await (const descendant of post.descendants({ contactId: message.wechaty.currentUser.id })) {
console.info(descendant)
}
/**
* Like
*/
// like message
await post.like(true)
// await post.tap(PostTapType.Like, true)
// check whether we have liked this post
const liked = await post.like()
console.info('liked date:', liked)
// const liked = await post.tap(PostTapType.Like)
// cancel like
await post.like(false)
// await post.tap(PostTapType.Like, false)
// list all likers
for await (const tap of post.taps()) {
console.info('Taper -------')
console.info('taper:', tap.contact)
console.info('date:', tap.date)
console.info('type:', tap.type)
}
for await (const like of post.likes()) {
console.info('-------')
console.info('liker:', like.contact)
console.info('date:', like.date)
console.info('type:', like.type)
}
})
}
void testVideoPost()
+177
View File
@@ -0,0 +1,177 @@
{
"name": "wechaty",
"version": "2.0.0-alpha.1",
"description": "Wechaty is a RPA SDK for Chatbot Makers.",
"type": "module",
"exports": {
".": {
"import": "./dist/esm/src/mods/mod.js",
"require": "./dist/cjs/src/mods/mod.js"
},
"./impls": {
"import": "./dist/esm/src/mods/impls.js",
"require": "./dist/cjs/src/mods/impls.js"
},
"./users": {
"import": "./dist/esm/src/mods/users.js",
"require": "./dist/cjs/src/mods/users.js"
}
},
"typesVersions": {
"*": {
"users": [
"./dist/esm/src/mods/users.d.ts"
],
"impls": [
"./dist/esm/src/mods/impls.d.ts"
]
}
},
"types": "./dist/esm/src/mods/mod.d.ts",
"engines": {
"node": ">=16",
"npm": ">=7"
},
"wechaty": {
"DEFAULT_PORT": 8080,
"DEFAULT_PROTOCOL": "io|0.0.1",
"DEFAULT_APIHOST": "api.chatie.io"
},
"scripts": {
"build": "tsc && tsc -p tsconfig.cjs.json",
"changelog": "docker run -it --rm -e CHANGELOG_GITHUB_TOKEN -v \"$(pwd)\":/usr/local/src/your-app ferrarimarco/github-changelog-generator -u wechaty -p wechaty && sed -i'.bak' /greenkeeper/d CHANGELOG.md && sed -i'.bak' /Snyk/d CHANGELOG.md && sed -i'.bak' '/An in-range update of/d' CHANGELOG.md && node --no-warnings --loader=ts-node/esm scripts/sort-contributiveness.ts < CHANGELOG.md > CHANGELOG.new.md 2>/dev/null && cat CHANGELOG.md >> CHANGELOG.new.md && mv CHANGELOG.new.md CHANGELOG.md",
"clean": "shx rm -fr dist/*",
"coverage": "nyc report --reporter=lcov",
"cli": "node --no-warnings --loader ts-node/esm bin/cli.ts",
"demo": "cross-env NODE_OPTIONS=\"--no-warnings --loader=ts-node/esm\" node examples/ding-dong-bot.ts",
"dist": "npm-run-all clean build dist:commonjs",
"dist:commonjs": "jq -n \"{ type: \\\"commonjs\\\" }\" > dist/cjs/package.json",
"docs": "bash -x scripts/generate-docs.sh",
"doctor": "node --no-warnings --loader=ts-node/esm bin/doctor.ts",
"io-client": "node --no-warnings --loader=ts-node/esm bin/io-client.ts",
"lint:es": "eslint --ignore-pattern node_modules/ --ignore-pattern fixtures/ \"{bin,examples,src,scripts,tests}/**/*.ts\"",
"lint:md": "markdownlint README.md",
"lint:sh": "bash -n bin/*.sh",
"lint:ts": "tsc --isolatedModules --noEmit",
"lint": "npm-run-all lint:es lint:ts lint:sh lint:md",
"puppet-install": "node --no-warnings --loader=ts-node/esm bin/puppet-install.ts",
"sloc": "sloc bin examples scripts src tests --details --format cli-table --keys total,source,comment && sloc bin examples scripts src tests",
"start": "npm run demo",
"test:pack": "bash -x scripts/npm-pack-testing.sh",
"test:shell": "shellcheck bin/*.sh scripts/*.sh",
"test:unit": "cross-env NODE_OPTIONS=\"--no-warnings --loader=ts-node/esm\" TAP_TIMEOUT=600 tap \"src/**/*.spec.ts\" \"tests/**/*.spec.ts\"",
"test": "npm run lint && nyc npm run test:unit",
"typedoc": "bash scripts/typedoc.sh"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wechaty/wechaty.git"
},
"bin": {
"wechaty": "dist/esm/bin/cli.js",
"wechaty-doctor": "dist/esm/bin/doctor.js",
"wechaty-io-client": "dist/esm/bin/io-client.js",
"wechaty-puppet-install": "dist/esm/bin/puppet-install.js",
"wechaty-version": "dist/esm/bin/version.js"
},
"keywords": [
"ai",
"bot",
"chatbot",
"chatie",
"conversational",
"framework",
"robotics",
"robot",
"rpa",
"sdk",
"wechat",
"wecom",
"wxwork",
"lark",
"TikTok",
"weixin",
"whatsapp",
"微信"
],
"author": {
"name": "Huan LI (李卓桓)",
"email": "zixia@zixia.net",
"url": "https://linkedin.com/in/zixia/"
},
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/wechaty/wechaty/issues"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/wechaty"
},
"homepage": "https://github.com/wechaty/",
"dependencies": {
"clone-class": "^1.1.1",
"cmd-ts": "^0.10.0",
"cockatiel": "^2.0.2",
"cross-spawn": "^7.0.3",
"dotenv": "^16.0.0",
"file-box": "^1.4.15",
"fp-ts": "^2.11.9",
"gerror": "^1.0.16",
"get-port": "^6.1.2",
"json-rpc-peer": "^0.17.0",
"memory-card": "^1.1.2",
"open-graph": "^0.2.6",
"rx-queue": "^1.0.5",
"state-switch": "^1.6.3",
"uuid": "^8.3.2",
"wechaty-puppet": "^1.20.1",
"wechaty-puppet-service": "^1.19.8",
"wechaty-puppet-wechat4u": "^1.13.15",
"wechaty-token": "^1.1.1",
"ws": "^8.5.0"
},
"devDependencies": {
"@chatie/eslint-config": "^1.0.4",
"@chatie/git-scripts": "^0.6.2",
"@chatie/semver": "^0.6.0",
"@chatie/tsconfig": "^4.6.3",
"@types/cross-spawn": "^6.0.2",
"@types/dotenv": "^8.2.0",
"@types/glob": "^7.2.0",
"@types/open-graph": "^0.2.2",
"@types/qrcode-terminal": "^0.12.0",
"@types/raven": "^2.5.4",
"@types/uuid": "^8.3.4",
"@types/ws": "^8.5.3",
"coveralls": "^3.1.1",
"glob": "^7.2.0",
"jsdoc-to-markdown": "^7.1.1",
"nyc": "^15.1.0",
"qrcode-terminal": "^0.12.0",
"raven": "^2.6.4",
"regenerator-runtime": "^0.13.9",
"sloc": "^0.2.1",
"typed-emitter": "^1.5.0-from-event",
"typedoc": "^1.0.0-dev.4 ",
"wechaty-puppet-mock": "^1.18.2",
"wechaty-puppet-padlocal": "^1.11.13"
},
"files": [
"bin/",
"dist/",
"src/"
],
"publishConfig": {
"access": "public",
"tag": "next"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/wechaty"
},
"git": {
"scripts": {
"pre-push": "npx git-scripts-pre-push"
}
}
}
+108
View File
@@ -0,0 +1,108 @@
#!/bin/bash -x
set -eo pipefail
export ARTIFACT_IMAGE='wechaty/wechaty:artifact'
function dockerBuild () {
platform=$1 # `all` for all platforms, `linux/amd64`, `linux/arm64`, etc.
tag=${2} # set the tag for building image
deploy=$3 # `true` for deploy, not set for local build
# Shellcheck - https://github.com/koalaman/shellcheck/wiki/SC2086
options=('--rm')
[ -n "$NO_CACHE" ] && options+=('--no-cache')
[ "$platform" == "all" ] && platform='linux/amd64,linux/arm64,linux/arm/v7'
[ -n "$deploy" ] && options+=('--push')
cat <<_CMD_
docker buildx build \
"${options[@]}" \
--platform "$platform" \
--tag "wechaty/wechaty:$tag" \
.
_CMD_
docker buildx build \
"${options[@]}" \
--platform "$platform" \
--tag "wechaty/wechaty:$tag" \
.
}
function deployOnbuild () {
export tag=$1
echo "Building & Deploying 'wechaty/onbuild:$tag' based on 'wechaty/wechaty:$tag'"
sed "s#FROM wechaty/wechaty:next#FROM wechaty/wechaty:$tag#" < Dockerfile.onbuild \
| docker buildx build \
--platform 'linux/amd64,linux/arm64,linux/arm/v7' \
--tag "wechaty/onbuild:$tag" \
--push \
-
}
function main () {
declare -i ret=0
case "$1" in
build | '')
docker build \
--rm \
--tag "$ARTIFACT_IMAGE" \
.
ret=$?
;;
test)
echo "Testing the docker image behaviors to make sure it works as expected..."
echo "bats tests/"
IMAGE_NAME="$ARTIFACT_IMAGE" bats tests/
echo
echo
echo docker run -i --rm -v /dev/shm:/dev/shm "$ARTIFACT_IMAGE" test
exec docker run -i --rm -v /dev/shm:/dev/shm "$ARTIFACT_IMAGE" test
ret=$?
;;
deploy)
version=$(npx pkg-jq -r .version)
majorVer=$(echo "$version" | cut -d. -f1)
minorVer=$(echo "$version" | cut -d. -f2)
versionTag="${majorVer}.${minorVer}"
dockerBuild all "$versionTag" deploy
deployOnbuild "$versionTag"
if npx --package @chatie/semver semver-is-prod "$version"; then
dockerBuild all latest deploy
deployOnbuild latest
else
dockerBuild all next deploy
deployOnbuild next
fi
;;
clean)
docker ps -a | grep Exited | awk '{print $1}' | xargs docker rm || true
docker images | grep none | awk '{print $3}' | xargs docker rmi
;;
*)
echo docker run -ti --rm -v /dev/shm:/dev/shm "$ARTIFACT_IMAGE" "$@"
exec docker run -ti --rm -v /dev/shm:/dev/shm "$ARTIFACT_IMAGE" "$@"
;;
esac
[ "$ret" -ne 0 ] && {
echo "ERROR: exec return $ret ???"
}
return $ret
}
main "$@"
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -e
VERSION=$(npx pkg-jq -r .version)
echo "Version $VERSION"
if ! npx --package @chatie/semver semver-is-prod "$VERSION"; then
echo "Current release is a development release, please only update the docs when there's a stable release."
if [ "$1" != "-f" ]; then
echo "Or use -f to force generating."
exit 1
else
echo "-f found. Forcing generating ..."
fi
fi
echo "Generating docs ..."
npm run dist
DOCS_INDEX_MD=docs/index.md
cat <<_EOF_ > "$DOCS_INDEX_MD"
# Wechaty v$(jq -r .version package.json) Documentation
- Website - <https://wechaty.js.org>
- Docs Site - <https://wechaty.js.org/docs/>
- API References - <https://wechaty.github.io/wechaty/>
_EOF_
jsdoc2md \
dist/src/wechaty.js \
dist/src/user/{room,contact,contact-self,friendship,message,room-invitation}.js \
>> "$DOCS_INDEX_MD"
#
# https://sidecar.gitter.im/
#
cat <<_EOF_ >> "$DOCS_INDEX_MD"
<script>
((window.gitter = {}).chat = {}).options = {
room: 'chatie/wechaty'
};
</script>
<script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
_EOF_
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -e
SRC_PACKAGE_JSON_TS_FILE='src/package-json.ts'
[ -f ${SRC_PACKAGE_JSON_TS_FILE} ] || {
echo ${SRC_PACKAGE_JSON_TS_FILE}" not found"
exit 1
}
GIT_COMMIT_HASH=$(git rev-parse HEAD)
cat <<_SRC_ > ${SRC_PACKAGE_JSON_TS_FILE}
/* eslint-disable */
/**
* This file was auto generated from scripts/generate-version.sh
*/
import type { PackageJsonWechaty } from './config'
export const GIT_COMMIT_HASH: string = '${GIT_COMMIT_HASH}'
export const packageJson: PackageJsonWechaty = $(cat package.json) as any
_SRC_
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
[ -z "$1" ] && {
echo
echo "Usage: git-push-tag.sh <tag>"
echo
exit 1
}
NO_HOOK=1 git push origin "$1"
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
[ "$(command -v google-chrome)" = "" ] && {
[ "$(grep chrome /etc/apt/sources.list.d/*)" -gt 0 ] && {
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
}
sudo apt-get update
sudo apt-get install -y google-chrome-stable
}
google-chrome --version
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -e
# https://docs.npmjs.com/cli/deprecate
# $ npm deprecate <pkg>[@<version>] <message>
# npm semver calculator
# - https://semver.npmjs.com
if [ $# -eq 0 ]; then
echo
echo "Usage:"
echo
echo " deprecate all the 0.15.* versions."
echo " \$ $0 0.15"
echo " \$ $0 0.15.*"
echo " \$ $0 ^0.15.1"
echo
else
message="${2:-WARNING: this version(odd number) is coming from a developing branch which means it is very possible UNSTABLE. Please use the latest version with even number if you are in production. You can know more about the Wechaty version numbering design at https://github.com/wechaty/wechaty/issues/905}"
npm deprecate "wechaty@$1" "$message"
fi
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -e
VERSION=$(npx pkg-jq -r .version)
if npx --package @chatie/semver semver-is-prod "$VERSION"; then
NPM_TAG=latest
else
NPM_TAG=next
fi
npm run dist
npm pack
TMPDIR="/tmp/npm-pack-testing.$$"
mkdir "$TMPDIR"
# trap "rm -fr $TMPDIR" EXIT
mv ./*-*.*.*.tgz "$TMPDIR"
cp tests/fixtures/smoke-testing.ts "$TMPDIR"
cd $TMPDIR
npm init -y
npm install --production ./*-*.*.*.tgz \
@types/node \
typescript@latest \
pkg-jq \
file-box@"$NPM_TAG" \
wechaty-puppet-mock@"$NPM_TAG" \
wechaty-puppet-padlocal@"$NPM_TAG" \
wechaty-puppet-service@"$NPM_TAG" \
#
# CommonJS
#
npx tsc \
--target esnext \
--module CommonJS \
\
--moduleResolution node \
--esModuleInterop \
--lib esnext \
--noEmitOnError \
--noImplicitAny \
--skipLibCheck \
smoke-testing.ts
echo
echo "CommonJS: pack testing..."
node smoke-testing.js
#
# ES Modules
#
npx pkg-jq -i '.type="module"'
npx tsc \
--target esnext \
--module esnext \
\
--moduleResolution node \
--esModuleInterop \
--lib esnext \
--noEmitOnError \
--noImplicitAny \
--skipLibCheck \
smoke-testing.ts
echo
echo "ES Module: pack testing..."
node smoke-testing.js
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
#
# Author: Huan LI <zixia@zixia.net>
# https://github.com/zixia
# License: Apache-2.0
#
[ -z "$1" ] && {
echo
echo "Usage: npm-set-latest.sh <version>"
echo
exit 1
}
npm dist-tag add "wechaty@$1" latest
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
VERSION=$(npx pkg-jq -r .version)
if npx --package @chatie/semver semver-is-prod "$VERSION"; then
npx pkg-jq -i '.publishConfig.tag="latest"'
echo "production release: publicConfig.tag set to latest."
else
npx pkg-jq -i '.publishConfig.tag="next"'
echo 'development release: publicConfig.tag set to next.'
fi
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { spawn } from 'child_process'
const MAX_RETRY_NUM = 3
async function main (): Promise<number> {
console.info('Safe Test: starting...')
let round = 0
let succ = false
do {
console.info(`Safe Test: running for round #${round}`)
succ = await unitTest()
if (succ) { // success!
console.info(`Safe Test: successed at round #${round}!`)
return 0
}
} while (round++ < MAX_RETRY_NUM)
return 1 // fail finally :(
}
async function unitTest () {
const child = spawn(
'npm',
[
'run',
'test:unit',
],
{
shell: true, // https://stackoverflow.com/a/39682805/1123955
stdio: 'inherit',
},
)
return new Promise<boolean>((resolve, reject) => {
child.once('exit', code =>
code === 0 ? resolve(true) : resolve(false),
)
child.once('error', reject)
})
}
main()
.then(process.exit)
.catch(e => {
console.error(e)
process.exit(1)
})
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import readline from 'readline'
const contributeMap: {
[contributor: string]: string[],
} = {}
function parseLine (line: string): string[] | null {
// [\#264](https://github.com/wechaty/wechaty/pull/264) ([lijiarui](https://github.com/lijiarui))
// const regex = /(\[\\#\d+\]\([^\)]+\))\s+(\(\[[^]]+\]\([^)]+\)))/i
const regex = /(\[\\#\d+\])(\([^)]+\))\s+\((\[[^\]]+\]\([^)]+\))/
const matches = regex.exec(line)
if (!matches) {
return null
}
// console.info('match!')
// console.info(matches[1]) // [\#264]
// console.info(matches[2]) // (https://github.com/wechaty/wechaty/pull/264)
// console.info(matches[3]) // ([lijiarui](https://github.com/lijiarui)
return matches
}
function processLine (line: string): void {
const matches = parseLine(line)
if (matches) {
// console.info('match:', line)
// console.info(matches)
const link = matches[1]! + matches[2]
const contributor = matches[3]!
// console.info('link:', link)
// console.info('contributor:', contributor)
if (!(contributor in contributeMap)) {
contributeMap[contributor] = []
}
contributeMap[contributor]!.push(link)
// console.info(contributiveness)
} else {
console.error('NO match:', line)
}
}
function outputContributorMd () {
const MIN_MAINTAINER_COMMIT_NUM = 2
function isMaintainer (committer: string): boolean {
return contributeMap[committer]!.length >= MIN_MAINTAINER_COMMIT_NUM
}
const activeContributorList = Object
.keys(contributeMap)
.filter(isMaintainer)
.sort(desc)
function desc (committerA: string, committerB: string): number {
return contributeMap[committerB]!.length - contributeMap[committerA]!.length
}
console.info([
'',
'# CHANGELOG',
'',
'## WECHATY CONTRIBUTORS',
'### Active Contributors',
'',
].join('\n'))
for (const contributor of activeContributorList) {
console.info(`1. @${contributor}: ${contributeMap[contributor]!.join(' ')}`)
}
console.info([
'',
'### Contributors',
'',
].join('\n'))
const SKIP_NAME_LIST = [
'snyk-bot',
'gitter-badger',
]
const SKIP_REGEX = new RegExp(SKIP_NAME_LIST.join('|'), 'i')
for (const contributor of Object.keys(contributeMap).sort(desc)) {
if (SKIP_REGEX.test(contributor)) {
continue
}
if (!activeContributorList.includes(contributor)) {
console.info(`1. @${contributor}: ${contributeMap[contributor]!.join(' ')}`)
}
}
console.info()
}
async function main () {
// https://stackoverflow.com/a/20087094/1123955
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
})
rl.on('line', processLine)
await new Promise(resolve => rl.on('close', resolve))
outputContributorMd()
return 0
}
main()
.then(process.exit)
.catch(e => {
console.error(e)
process.exit(1)
})
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
typedoc \
--exclude \"src/*.spec.ts\" \
--excludeExternals \
--excludePrivate \
--excludeProtected \
--externalPattern ./**/lib/** \
--mode modules \
--module commonjs \
--target ES6 \
--name "Wechaty Documentation" \
--out dist/docs/ \
src/
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {
createReadStream,
createWriteStream,
promises as fsPromises,
// link as linkCallback,
// unlink as unlinkCallback,
} from 'fs'
import {
Transform,
// TransformOptions,
} from 'stream'
import { promisify } from 'util'
import globCallback from 'glob'
const LICENSE = `/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/`
class LicenseTransformer extends Transform {
private lineBuf = ''
private lineNum = 0
private updating = false
private updated = false
// constructor (options?: TransformOptions) {
// super(options)
// }
override _transform (chunk: any, _: string /* encoding: string */, done: () => void) {
if (this.updated) {
this.push(chunk)
} else {
const updatedChunk = this.updateChunk(chunk)
this.push(updatedChunk)
}
done()
}
private updateChunk (chunk: any): string {
const buffer = this.lineBuf + chunk.toString()
this.lineBuf = ''
if (!buffer) {
console.error('no data')
return ''
}
const updatedLineList: string[] = []
buffer
.split(/\n/)
.forEach(line => {
if (this.lineNum === 0 && line.startsWith('#!')) {
updatedLineList.push(line)
} else if (this.updated) {
updatedLineList.push(line)
} else if (this.updating) {
if (/\*\//.test(line)) {
updatedLineList.push(line.replace(/.*\*\//, LICENSE))
this.updating = false
this.updated = true
} else {
// drop the old comments
}
} else { // not updating and not updated. searching...
if (!line) {
updatedLineList.push(line)
} else if (/\s*\/\*\*/.test(line)) { // comment start
if (/\*\//.test(line)) { // comment end at the same line with start
updatedLineList.push(line.replace(/\/\*\*.*\*\//, LICENSE))
this.updated = true
} else {
this.updating = true
}
} else { // not a comment. INSERT here
updatedLineList.push(LICENSE)
updatedLineList.push(line)
this.updated = true
}
}
this.lineBuf = line
this.lineNum++
})
return updatedLineList.join('\n')
}
override _flush (done: () => void) {
if (this.lineBuf) {
this.push(this.lineBuf)
this.lineBuf = ''
}
done()
}
}
async function updateLicense (file: string): Promise<void> {
const tmpFile = file + `.${process.pid}.tmp`
const readStream = createReadStream(file)
const writeStream = createWriteStream(tmpFile)
const tranStream = new LicenseTransformer()
console.info(`Updating LICENSE for file ${file}...`)
await new Promise<void>((resolve, reject) => {
readStream
.pipe(tranStream)
.pipe(writeStream)
.on('close', resolve)
.on('error', reject)
})
// await promisify(unlinkCallback)
await fsPromises.unlink(file)
// await promisify(linkCallback)(tmpFile, file)
await fsPromises.link(tmpFile, file)
// await promisify(unlinkCallback)(tmpFile)
await fsPromises.unlink(tmpFile)
}
async function glob (pattern: string): Promise<string[]> {
return promisify<string, string[]>(globCallback as any)(pattern)
}
async function main (): Promise<number> {
const pattern = '{bin/**/*.ts,examples/**/*.{js,ts},scripts/**/*.{ts,js},src/**/*.{ts,js},tests/**/*.ts}'
// const pattern = 't.ts'
const srcFileList = await glob(pattern)
const promiseList = srcFileList.map(updateLicense)
await Promise.all(promiseList)
return 0
}
main()
.then(process.exit)
.catch(e => {
console.error(e)
process.exit(1)
})
+50
View File
@@ -0,0 +1,50 @@
# Language setting http://docs.shippable.com/ci/languages/node/
language: python
python:
- 3.6
# use this to control what branches get built.
# http://docs.shippable.com/ci/advancedOptions/branches/
branches:
only:
- master
- v*.*
env:
global:
- secure: x0+gSTZx9QXVhhnEu+7iRXBEE0O/nGTXAvSJVgc+wx/qPocyB2CQqAf7+UTq3jGsrcdZYKGe8MCgxFOmT2abcv8TQn02LT9LQVsSBo9I72U4tisJcpcbFad4XkjwUXHOMZfndubGgy58GfDTNl/x1v7DB/Olhwu6nlJRYnedj22l8kdEKBpabMmrnogrWesoKKpYbkhR9Vi3XHeZ3FA5hu14wbhq8A2jpcGqh35bDpViAL7f447nPdn20SAFGOrqF3yC/Ws8keG0ChQ2IhQ4cx/ucpGmswUw+ZLJEUCQL0ZlUgkXAAUVeAUAMMywE+yWYLnfDRqnVshRdb6+lHz6VA==
build:
pre_ci_boot:
options: "-v /root/src:/root/src"
# http://docs.shippable.com/ci/shippableyml/#ci
ci:
- curl -sSL -o /tmp/bats_v0.4.0.tar.gz https://github.com/sstephenson/bats/archive/v0.4.0.tar.gz
- tar -xf /tmp/bats_v0.4.0.tar.gz
- sudo bats-0.4.0/install.sh /usr/local
- pwd
- ls -l
- docker run -v "$(pwd)":/mnt nlknguyen/alpine-shellcheck bin/*.sh
- ./scripts/docker.sh build
- ./scripts/docker.sh test
on_success:
- echo $BRANCH
- echo $IS_PULL_REQUEST
- if [ "$BRANCH" == "master" ] && [ "$IS_PULL_REQUEST" == false ]; then curl -X POST -d '{"from":"shippable"}' "$DOCKER_REBUILD_URL"; fi
# Integrations are used to connect external resources to CI
# http://docs.shippable.com/integrations/overview/
integrations:
# http://docs.shippable.com/ci/shippableyml/#notifications
notifications:
# turning of email for PR builds, get notified only on failure and change in status
# http://docs.shippable.com/integrations/notifications/email/
- integrationName: email
type: email
on_success: change
on_failure: change
on_pull_request: never
+20
View File
@@ -0,0 +1,20 @@
/* eslint-disable sort-keys */
import {
command,
} from 'cmd-ts'
async function handler (_args: any) {
console.info('this command can help you contact Friday BOT and Talk to him.')
console.info('Not implemented yet, please stay tuned and contribute is welcome!')
console.info('GitHub repo(source code): https://github.com/wechaty/wechaty/blob/main/src/cli/friday-bot.ts')
}
const friday = command({
name: 'friday',
description: 'Contact Friday BOT and talk to him!',
args: {
},
handler,
})
export { friday }
+190
View File
@@ -0,0 +1,190 @@
/* eslint-disable sort-keys */
import {
command,
option,
optional,
string,
number,
} from 'cmd-ts'
import {
log,
} from 'wechaty-puppet'
import { WechatyToken } from 'wechaty-token'
import getPort from 'get-port'
import {
VERSION,
} from '../config.js'
import {
IoClient,
IoClientOptions,
} from '../io-client.js'
import { WechatyBuilder } from '../wechaty-builder.js'
import {
isPuppetModuleName,
OFFICIAL_PUPPET_DEPENDENCIES,
OFFICIAL_PUPPET_DEFAULT,
} from '../puppet-config.js'
async function onError (
this : IoClient,
e : Error,
) {
log.error('Gateway', 'Error: %s', e)
await this.quit()
process.exit(-1)
}
const gateway = command({
name: 'gateway',
description: 'Publish your DIY token as a Wechaty Puppet Service. (you can generate a token by running `wechaty token generate`)',
args: {
token: option({
description: 'Specify a TOKEN for your DIY Wechaty Puppet Service (WPS). It is the standard token service for TypeScript and Polyglot Wechaty (like Python, Rust, Go, etc.). If it not specified, it will be generated automatically.',
long: 'token',
short: 't',
type: optional(string),
}),
port: option({
description: 'Specify a Port for your DIY Wechaty Puppet Service (WPS). If it not specified, it will select a free port automatically. This port MUST be public accessiable via the internet.',
long: 'port',
short: 'p',
type: optional(number),
}),
puppet: option({
description: 'Specify a Wechaty Puppet Provider (WPP). If it not specified, it will use the environment variable `WECHATY_PUPPET`.',
long: 'puppet',
short: 'P',
type: optional(string),
}),
puppetToken: option({
description: 'Specify the WEchaty Puppet Provider TOKEN. If it not specified, it will use the environment variable `WECHATY_PUPPET_XXX_TOKEN` (`XXX` is your WPP name).',
long: 'puppet-token',
short: 'T',
type: optional(string),
}),
},
handler: async args => {
log.info('Gateway', 'Wechaty version %s', VERSION)
let {
token,
port,
puppet,
puppetToken,
} = args
/**
* Token
*/
if (!token) {
log.info('Gateway', 'no `--token` specified, generating random Wechaty Puppet Service TOKEN ...')
token = WechatyToken.generate('insecure')
}
log.info('Gateway', 'Wechaty Puppet Service TOKEN: %s', token)
/**
* Puppet Service Server Port
*/
if (!port) {
log.info('Gateway', 'no `--port` specified, selecting random free Wechaty Puppet Service Server Port ...')
port = await getPort({
port: 8788,
})
}
log.info('Gateway', 'Wechaty Puppet Service Server Port: %d', port)
/**
* Puppet Provider
*/
if (!puppet) {
log.info('Gateway', 'no `--puppet` specified, reading from environment variable `WECHATY_PUPPET` for Wechaty Puppet Provider ...')
puppet = process.env['WECHATY_PUPPET']
if (!puppet) {
log.info('Gateway', `environment variable "WECHATY_PUPPET" is not available. Using system default puppet "${OFFICIAL_PUPPET_DEFAULT}"`)
puppet = OFFICIAL_PUPPET_DEFAULT
}
}
if (!isPuppetModuleName(puppet)) {
console.error(`The puppet name "${puppet}" is not a valid puppet name.`)
console.error(`Valid puppet names are: ${Object.keys(OFFICIAL_PUPPET_DEPENDENCIES).join(', ')}`)
return 1
}
log.info('Gateway', 'Wechaty Puppet Provider: %s', puppet)
/**
* Puppet Token
*/
if (!puppetToken) {
log.info('Gateway', 'no `--puppet-token` specified, reading from environment variable `WECHATY_PUPPET_TOKEN` for your Wechaty Puppet Provider ...')
puppetToken = process.env['WECHATY_PUPPET_TOKEN']
if (!puppetToken) {
log.info('Gateway', 'WECHATY_PUPPET_TOKEN environment variable not available. Token disabled.')
}
}
log.info('Gateway', 'Wechaty Puppet Provider Token: %s', puppetToken ? `"${puppetToken}"` : 'disabled')
const wechaty = WechatyBuilder.build({
name: 'Gateway',
puppet,
puppetOptions: {
token: puppetToken,
},
})
const options: IoClientOptions = {
port,
token,
wechaty,
}
const client = new IoClient(options)
client.start()
.catch(onError.bind(client))
const wechatyToken = new WechatyToken(token)
let address
while (true) {
log.info('Gateway', 'Registering Wechaty Puppet Token Gateway Service Discovery ...')
await new Promise(resolve => setTimeout(resolve, 1000))
address = await wechatyToken.discover()
if (address) {
log.info('Gateway', 'Registering Wechaty Puppet Token Gateway Service Discovery ... success')
break
}
log.info('Gateway', 'Registering Wechaty Puppet Token Gateway Service Discovery ... timeout')
}
console.info(`
Your have successfully ran Wechaty Token Gateway with the below settings:
1. token: ${token}
2. port: ${port}
3. puppet: ${puppet}
4. puppetToken: ${puppetToken || ''}
The Wechaty Puppet Token Gateway Service Address is ${address.host}:${address.port}, please make sure it can be public accessed via the internet.
You can save the below command to restore the token gateway in future:
$ wechaty gateway --token ${token} --port ${port} --puppet ${puppet} --puppet-token ${puppetToken || ''}
You can set the below environment variables for your Wechaty program to use the DIY token provided by this gateway:
__________________________________________________________________________
$ export WECHATY_PUPPET=wechaty-puppet-service
$ export WECHATY_PUPPET_SERVICE_TOKEN=${token}
$ npm start
Please file an issue if you found any bug or have feature request at https://github.com/wechaty/wechaty/issues
Join the Wechaty Community Gitter Channel at https://gitter.im/wechaty/wechaty if you have any questions.
`)
return 0
},
})
export { gateway }
+5
View File
@@ -0,0 +1,5 @@
export { friday } from './friday.js'
export { gateway } from './gateway.js'
export { provider } from './provider.js'
export { service } from './service.js'
export { token } from './token.js'
+20
View File
@@ -0,0 +1,20 @@
/* eslint-disable sort-keys */
import {
command,
} from 'cmd-ts'
async function handler (_args: any) {
console.info('this command can help you manage Wechaty Puppet Provider (WPP).')
console.info('Not implemented yet, please stay tuned and contribute is welcome!')
console.info('GitHub repo(source code): https://github.com/wechaty/wechaty/blob/main/src/cli/puppet-provider.ts')
}
const provider = command({
name: 'provider',
description: 'Manage Wechaty Puppet Provider (WPP)',
args: {
},
handler,
})
export { provider }
+20
View File
@@ -0,0 +1,20 @@
/* eslint-disable sort-keys */
import {
command,
} from 'cmd-ts'
async function handler (_args: any) {
console.info('this command can help you manage Wechaty Puppet Servivce (WPS).')
console.info('Not implemented yet, please stay tuned and contribute is welcome!')
console.info('GitHub repo(source code): https://github.com/wechaty/wechaty/blob/main/src/cli/puppet-service.ts')
}
const service = command({
name: 'service',
description: 'Manage Wechaty Puppet Service (WPS)',
args: {
},
handler,
})
export { service }
+19
View File
@@ -0,0 +1,19 @@
/* eslint-disable sort-keys */
import {
subcommands,
} from 'cmd-ts'
import { cmds } from 'wechaty-token'
import {
VERSION,
} from '../config.js'
const token = subcommands({
name: 'Token',
description: 'Generate & Discover TOKEN for Wechaty',
version: VERSION,
cmds,
})
export { token }
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { test } from 'tstest'
import { config } from './config.js'
// import { Puppet } from './puppet'
test('important variables', async t => {
// t.ok('puppet' in config, 'should exist `puppet` in Config')
t.ok('apihost' in config, 'should exist `apihost` in Config')
t.ok('token' in config, 'should exist `token` in Config')
// t.ok(config.default.DEFAULT_PUPPET , 'should export DEFAULT_PUPPET')
// t.ok(config.default.DEFAULT_PROFILE , 'should export DEFAULT_PROFILE')
t.ok(config.default.DEFAULT_PROTOCOL, 'should export DEFAULT_PROTOCOL')
t.ok(config.default.DEFAULT_APIHOST, 'should export DEFAULT_APIHOST')
})
test('validApiHost()', async t => {
const OK_APIHOSTS = [
'api.chatie.io',
'chatie.io:8080',
]
const ERR_APIHOSTS = [
'https://api.chatie.io',
'chatie.io/',
]
OK_APIHOSTS.forEach(apihost => {
t.doesNotThrow(() => {
config.validApiHost(apihost)
})
}, 'should not row for right apihost')
ERR_APIHOSTS.forEach(apihost => {
t.throws(() => {
config.validApiHost(apihost)
})
}, 'should throw for error apihost')
})
// test('puppetInstance()', async t => {
// // BUG Compitable with Win32 CI
// // global instance infected across unit tests... :(
// const bak = config.puppetInstance()
// config.puppetInstance(null)
// t.throws(() => {
// config.puppetInstance()
// }, Error, 'should throw when not initialized')
// config.puppetInstance(bak)
// const EXPECTED: Puppet = {userId: 'test'} as any
// const mockPuppet = EXPECTED
// config.puppetInstance(mockPuppet)
// const instance = config.puppetInstance()
// t.same(instance, EXPECTED, 'should equal with initialized data')
// config.puppetInstance(null)
// t.throws(() => {
// config.puppetInstance()
// }, Error, 'should throw after set to null')
// config.puppetInstance(bak)
// })
test('systemPuppetName ()', async t => {
const WECHATY_PUPPET_ORIG = process.env['WECHATY_PUPPET']
delete process.env['WECHATY_PUPPET']
t.equal(config.systemPuppetName(), 'wechaty-puppet-wechat4u', 'should get wechaty-puppet-wechat4u as default puppet name')
process.env['WECHATY_PUPPET'] = 'wechaty-puppet-mock'
t.equal(config.systemPuppetName(), 'wechaty-puppet-mock', 'should get puppet name from process.env')
// restore the original value
process.env['WECHATY_PUPPET'] = WECHATY_PUPPET_ORIG
})
+165
View File
@@ -0,0 +1,165 @@
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/// <reference path="./io-peer/json-rpc-peer.d.ts" />
import {
log,
} from 'wechaty-puppet'
import { FileBox } from 'file-box'
import type {
FileBoxInterface,
} from 'file-box'
import type {
PackageJson,
} from 'type-fest'
import {
OfficialPuppetNpmName,
OFFICIAL_PUPPET_DEFAULT,
} from './puppet-config.js'
import {
packageJson,
GIT_COMMIT_HASH,
} from './package-json.js'
type PackageJsonWechaty = PackageJson & {
wechaty: {
DEFAULT_PORT: number
DEFAULT_PROTOCOL: string
DEFAULT_APIHOST: string
}
}
const VERSION = packageJson.version || '0.0.0'
/**
* to handle unhandled exceptions
*/
if (log.level() === 'verbose' || log.level() === 'silly') {
log.info('Config', 'registering process.on("unhandledRejection") for development/debug')
/**
* Refer to https://nodejs.org/api/process.html#process_event_unhandledrejection
* the reason is in type: Error | any
*/
process.on('unhandledRejection', (reason: Error | any, promise) => {
log.error('Config', '###########################')
log.error('Config', 'Wechaty unhandledRejection: %s %s', reason.stack || reason, promise)
log.error('Config', '###########################')
promise.catch(err => {
log.error('Config', 'process.on(unhandledRejection) promise.catch(%s)', err.message)
console.error('Config', err) // I don't know if log.error has similar full trace print support like console.error
})
})
process.on('uncaughtException', function (error) {
const origin = arguments[1] // to compatible with node 12 or below version typings
log.error('Config', '###########################')
log.error('Config', 'Wechaty uncaughtException: %s %s', error.stack, origin)
log.error('Config', '###########################')
})
}
export interface DefaultSetting {
DEFAULT_PORT : number,
DEFAULT_APIHOST : string,
DEFAULT_PROTOCOL : string,
}
const DEFAULT_SETTING = packageJson['wechaty'] as DefaultSetting
export class Config {
default = DEFAULT_SETTING
apihost = process.env['WECHATY_APIHOST'] || DEFAULT_SETTING['DEFAULT_APIHOST']
serviceIp = process.env['WECHATY_PUPPET_SERVICE_IP'] || ''
systemPuppetName (): OfficialPuppetNpmName {
return (
process.env['WECHATY_PUPPET'] || OFFICIAL_PUPPET_DEFAULT
).toLowerCase() as OfficialPuppetNpmName
}
name = process.env['WECHATY_NAME']
// DO NOT set DEFAULT, because sometimes user do not want to connect to io cloud service
token = process.env['WECHATY_TOKEN']
debug = !!(process.env['WECHATY_DEBUG'])
httpPort = process.env['PORT']
|| process.env['WECHATY_PORT']
|| DEFAULT_SETTING['DEFAULT_PORT']
docker = !!(process.env['WECHATY_DOCKER'])
constructor () {
log.verbose('Config', 'constructor()')
this.validApiHost(this.apihost)
}
validApiHost (apihost: string): boolean {
if (/^[a-zA-Z0-9.\-_]+:?[0-9]*$/.test(apihost)) {
return true
}
throw new Error('validApiHost() fail for ' + apihost)
}
}
export const CHATIE_OFFICIAL_ACCOUNT_ID = 'gh_051c89260e5d'
export function qrCodeForChatie (): FileBoxInterface {
const CHATIE_OFFICIAL_ACCOUNT_QRCODE = 'http://weixin.qq.com/r/qymXj7DEO_1ErfTs93y5'
return FileBox.fromQRCode(CHATIE_OFFICIAL_ACCOUNT_QRCODE)
}
// http://jkorpela.fi/chars/spaces.html
// String.fromCharCode(8197)
export const FOUR_PER_EM_SPACE = String.fromCharCode(0x2005)
// mobile: \u2005, PC、mac: \u0020
export const AT_SEPARATOR_REGEX = /[\u2005\u0020]/
export function qrcodeValueToImageUrl (qrcodeValue: string): string {
return [
'https://wechaty.js.org/qrcode/',
encodeURIComponent(qrcodeValue),
].join('')
}
export function isProduction (): boolean {
return process.env['NODE_ENV'] === 'production'
|| process.env['NODE_ENV'] === 'prod'
}
const config = new Config()
export type {
PackageJsonWechaty,
}
export {
log,
config,
GIT_COMMIT_HASH,
VERSION,
}
+92
View File
@@ -0,0 +1,92 @@
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {
createServer,
Socket,
} from 'net'
import { spawnSync as spawn } from 'child_process'
import {
log,
} from 'wechaty-puppet'
export class Doctor {
constructor () {
log.verbose('Doctor', 'constructor()')
}
public chromedriverVersion (): string {
let version: string
try {
const cmd = spawn('chromedriver', [ '--version' ])
version = String(cmd.error) || cmd.stdout.toString() || cmd.stderr.toString()
} catch (e) {
version = (e as Error).message
}
return version
}
/**
* https://gist.github.com/tedmiston/5935757
*/
public testTcp (): Promise<boolean> {
log.verbose('Doctor', 'testTcp()')
return new Promise<boolean>((resolve, reject) => {
/**
* Server
*/
const server = createServer(socket => socket.pipe(socket))
/**
* Promise Reject
*/
server.on('error', reject)
server.on('close', () => log.silly('Doctor', 'testTcp() server closed'))
server.listen(8788, 'localhost', () => {
/**
* Client
*/
const client = new Socket()
client.connect(8788, 'localhost', () => {
log.silly('Doctor', 'testTcp() client connected')
client.write('ding')
})
client.on('data', () => {
/**
* Promise Resolve
*/
resolve(true)
client.destroy() // kill client after server's response
})
/**
* Promise Reject
*/
client.on('error', reject)
client.on('close', () => server.close())
})
})
}
}
+34
View File
@@ -0,0 +1,34 @@
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import og from 'open-graph'
export async function openGraph (url: string): Promise<og.Data> {
return new Promise((resolve, reject) => {
og(url, (err, meta) => {
if (err) {
reject(err)
} else if (!meta) {
reject(new Error('meta is undefined'))
} else {
resolve(meta)
}
})
})
}
+13
View File
@@ -0,0 +1,13 @@
// https://stackoverflow.com/a/38251445/1123955
function stringifyFilter (_key: string, value: any) {
if (value instanceof RegExp) {
return value.toString()
}
return value
}
export {
stringifyFilter,
}
+276
View File
@@ -0,0 +1,276 @@
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { StateSwitch } from 'state-switch'
import {
PuppetServer,
PuppetServerOptions,
} from 'wechaty-puppet-service'
import type { MessageInterface } from './user-modules/mod.js'
import type { WechatyInterface } from './wechaty/mod.js'
import {
log,
} from './config.js'
import { Io } from './io.js'
export interface IoClientOptions {
token : string,
wechaty : WechatyInterface,
port?: number
}
const DEFAULT_IO_CLIENT_OPTIONS: Partial<IoClientOptions> = {
port: 8788,
}
export class IoClient {
/**
* Huan(20161026): keep io `null-able` or not?
* Huan(202002): make it optional.
*/
private io?: Io
private puppetServer?: PuppetServer
private state: StateSwitch
protected options: Required<IoClientOptions>
constructor (
options: IoClientOptions,
) {
log.verbose('IoClient', 'constructor({%s})',
Object.keys(options)
.map(key => {
return `${key}:${(options as any)[key]}`
})
.reduce((acc, cur) => `${acc}, ${cur}`),
)
const normalizedOptions = {
...DEFAULT_IO_CLIENT_OPTIONS,
...options,
} as Required<IoClientOptions>
this.options = normalizedOptions
this.state = new StateSwitch('IoClient', { log })
}
private async startPuppetServer () {
log.verbose('IoClient', 'startPuppetServer()')
if (this.puppetServer) {
throw new Error('puppet server exists')
}
const options: PuppetServerOptions = {
endpoint : '0.0.0.0:' + this.options.port,
/**
* Huan(202110): FIXME: remove the any
* by updating the puppet-service server code
* to use PuppetInterface
*/
puppet : this.options.wechaty.puppet as any,
token : this.options.token,
}
this.puppetServer = new PuppetServer(options)
await this.puppetServer.start()
}
private async stopPuppetServer () {
log.verbose('IoClient', 'stopPuppetServer()')
if (!this.puppetServer) {
log.error('IoClient', 'stopPuppetServer() this.puppetServer is `undefined`')
return
}
await this.puppetServer.stop()
this.puppetServer = undefined
}
public async start (): Promise<void> {
log.verbose('IoClient', 'start()')
if (this.state.active()) {
log.warn('IoClient', 'start() with a on state, wait and return')
await this.state.stable('active')
return
}
this.state.active('pending')
try {
await this.hookWechaty(this.options.wechaty)
await this.startIo()
await this.options.wechaty.start()
await this.startPuppetServer()
this.state.active(true)
} catch (e) {
log.error('IoClient', 'start() exception: %s', (e as Error).message)
this.state.inactive(true)
throw e
}
}
private async hookWechaty (wechaty: WechatyInterface): Promise<void> {
log.verbose('IoClient', 'hookWechaty()')
if (this.state.inactive()) {
const e = new Error('state.off() is true, skipped')
log.warn('IoClient', 'initWechaty() %s', e.message)
throw e
}
wechaty
.on('login', user => log.info('IoClient', `${user.name()} logged in`))
.on('logout', user => log.info('IoClient', `${user.name()} logged out`))
.on('message', msg => this.onMessage(msg))
.on('scan', (url, code) => {
log.info('IoClient', [
`[${code}] ${url}`,
`Online QR Code Image: https://wechaty.js.org/qrcode/${encodeURIComponent(url)}`,
].join('\n'))
})
}
private async startIo (): Promise<void> {
log.verbose('IoClient', 'startIo() with token %s', this.options.token)
if (this.state.inactive()) {
const e = new Error('startIo() state.off() is true, skipped')
log.warn('IoClient', e.message)
throw e
}
if (this.io) {
throw new Error('io exists')
}
this.io = new Io({
servicePort : this.options.port,
token : this.options.token,
wechaty : this.options.wechaty,
})
try {
await this.io.start()
} catch (e) {
log.verbose('IoClient', 'startIo() init fail: %s', (e as Error).message)
throw e
}
}
private async stopIo () {
log.verbose('IoClient', 'stopIo()')
if (!this.io) {
log.warn('IoClient', 'stopIo() io does not exist')
return
}
await this.io.stop()
this.io = undefined
}
private async onMessage (msg: MessageInterface) {
log.verbose('IoClient', 'onMessage(%s)', msg)
// const from = m.from()
// const to = m.to()
// const content = m.toString()
// const room = m.room()
// log.info('Bot', '%s<%s>:%s'
// , (room ? '['+room.topic()+']' : '')
// , from.name()
// , m.toStringDigest()
// )
// if (/^wechaty|chatie|botie/i.test(m.text()) && !m.self()) {
// await m.say('https://www.chatie.io')
// .then(_ => log.info('Bot', 'REPLIED to magic word "chatie"'))
// }
}
public async stop (): Promise<void> {
log.verbose('IoClient', 'stop()')
this.state.inactive('pending')
await this.stopIo()
await this.stopPuppetServer()
await this.options.wechaty.stop()
this.state.inactive(true)
// XXX 20161026
// this.io = null
}
public async restart (): Promise<void> {
log.verbose('IoClient', 'restart()')
try {
await this.stop()
await this.start()
} catch (e) {
log.error('IoClient', 'restart() exception %s', (e as Error).message)
throw e
}
}
public async quit (): Promise<void> {
log.verbose('IoClient', 'quit()')
if (this.state.inactive() === 'pending') {
log.warn('IoClient', 'quit() with state.off() = `pending`, skipped')
throw new Error('quit() with state.off() = `pending`')
}
this.state.inactive('pending')
try {
// if (this.options.wechaty) {
await this.options.wechaty.stop()
// this.wechaty = null
// } else { log.warn('IoClient', 'quit() no this.wechaty') }
if (this.io) {
await this.io.stop()
// this.io = null
} else { log.warn('IoClient', 'quit() no this.io') }
} catch (e) {
log.error('IoClient', 'exception: %s', (e as Error).message)
throw e
} finally {
this.state.inactive(true)
}
}
}
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { test } from 'tstest'
import jsonRpcPeer from 'json-rpc-peer'
import {
getPeer,
} from './io-peer.js'
test('getPeer()', async t => {
const EXPECTED_PORT = 8788
const server = getPeer({
serviceGrpcPort: EXPECTED_PORT,
})
const client = new jsonRpcPeer.Peer()
/**
* FIXME: Huan(202108): remove `any` to fix the typing
*/
server.pipe(client as any).pipe(server)
/**
* Huan(202101) Need to be fixed by new IO Bus system.
* See: https://github.com/wechaty/wechaty-puppet-service/issues/118
*/
const port = await client.request('getHostieGrpcPort')
t.equal(port, EXPECTED_PORT, 'should get the right port')
})
test('exec()', async t => {
const EXPECTED_PORT = 8788
const server = getPeer({
serviceGrpcPort: EXPECTED_PORT,
})
/**
* Huan(202101) Need to be fixed by new IO Bus system.
* See: https://github.com/wechaty/wechaty-puppet-service/issues/118
*/
const request = jsonRpcPeer.format.request(42, 'getHostieGrpcPort')
const response = await server.exec(request) as string
// console.info('response: ', response)
const obj = jsonRpcPeer.parse(response) as jsonRpcPeer.JsonRpcPayloadResponse
t.equal(obj.result, EXPECTED_PORT, 'should get the right port from payload')
})
+79
View File
@@ -0,0 +1,79 @@
/// <reference path="json-rpc-peer.d.ts"/>
import jsonRpcPeer from 'json-rpc-peer'
// // https://stackoverflow.com/a/50375286/1123955
// type UnionToIntersection<U> =
// (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
// type UnknownJsonRpcPayload = Partial<UnionToIntersection<JsonRpcPayload>>
const isJsonRpcRequest = (payload: jsonRpcPeer.JsonRpcPayload): payload is jsonRpcPeer.JsonRpcPayloadRequest => ('method' in payload)
const isJsonRpcNotification = (payload: jsonRpcPeer.JsonRpcPayload): payload is jsonRpcPeer.JsonRpcPayloadNotification => isJsonRpcRequest(payload) && (!('id' in payload))
const isJsonRpcResponse = (payload: jsonRpcPeer.JsonRpcPayload): payload is jsonRpcPeer.JsonRpcPayloadResponse => ('result' in payload)
const isJsonRpcError = (payload: jsonRpcPeer.JsonRpcPayload): payload is jsonRpcPeer.JsonRpcPayloadError => ('error' in payload)
interface IoPeerOptions {
serviceGrpcPort: number,
}
const getPeer = (options: IoPeerOptions) => {
const getServiceGrpcPort = () => options.serviceGrpcPort
const serviceImpl = {
/**
* Huan(202101) Need to be fixed by new IO Bus system.
* See: https://github.com/wechaty/wechaty-puppet-service/issues/118
*/
getHostieGrpcPort: getServiceGrpcPort,
}
const onMessage = async (message: jsonRpcPeer.JsonRpcPayload): Promise<any> => {
if (isJsonRpcRequest(message)) {
const {
// id,
method,
// params,
} = message
if (!(method in serviceImpl)) {
console.error('serviceImpl does not contain method: ' + method)
return
}
const serviceMethodName = method as keyof typeof serviceImpl
switch (serviceMethodName) {
/**
* Huan(202101) Need to be fixed by new IO Bus system.
* See: https://github.com/wechaty/wechaty-puppet-service/issues/118
*/
case 'getHostieGrpcPort':
return serviceImpl[serviceMethodName]()
default:
throw new jsonRpcPeer.MethodNotFound(serviceMethodName)
}
} else if (isJsonRpcResponse(message)) {
// NOOP: we are server
} else if (isJsonRpcNotification(message)) {
// NOOP: we are server
} else if (isJsonRpcError(message)) {
// NOOP: we are server
} else {
throw new Error('unknown json-rpc message: ' + JSON.stringify(message))
}
console.info(JSON.stringify(message))
}
const ioPeer = new jsonRpcPeer.Peer(onMessage)
return ioPeer
}
export {
getPeer,
isJsonRpcError,
isJsonRpcNotification,
isJsonRpcRequest,
isJsonRpcResponse,
}
+68
View File
@@ -0,0 +1,68 @@
/**
* Add typescript definitions #60
* https://github.com/JsCommunity/json-rpc-peer/pull/60
*/
declare module 'json-rpc-peer' {
/// <reference types='json-rpc-protocol' />
/// <reference types='node' />
import { Writable } from 'stream'
import { EventEmitter } from 'events'
import {
JsonRpcPayload,
JsonRpcParamsSchema,
} from 'json-rpc-protocol'
// export as namespace JsonRpcPeer
export * from 'json-rpc-protocol'
export class Peer extends EventEmitter implements Writable {
constructor(onmessage?: (message: JsonRpcPayload, data: any) => Promise<any>)
public exec(
message: string | object,
data?: any
): Promise<undefined | string | JsonRpcPayload | JsonRpcPayload[]>
/**
* Fails all pending requests.
*/
public failPendingRequests(reason?: string)
/**
* This function should be called to send a request to the other end.
*/
public request(method: string, params?: JsonRpcParamsSchema): Promise<any>
/**
* This function should be called to send a notification to the other end.
*/
public notify(method: string, params?: JsonRpcParamsSchema)
public push(chunk: any, encoding?: string)
public pipe<T extends Writable>(writable: T): T
// NodeJS.WritableStream
writable: boolean
write(
buffer: Uint8Array | string,
cb?: (err?: Error | null) => void
): boolean
write(
str: string,
encoding?: string,
cb?: (err?: Error | null) => void
): boolean
end(cb?: () => void): void
end(data: string | Uint8Array, cb?: () => void): void
end(str: string, encoding?: string, cb?: () => void): void
}
}
Executable
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { test } from 'tstest'
import { WechatyBuilder } from './wechaty-builder.js'
import { Io } from './io.js'
test('Io restart without problem', async t => {
const io = new Io({
// token must not contain any white spaces
servicePort : 8788,
token : 'mock_00000000-0000-0000-0000-000000000000',
wechaty : WechatyBuilder.build(),
})
try {
for (let i = 0; i < 2; i++) {
await io.start()
await io.stop()
t.pass('start/stop-ed at #' + i)
}
t.pass('start/restart successed.')
} catch (e) {
t.fail(e as any)
}
})
+585
View File
@@ -0,0 +1,585 @@
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import WebSocket from 'ws'
import type * as PUPPET from 'wechaty-puppet'
import { StateSwitch } from 'state-switch'
import * as jsonRpcPeer from 'json-rpc-peer'
import type {
MessageInterface,
} from './user-modules/mod.js'
import type { WechatyInterface } from './wechaty/mod.js'
import {
log,
config,
} from './config.js'
import {
getPeer,
isJsonRpcRequest,
} from './io-peer/io-peer.js'
export interface IoOptions {
wechaty : WechatyInterface,
token : string,
apihost? : string,
protocol? : string,
servicePort? : number,
}
export const IO_EVENT_DICT = {
botie : 'tbw',
error : 'tbw',
heartbeat : 'tbw',
jsonrpc : 'JSON RPC',
login : 'tbw',
logout : 'tbw',
message : 'tbw',
raw : 'tbw',
reset : 'tbw',
scan : 'tbw',
shutdown : 'tbw',
sys : 'tbw',
update : 'tbw',
}
type IoEventName = keyof typeof IO_EVENT_DICT
interface IoEventScan {
name : 'scan',
payload : PUPPET.payloads.EventScan,
}
interface IoEventJsonRpc {
name : 'jsonrpc',
payload : jsonRpcPeer.JsonRpcPayload,
}
interface IoEventAny {
name: IoEventName,
payload: any,
}
type IoEvent = IoEventScan | IoEventJsonRpc | IoEventAny
/**
* https://github.com/Chatie/botie/issues/2
* https://github.com/actions/github-script/blob/f035cea4677903b153fa754aa8c2bba66f8dc3eb/src/async-function.ts#L6
*/
const AsyncFunction = Object.getPrototypeOf(async () => null).constructor
// function callAsyncFunction<U extends {} = {}, V = unknown> (
// args: U,
// source: string
// ): Promise<V> {
// const fn = new AsyncFunction(...Object.keys(args), source)
// return fn(...Object.values(args))
// }
export class Io {
protected readonly id : string
protected readonly protocol : string
protected eventBuffer : IoEvent[] = []
protected ws : undefined | WebSocket
protected readonly state = new StateSwitch('Io', { log })
protected reconnectTimer? : ReturnType<typeof setTimeout>
protected reconnectTimeout? : number
protected lifeTimer? : ReturnType<typeof setTimeout>
protected onMessage: undefined | Function
protected scanPayload?: PUPPET.payloads.EventScan
protected jsonRpc?: jsonRpcPeer.Peer
constructor (
protected options: IoOptions,
) {
options.apihost = options.apihost || config.apihost
options.protocol = options.protocol || config.default.DEFAULT_PROTOCOL
this.id = options.wechaty.id
this.protocol = options.protocol + '|' + options.wechaty.id + '|' + config.serviceIp + '|' + options.servicePort
log.verbose('Io', 'instantiated with apihost[%s], token[%s], protocol[%s], cuid[%s]',
options.apihost,
options.token,
options.protocol,
this.id,
)
if (options.servicePort) {
this.jsonRpc = getPeer({
serviceGrpcPort: this.options.servicePort!,
})
}
}
public toString () {
return `Io<${this.options.token}>`
}
private connected () {
return this.ws && this.ws.readyState === WebSocket.OPEN
}
public async start (): Promise<void> {
log.verbose('Io', 'start()')
if (this.lifeTimer) {
throw new Error('lifeTimer exist')
}
this.state.active('pending')
try {
this.initEventHook()
this.ws = await this.initWebSocket()
this.options.wechaty.on('login', () => { this.scanPayload = undefined })
this.options.wechaty.on('scan', (qrcode, status) => {
this.scanPayload = {
...this.scanPayload,
qrcode,
status,
}
})
this.lifeTimer = setInterval(() => {
if (this.ws && this.connected()) {
log.silly('Io', 'start() setInterval() ws.ping()')
// TODO: check 'pong' event on ws
this.ws.ping()
}
}, 1000 * 10)
this.state.active(true)
} catch (e) {
log.warn('Io', 'start() exception: %s', (e as Error).message)
this.state.inactive(true)
throw e
}
}
private initEventHook () {
log.verbose('Io', 'initEventHook()')
const wechaty = this.options.wechaty
wechaty.on('error', error => this.send({ name: 'error', payload: error }))
wechaty.on('heartbeat', data => this.send({ name: 'heartbeat', payload: { cuid: this.id, data } }))
wechaty.on('login', user => this.send({ name: 'login', payload: wechaty.puppet.contactPayload(user.id) }))
wechaty.on('logout', user => this.send({ name: 'logout', payload: wechaty.puppet.contactPayload(user.id) }))
wechaty.on('message', message => this.ioMessage(message))
// FIXME: payload schema need to be defined universal
// wechaty.on('scan', (url, code) => this.send({ name: 'scan', payload: { url, code } }))
wechaty.on('scan', (qrcode, status) => this.send({ name: 'scan', payload: { qrcode, status } } as IoEventScan))
}
private async initWebSocket (): Promise<WebSocket> {
log.verbose('Io', 'initWebSocket()')
// this.state.current('on', false)
// const auth = 'Basic ' + new Buffer(this.setting.token + ':X').toString('base64')
const auth = 'Token ' + this.options.token
const headers = { Authorization: auth }
if (!this.options.apihost) {
throw new Error('no apihost')
}
let endpoint = 'wss://' + this.options.apihost + '/v0/websocket'
// XXX quick and dirty: use no ssl for API_HOST other than official
// FIXME: use a configurable VARIABLE for the domain name at here:
if (!/api\.chatie\.io/.test(this.options.apihost)) {
endpoint = 'ws://' + this.options.apihost + '/v0/websocket'
}
const ws = this.ws = new WebSocket(endpoint, this.protocol, { headers })
ws.on('open', () => this.wsOnOpen(ws))
ws.on('message', data => this.wsOnMessage(data))
ws.on('error', e => this.wsOnError(e))
ws.on('close', (code, reason) => this.wsOnClose(ws, code, reason.toString()))
await new Promise((resolve, reject) => {
ws.once('open', resolve)
ws.once('error', reject)
ws.once('close', reject)
})
return ws
}
private wsOnOpen (ws: WebSocket): void {
if (this.protocol !== ws.protocol) {
log.error('Io', 'wsOnOpen() require protocol[%s] failed', this.protocol)
// XXX deal with error?
}
log.verbose('Io', 'wsOnOpen() connected with protocol [%s]', ws.protocol)
// this.currentState('connected')
// this.state.current('on')
// FIXME: how to keep alive???
// ws._socket.setKeepAlive(true, 100)
this.reconnectTimeout = undefined
const name = 'sys'
const payload = 'Wechaty version ' + this.options.wechaty.version() + ` with CUID: ${this.id}`
const initEvent: IoEvent = {
name,
payload,
}
this.send(initEvent).catch(console.error)
}
private wsOnMessage (data: WebSocket.Data): void {
log.silly('Io', 'wsOnMessage() ws.on(message): %s', data)
this.wsOnMessageAsync(data).catch(console.error)
}
private async wsOnMessageAsync (data: WebSocket.Data): Promise<void> {
log.silly('Io', 'wsOnMessageAsync() ws.on(message): %s', data)
// flags.binary will be set if a binary data is received.
// flags.masked will be set if the data was masked.
let payload: string
if (Array.isArray(data)) {
payload = data[0]?.toString() ?? 'NODATA'
} else {
payload = data.toString()
}
const ioEvent: IoEvent = {
name : 'raw',
payload : data,
}
try {
const obj = JSON.parse(payload)
ioEvent.name = obj.name
ioEvent.payload = obj.payload
} catch (e) {
log.verbose('Io', 'on(message) recv a non IoEvent data[%s]', data)
}
switch (ioEvent.name) {
case 'botie':
{
const payload = ioEvent.payload
const args = payload.args
const source = payload.source
try {
if (args[0] === 'message' && args.length === 1) {
const fn = new AsyncFunction(...args, source)
this.onMessage = fn
} else {
log.warn('Io', 'server pushed function is invalid. args: %s', JSON.stringify(args))
}
} catch (e) {
log.warn('Io', 'server pushed function exception: %s', e)
this.options.wechaty.emitError(e)
}
}
break
case 'reset':
log.verbose('Io', 'on(reset): %s', ioEvent.payload)
this.options.wechaty.emitError(
new Error(
'reset by server: '
+ JSON.stringify(ioEvent.payload),
),
)
await this.options.wechaty.reset()
break
case 'shutdown':
log.info('Io', 'on(shutdown): %s', ioEvent.payload)
process.exit(0)
// eslint-disable-next-line
break
case 'update':
log.verbose('Io', 'on(update): %s', ioEvent.payload)
{
const wechaty = this.options.wechaty
if (wechaty.isLoggedIn) {
const loginEvent: IoEvent = {
name : 'login',
payload : await wechaty.puppet.contactPayload(
wechaty.puppet.currentUserId,
),
}
await this.send(loginEvent)
}
if (this.scanPayload) {
const scanEvent: IoEventScan = {
name: 'scan',
payload: this.scanPayload,
}
await this.send(scanEvent)
}
}
break
case 'sys':
// do nothing
break
case 'logout':
log.info('Io', 'on(logout): %s', ioEvent.payload)
await this.options.wechaty.logout()
break
case 'jsonrpc':
log.info('Io', 'on(jsonrpc): %s', ioEvent.payload)
try {
const request = (ioEvent as IoEventJsonRpc).payload
if (!isJsonRpcRequest(request)) {
log.warn('Io', 'on(jsonrpc) payload is not a jsonrpc request: %s', JSON.stringify(request))
return
}
if (!this.jsonRpc) {
throw new Error('jsonRpc not initialized!')
}
const response = await this.jsonRpc.exec(request)
if (!response) {
log.warn('Io', 'on(jsonrpc) response is undefined.')
return
}
const payload = jsonRpcPeer.parse(response) as jsonRpcPeer.JsonRpcPayloadResponse
const jsonrpcEvent: IoEventJsonRpc = {
name: 'jsonrpc',
payload,
}
log.verbose('Io', 'on(jsonrpc) send(%s)', response)
await this.send(jsonrpcEvent)
} catch (e) {
log.error('Io', 'on(jsonrpc): %s', e)
}
break
default:
log.warn('Io', 'UNKNOWN on(%s): %s', ioEvent.name, ioEvent.payload)
break
}
}
// FIXME: it seems the parameter `e` might be `undefined`.
// @types/ws might has bug for `ws.on('error', e => this.wsOnError(e))`
private wsOnError (e?: Error) {
log.warn('Io', 'wsOnError() error event[%s]', e && e.message)
if (!e) {
return
}
if (!this.ws) {
log.error('Io', 'wsOnError() ws.on(error) this.ws is `undefined`', e.message)
return
}
if (this.ws.readyState === WebSocket.CONNECTING) {
log.error('Io', 'wsOnError() ws.on(error) ws.readyState is CONNECTING: %s', e.message)
return
}
if (this.ws.readyState === WebSocket.CLOSING) {
log.error('Io', 'wsOnError() ws.on(error) ws.readyState is CLOSING: %s', e.message)
return
}
this.options.wechaty.emitError(e)
// when `error`, there must have already a `close` event
// we should not call this.reconnect() again
//
// this.close()
// this.reconnect()
}
private wsOnClose (
ws : WebSocket,
code : number,
message : string,
): void {
if (this.state.active()) {
log.warn('Io', 'wsOnClose() close event[%d: %s]', code, message)
ws.close()
this.reconnect()
}
}
private reconnect () {
log.verbose('Io', 'reconnect()')
if (this.state.inactive()) {
log.warn('Io', 'reconnect() canceled because state.target() === offline')
return
}
if (this.connected()) {
log.warn('Io', 'reconnect() on a already connected io')
return
}
if (this.reconnectTimer) {
log.warn('Io', 'reconnect() on a already re-connecting io')
return
}
if (!this.reconnectTimeout) {
this.reconnectTimeout = 1
} else if (this.reconnectTimeout < 10 * 1000) {
this.reconnectTimeout *= 3
}
log.warn('Io', 'reconnect() will reconnect after %d s', Math.floor(this.reconnectTimeout / 1000))
this.reconnectTimer = setTimeout(() => {
this.reconnectTimer = undefined
this.initWebSocket().catch(console.error)
}, this.reconnectTimeout)// as any as NodeJS.Timer
}
private async send (ioEvent?: IoEvent): Promise<void> {
if (!this.ws) {
throw new Error('no ws')
}
const ws = this.ws
if (ioEvent) {
log.silly('Io', 'send(%s)', JSON.stringify(ioEvent))
this.eventBuffer.push(ioEvent)
} else { log.silly('Io', 'send()') }
if (!this.connected()) {
log.verbose('Io', 'send() without a connected websocket, eventBuffer.length = %d', this.eventBuffer.length)
return
}
const list: Array<Promise<any>> = []
while (this.eventBuffer.length) {
const data = JSON.stringify(
this.eventBuffer.shift(),
)
const p = new Promise<void>((resolve, reject) => ws.send(
data,
(err: undefined | Error) => {
if (err) {
reject(err)
} else {
resolve()
}
},
))
list.push(p)
}
try {
await Promise.all(list)
} catch (e) {
log.error('Io', 'send() exception: %s', (e as Error).stack)
throw e
}
}
public async stop (): Promise<void> {
log.verbose('Io', 'stop()')
if (!this.ws) {
throw new Error('no ws')
}
this.state.inactive('pending')
// try to send IoEvents in buffer
await this.send()
this.eventBuffer = []
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = undefined
}
if (this.lifeTimer) {
clearInterval(this.lifeTimer)
this.lifeTimer = undefined
}
this.ws.close()
await new Promise<void>(resolve => {
if (this.ws) {
this.ws.once('close', resolve)
} else {
resolve()
}
})
this.ws = undefined
this.state.inactive(true)
}
/**
*
* Prepare to be overwritten by server setting
*
*/
private async ioMessage (m: MessageInterface): Promise<void> {
log.silly('Io', 'ioMessage() is a nop function before be overwritten from cloud')
if (typeof this.onMessage === 'function') {
await this.onMessage(m)
}
}
protected async syncMessage (m: MessageInterface): Promise<void> {
log.silly('Io', 'syncMessage(%s)', m)
const messageEvent: IoEvent = {
name : 'message',
payload : await m.wechaty.puppet.messagePayload(m.id),
}
await this.send(messageEvent)
}
}
+5
View File
@@ -0,0 +1,5 @@
export {
messageToSayable,
payloadToSayableWechaty,
sayableToPayload,
} from '../sayable/mod.js'
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
import { test } from 'tstest'
import * as impls from './impls.js'
test('Export of the Framework', async t => {
t.ok(impls.ContactImpl, 'should export Contact')
t.ok(impls.FriendshipImpl, 'should export Friendship')
t.ok(impls.MessageImpl, 'should export Message')
t.ok(impls.RoomImpl, 'should export Room')
t.ok(impls.WechatyImpl, 'should export Wechaty')
})
+115
View File
@@ -0,0 +1,115 @@
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* Huan(202111): We export all names with specific postfix to make it easier to be recongnized
* with less misunderstandings.
*
* Postfixes:
* - Interface: type, the `interface
* - Constructor: type, the `Constructor<interface>`
* - Impl: value, the `class`
*
* - no postfix: value, the `class`
*/
export {
ContactImpl,
ContactSelfImpl,
FavoriteImpl,
FriendshipImpl,
ImageImpl,
LocationImpl,
MessageImpl,
MiniProgramImpl,
MomentImpl,
MoneyImpl,
PostImpl,
RoomImpl,
RoomInvitationImpl,
DelayImpl,
TagImpl,
UrlLinkImpl,
} from '../user-modules/mod.js'
// export {
// ContactImpl as Contact,
// ContactSelfImpl as ContactSelf,
// FavoriteImpl as Favorite,
// FriendshipImpl as Friendship,
// ImageImpl as Image,
// LocationImpl as Location,
// MessageImpl as Message,
// MiniProgramImpl as MiniProgram,
// MomentImpl as Moment,
// MoneyImpl as Money,
// PostImpl as Post,
// RoomImpl as Room,
// RoomInvitationImpl as RoomInvitation,
// DelayImpl as Delay,
// TagImpl as Tag,
// UrlLinkImpl as UrlLink,
// } from '../user-modules/mod.js'
export type {
ContactInterface,
ContactSelfInterface,
DelayInterface,
FavoriteInterface,
FriendshipInterface,
ImageInterface,
LocationInterface,
MessageInterface,
MiniProgramInterface,
MomentInterface,
MoneyInterface,
PostInterface,
RoomInterface,
RoomInvitationInterface,
TagInterface,
UrlLinkInterface,
} from '../user-modules/mod.js'
export type {
ContactConstructor,
ContactSelfConstructor,
DelayConstructor,
FavoriteConstructor,
FriendshipConstructor,
ImageConstructor,
LocationConstructor,
MessageConstructor,
MiniProgramConstructor,
MomentConstructor,
MoneyConstructor,
PostConstructor,
RoomConstructor,
RoomInvitationConstructor,
TagConstructor,
UrlLinkConstructor,
} from '../user-modules/mod.js'
export {
WechatyImpl,
} from '../wechaty/mod.js'
export type {
WechatyInterface,
WechatyConstructor,
} from '../wechaty/mod.js'
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
import { test } from 'tstest'
import * as WECHATY from './mod.js'
test('Export of the Framework', async t => {
t.ok(WECHATY.impls, 'should export impls.*')
t.ok(WECHATY.types, 'should export types.*')
t.ok(WECHATY.IoClient, 'should export IoClient')
t.ok(WECHATY.log, 'should export log')
})
test('Config setting', async t => {
t.ok(WECHATY.config, 'should export Config')
// t.ok(config.default.DEFAULT_PUPPET , 'should has DEFAULT_PUPPET')
})
+74
View File
@@ -0,0 +1,74 @@
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* We need to put `Wechaty` at the beginning of this file for import
* because we have circular dependencies between `Puppet` & `Wechaty`
*/
export type {
WechatyOptions,
WechatyInterface as Wechaty,
} from '../wechaty/mod.js'
export {
WechatyBuilder,
type BuilderInterface,
} from '../wechaty-builder.js'
export {
type SayableSayer,
type Sayable,
} from '../sayable/mod.js'
export * as types from 'wechaty-puppet/types'
export * as payloads from 'wechaty-puppet/payloads'
export * from './users.js'
export * as users from './users.js'
export * as impls from './impls.js'
export * as helpers from './helpers.js'
export {
ScanStatus,
} from 'wechaty-puppet/types'
export {
log,
config,
qrcodeValueToImageUrl,
VERSION,
} from '../config.js'
export type {
WechatyEventName,
} from '../schemas/mod.js'
export type {
OfficialPuppetNpmName,
PuppetModuleName, // DEPRECATED
} from '../puppet-config.js'
export type {
WechatyPlugin,
WechatyPluginUninstaller,
} from '../plugin.js'
export type {
IoClientOptions,
} from '../io-client.js'
export {
IoClient,
} from '../io-client.js'
+44
View File
@@ -0,0 +1,44 @@
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
* @copyright 2016 Huan LI (李卓桓) <https://github.com/huan>, and
* Wechaty Contributors <https://github.com/wechaty>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* Huan(202111): we export all `interface` from this file, as the most convenience class name.
*
* since they are `interface`s, so they are `type`-only.
*/
export type {
ContactInterface as Contact,
ContactSelfInterface as ContactSelf,
DelayInterface as Delay,
FavoriteInterface as Favorite,
FriendshipInterface as Friendship,
ImageInterface as Image,
LocationInterface as Location,
MessageInterface as Message,
MiniProgramInterface as MiniProgram,
MomentInterface as Moment,
MoneyInterface as Money,
PostInterface as Post,
RoomInterface as Room,
RoomInvitationInterface as RoomInvitation,
TagInterface as Tag,
UrlLinkInterface as UrlLink,
} from '../user-modules/mod.js'
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
import { test } from 'tstest'
import { packageJson } from './package-json.js'
test('Make sure the packageJson is fresh in source code', async t => {
const json = JSON.parse(JSON.stringify(packageJson))
/**
* Delete test data (keys), which will
* be overwrite
* by the real data before deploy
*/
delete json['wechaty']
const keyNum = Object.keys(json).length
t.equal(keyNum, 0, 'packageJson should be empty in source code, only updated before publish to NPM')
})

Some files were not shown because too many files have changed in this diff Show More