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
@@ -0,0 +1,8 @@
|
||||
languages:
|
||||
Ruby: false
|
||||
JavaScript: true
|
||||
PHP: false
|
||||
Python: false
|
||||
exclude_paths:
|
||||
- "/doc/"
|
||||
- "**/*.spec.ts"
|
||||
@@ -0,0 +1,6 @@
|
||||
dist
|
||||
docs
|
||||
node_modules
|
||||
*.memory-card.json
|
||||
t/
|
||||
t.*
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
const rules = {
|
||||
'no-use-before-define': 'off',
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extends: '@chatie',
|
||||
rules,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# https://help.github.com/articles/about-codeowners/
|
||||
#
|
||||
|
||||
/ @wechaty/contributors
|
||||
@@ -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
|
||||
@@ -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
|
||||
---
|
||||
|
||||
> Important:Please 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-XXX(padchat/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]
|
||||
@@ -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.
|
||||
@@ -0,0 +1,8 @@
|
||||
template: |
|
||||
## What’s Changed
|
||||
|
||||
$CHANGES
|
||||
|
||||
## Contributors
|
||||
|
||||
$CONTRIBUTORS
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Huan <zixia@zixia.net> https://github.com/huan
|
||||
Rui <ruiruibupt@gmail.com> https://github.com/lijiarui
|
||||
@@ -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:
|
||||
|
||||
[](https://github.com/wechaty/wechaty)
|
||||
|
||||
### Markdown
|
||||
|
||||
```markdown
|
||||
[](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
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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" ]
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,569 @@
|
||||
# Wechaty [](https://discord.gg/7q8NBZbQzt) [](https://www.npmjs.com/package/wechaty) [](https://github.com/wechaty/wechaty/actions?query=workflow%3ANPM) [](https://github.com/wechaty/wechaty/actions?query=workflow%3ADocker)
|
||||
|
||||
[](https://wechaty.js.org)
|
||||
|
||||
[Ship.Fail](https://ship.fail) [PreAngel](https://PreAngel.AI)
|
||||
[](https://www.npmjs.com/package/wechaty)
|
||||
[](https://github.com/wechaty/wechaty)
|
||||
[](https://hub.docker.com/r/wechaty/wechaty/)
|
||||
[](https://github.com/Chatie/tsconfig/issues/16)
|
||||
|
||||
[](https://wechaty.js.org/docs/polyglot/typescript/)
|
||||
[](https://wechaty.js.org/docs/polyglot/typescript/)
|
||||
[](https://wechaty.js.org/docs/polyglot/python/)
|
||||
[](https://wechaty.js.org/docs/polyglot/go/)
|
||||
[](https://wechaty.js.org/docs/polyglot/java/)
|
||||
[](https://wechaty.js.org/docs/polyglot/dotnet/)
|
||||
[](https://wechaty.js.org/docs/polyglot/php/)
|
||||
[](https://wechaty.js.org/docs/polyglot/rust/)
|
||||
[](https://wechaty.js.org/docs/polyglot/scala/)
|
||||
|
||||
[](https://wechaty.js.org/docs/puppet-providers/wechat)
|
||||
[](https://wechaty.js.org/docs/puppet-providers/whatsapp)
|
||||
|
||||
<!--
|
||||
[](https://wechaty.js.org/docs/puppet-providers/tiktok)
|
||||
[](https://wechaty.js.org/docs/puppet-providers/line)
|
||||
[](https://wechaty.js.org/docs/puppet-providers/telegram)
|
||||
[](https://gitter.im/wechaty/wechaty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://gitter.im/wechaty/wechaty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](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)
|
||||
> — <cite>@Gcaufy, Tencent Engineer, Author of [WePY](https://github.com/Tencent/wepy)</cite>
|
||||
>
|
||||
> "太好用,好用的想哭"
|
||||
> — <cite>@xinbenlv, Google Engineer, Founder of HaoShiYou.org</cite>
|
||||
>
|
||||
> ”好用到哭“——你们对得起这个评价! [link](https://github.com/bigbrother666sh/shezhangbujianle#%E8%87%B4%E6%95%AC)
|
||||
> — <cite>[@bigbrother666sh](https://github.com/bigbrother666sh), creator of《社长不见了》剧本杀 NPC DM</cite>
|
||||
>
|
||||
> "最好的微信开发库" [link](http://weibo.com/3296245513/Ec4iNp9Ld?type=comment)
|
||||
> — <cite>@Jarvis, Baidu Engineer</cite>
|
||||
>
|
||||
> "Wechaty让运营人员更多的时间思考如何进行活动策划、留存用户,商业变现" [link](http://mp.weixin.qq.com/s/dWHAj8XtiKG-1fIS5Og79g)
|
||||
> — <cite>@lijiarui, Founder & CEO of Juzi.BOT.</cite>
|
||||
>
|
||||
> "If you know js ... try Wechaty. It's easy to use."
|
||||
> — <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."
|
||||
> — <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
|
||||
|
||||
[](https://discord.gg/7q8NBZbQzt)
|
||||
[](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**.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
[](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
|
||||
|
||||
[](https://www.npmjs.com/package/wechaty)
|
||||
[](https://www.npmjs.com/package/wechaty?activeTab=versions)
|
||||
|
||||
[](https://www.npmjs.com/package/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
|
||||
|
||||
[](https://hub.docker.com/r/wechaty/wechaty/)
|
||||
[](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
|
||||
|
||||
[](https://github.com/wechaty/wechaty/actions?query=workflow%3ANPM)
|
||||
[](https://github.com/wechaty/wechaty/actions?query=workflow%3ADocker)
|
||||
[](https://coveralls.io/github/wechaty/wechaty?branch=main)
|
||||
|
||||
[](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
|
||||
|
||||
[](http://hits.dwyl.io/wechaty/wechaty)
|
||||
|
||||
## :sparkling_heart: POWERED BY WECHATY
|
||||
|
||||
[](https://wechaty.js.org)
|
||||
|
||||
### :sparkles: Wechaty Badge
|
||||
|
||||
```markdown
|
||||
[](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
|
||||
|
||||
[](https://starchart.cc/wechaty/wechaty)
|
||||
|
||||
## :two_hearts: Contributors
|
||||
|
||||
[](https://github.com/Wechaty/wechaty/issues)
|
||||
[](https://github.com/Wechaty/wechaty/pulls)
|
||||
[](https://opencollective.com/wechaty/)
|
||||
[](https://opencollective.com/wechaty/)
|
||||
|
||||
[](https://sourcerer.io/fame/huan/wechaty/wechaty/links/0)
|
||||
[](https://sourcerer.io/fame/huan/wechaty/wechaty/links/1)
|
||||
[](https://sourcerer.io/fame/huan/wechaty/wechaty/links/2)
|
||||
[](https://sourcerer.io/fame/huan/wechaty/wechaty/links/3)
|
||||
[](https://sourcerer.io/fame/huan/wechaty/wechaty/links/4)
|
||||
[](https://sourcerer.io/fame/huan/wechaty/wechaty/links/5)
|
||||
[](https://sourcerer.io/fame/huan/wechaty/wechaty/links/6)
|
||||
[](https://sourcerer.io/fame/huan/wechaty/wechaty/links/7)
|
||||
|
||||
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
|
||||
|
||||
-----
|
||||
|
||||
[](https://github.com/Wechaty/wechaty/graphs/contributors)
|
||||
|
||||
## :sunglasses: Backers
|
||||
|
||||
[](#sunglasses-backers)
|
||||
|
||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/wechaty#backer)]
|
||||
|
||||
[](https://opencollective.com/wechaty#backers)
|
||||
|
||||
## :smirk: Sponsors
|
||||
|
||||
[](#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)]
|
||||
|
||||
[](https://opencollective.com/wechaty/#sponsor)
|
||||
|
||||
## Multi-language Wechaty
|
||||
|
||||
[](https://github.com/wechaty/python-wechaty)
|
||||
[](https://github.com/wechaty/go-wechaty)
|
||||
[](https://github.com/wechaty/java-wechaty)
|
||||
[](https://github.com/wechaty/scala-wechaty)
|
||||
[](https://github.com/wechaty/php-wechaty)
|
||||
[](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)
|
||||
|
||||
[](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
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`wechaty/wechaty`
|
||||
- 原始仓库:https://github.com/wechaty/wechaty
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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}"
|
||||
@@ -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, ' ')
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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 "$@"
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-minimal
|
||||
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 129 KiB |
@@ -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 |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 39 KiB |
@@ -0,0 +1,6 @@
|
||||
__ __ _ _
|
||||
\ \ / /__ ___| |__ __ _| |_ _ _
|
||||
\ \ /\ / / _ \/ __| '_ \ / _` | __| | | |
|
||||
\ V V / __/ (__| | | | (_| | |_| |_| |
|
||||
\_/\_/ \___|\___|_| |_|\__,_|\__|\__, |
|
||||
|___/
|
||||
@@ -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 |
@@ -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 |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 "$@"
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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/
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -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'
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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')
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
messageToSayable,
|
||||
payloadToSayableWechaty,
|
||||
sayableToPayload,
|
||||
} from '../sayable/mod.js'
|
||||
@@ -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')
|
||||
})
|
||||
@@ -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'
|
||||
@@ -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')
|
||||
})
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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')
|
||||
})
|
||||