...
 
Commits (249)
version: 2
jobs:
test:
parallelism: 1
docker:
- image: scholzj/circleci-centos-golang
steps:
- checkout
# - setup_remote_docker:
# docker_layer_caching: true
- run:
name: Install Node.js
command: |
curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
sudo yum -y install nodejs
- run:
name: Install RedHat Packages
command: |
sudo yum -y install gcc-c++ make redhat-lsb libtool autoconf
sudo rpm -e --nodeps automake
cd /tmp
wget http://ftp.gnu.org/gnu/automake/automake-1.14.tar.gz
tar xvzf automake-1.14.tar.gz
cd automake-1.14
./configure --prefix=/usr
make
sudo make install
- run:
name: NPM Install
command: npm install
- run:
name: Unit Testing
command: npm run test:unit
- run:
name: Show CentOS Version
command: lsb_release -a
workflows:
version: 2
test-flow:
jobs:
- test
......@@ -2,45 +2,131 @@
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
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 creating a positive environment include:
Examples of behavior that contributes to a positive environment for our
community include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
* 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 by participants include:
Examples of unacceptable behavior include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* 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 electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
* 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
## Our Responsibilities
## Enforcement Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
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.
Project maintainers 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, or to ban temporarily or permanently any contributor for other behaviors 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 both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
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 by contacting the project team at dev@chatie.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
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.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
**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 1.4, available at [http://contributor-covenant.org/version/1/4][version]
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]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
[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
......@@ -25,7 +25,7 @@ about: Create a bug report for a bug you found in wechaty
Answer:
> Which puppet are you using for wechaty? (hostie/puppeteer/padchat/...)
> Which puppet are you using for wechaty? (puppeteer/padlocal/service...)
Answer:
......
......@@ -5,7 +5,7 @@
- A bugfix commit message is prefixed "fix:"
- [ ] Tests for the changes have been added
- [ ] CI has been passed. (GitHub actions all turns green)
- [ ] SLA has been signed
- [ ] CLA has been signed
## Description
......
template: |
## What’s Changed
$CHANGES
## Contributors
$CONTRIBUTORS
......@@ -25,7 +25,7 @@ jobs:
publish:
name: Publish
needs: [build]
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/v[0-9]+'))
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/v'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
......
......@@ -14,13 +14,20 @@ jobs:
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
node-version: [12, 14]
node-version: [12, 14, 16]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-wechaty-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-wechaty-
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
......
......@@ -8,13 +8,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12
uses: actions/setup-node@v1
- uses: actions/cache@v2
with:
node-version: 12
path: ~/.npm
key: ${{ runner.os }}-wechaty-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-wechaty-
- name: Use Node.js 16
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- name: Install Dependencies
run: npm install
run: |
node --version
npm --version
npm install
- name: Test
run: npm test
......@@ -23,11 +32,23 @@ jobs:
name: Pack
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
node: [ 12, 14, 16 ]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-wechaty-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-wechaty-
- uses: actions/setup-node@v2
with:
node-version: 12
node-version: ${{ matrix.node }}
- run: npm install
- run: ./scripts/generate-version.sh
- run: ./scripts/npm-pack-testing.sh
......@@ -41,9 +62,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-wechaty-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-wechaty-
- uses: actions/setup-node@v2
with:
node-version: 12
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm install
- run: ./scripts/generate-version.sh
......@@ -63,10 +91,10 @@ jobs:
VERSION=$(npx pkg-jq -r .version)
if npx version-exists "$NAME" "$VERSION"
then echo "$NAME@$VERSION exists on NPM, skipped."
else npm publish
else echo "Publishing..." && npm publish
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
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'
......@@ -96,4 +96,5 @@
"zbeekman",
"zixia"
],
"typescript.format.semicolons": "remove",
}
此差异已折叠。
FROM ubuntu:eoan
FROM debian:buster
LABEL maintainer="Huan LI (李卓桓) <zixia@zixia.net>"
ENV DEBIAN_FRONTEND noninteractive
......@@ -34,10 +34,11 @@ RUN apt-get update \
tzdata \
vim \
wget \
libxtst6 \
&& apt-get purge --auto-remove \
&& rm -rf /tmp/* /var/lib/apt/lists/*
RUN curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - \
RUN curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - \
&& apt-get update && apt-get install -y --no-install-recommends nodejs \
&& apt-get purge --auto-remove \
&& rm -rf /tmp/* /var/lib/apt/lists/*
......
Wechaty [![NPM Version](https://img.shields.io/npm/v/wechaty?color=brightgreen)](https://www.npmjs.com/package/wechaty) [![NPM](https://github.com/wechaty/wechaty/workflows/NPM/badge.svg)](https://github.com/wechaty/wechaty/actions?query=workflow%3ANPM) [![Docker](https://github.com/wechaty/wechaty/workflows/Docker/badge.svg)](https://github.com/wechaty/wechaty/actions?query=workflow%3ADocker)
# Wechaty [![NPM Version](https://img.shields.io/npm/v/wechaty?color=brightgreen)](https://www.npmjs.com/package/wechaty) [![NPM](https://github.com/wechaty/wechaty/workflows/NPM/badge.svg)](https://github.com/wechaty/wechaty/actions?query=workflow%3ANPM) [![Docker](https://github.com/wechaty/wechaty/workflows/Docker/badge.svg)](https://github.com/wechaty/wechaty/actions?query=workflow%3ADocker)
[![Wechaty](https://wechaty.js.org/img/wechaty-logo.svg)](https://wechaty.js.org)
......@@ -6,18 +6,24 @@
[![GitHub stars](https://img.shields.io/github/stars/wechaty/wechaty.svg?label=github%20stars)](https://github.com/wechaty/wechaty)
[![Docker Pulls](https://img.shields.io/docker/pulls/wechaty/wechaty.svg?maxAge=2592000)](https://hub.docker.com/r/wechaty/wechaty/)
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-blue.svg)](https://www.typescriptlang.org/)
[![Gitter](https://badges.gitter.im/wechaty/wechaty.svg)](https://gitter.im/wechaty/wechaty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Telegram Wechaty Channel](https://img.shields.io/badge/chat-on%20telegram-blue)](https://t.me/wechaty)
## :hearts: Connecting Chatbots
Wechaty is a Conversational 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).
Wechaty is a 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/wiki>
: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/)
## :yum: Voice of Developers
> "Wechaty is a great solution, I believe there would be much more users recognize it." [link](https://github.com/Wechaty/wechaty/pull/310#issuecomment-285574472)
......@@ -42,12 +48,16 @@ See more at [Wiki:Voice Of Developer](https://github.com/Wechaty/wechaty/wiki/Vo
### :raising_hand: Join Us
[![Gitter](https://badges.gitter.im/wechaty/wechaty.svg)](https://gitter.im/wechaty/wechaty?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Wechaty is used in many ChatBot projects by thousands of developers. If you want to talk with other developers, just scan the following QR Code in WeChat with secret code _wechaty_, join our **Wechaty Developers' Home**.
![Wechaty Friday.BOT QR Code](https://wechaty.js.org/img/friday-qrcode.svg)
Scan now, because other Wechaty developers want to talk with you too! (secret code: _wechaty_)
> You are also welcome to join our Gitter channel at <https://gitter.im/wechaty/wechaty> with your GitHub account!
### :book: Resource
Wechaty already held lots of talk and got a lot of blogs in the past 4 years, here is all of the wechaty resources:
......@@ -143,17 +153,21 @@ Wechaty is very powerful that it can run over different protocols. You can speci
If you cannot use Web protocol, you can apply other protocal 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:
Currently we support the following [puppet providers](https://wechaty.js.org/docs/puppet-services/) :
| Protocol | Puppet Provider | Environment Variable |
| --- | --- | --- |
| Web | PuppetPuppeteer | `export WECHATY_PUPPET=wechaty-puppet-puppeteer` |
| iPad | PuppetPadplus | `export WECHATY_PUPPET=wechaty-puppet-padplus` |
| Mac | PuppetMacpro | `export WECHATY_PUPPET=wechaty-puppet-macpro` |
| Mock | PuppetMock | `export WECHATY_PUPPET=wechaty-puppet-mock` |
| Web | PuppetWechat4u | `export WECHATY_PUPPET=wechaty-puppet-wechat4u` |
| iPad | PuppetPadpro **DEPRECATED** | `export WECHATY_PUPPET=wechaty-puppet-padpro` |
| iPad | PuppetPadchat **DEPRECATED** | `export WECHATY_PUPPET=wechaty-puppet-padchat` |
| Web | [PuppetPuppeteer](https://github.com/wechaty/wechaty-puppet-puppeteer) | `export WECHATY_PUPPET=wechaty-puppet-puppeteer` |
| Windows | [PuppetWxwork](https://github.com/juzibot/wxwork-tester) | `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` |
Learn more about Wechaty Puppet from the Puppet Wiki:
......@@ -168,12 +182,12 @@ Read the Full Documentation at [Wechaty Official API Reference](https://wechaty.
Main bot class.
A `Bot` is a Wechaty instance that control a specific [wechaty-puppet](https://github.com/Wechaty/wechaty/wiki/Puppet).
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-puppeteer](https://github.com/Wechaty/wechaty-puppet-puppeteer) - Angular Hook for Web Wechat <- This is the DEFAULT
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
......@@ -465,13 +479,13 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
[![PHP Wechaty](https://img.shields.io/badge/Wechaty-PHP-99c)](https://github.com/wechaty/php-wechaty)
[![.NET(C#) Wechatyin](https://img.shields.io/badge/Wechaty-.NET-629)](https://github.com/wechaty/dotnet-wechaty)
- [Wechaty](https://github.com/wechaty/wechaty) - Conversatioanl SDK for Chatot Makers (TypeScript)
- [Python Wechaty](https://github.com/wechaty/python-wechaty) - Conversational SDK for Chatbot Makers written in Python
- [Go Wechaty](https://github.com/wechaty/go-wechaty) - Conversational SDK for Chatbot Makers written in Go
- [Java Wechaty](https://github.com/wechaty/java-wechaty) - Conversational SDK for Chatbot Makers written in Java(Kotlin)
- [Scala Wechaty](https://github.com/wechaty/scala-wechaty) - Conversational SDK for Chatbot Makers written in Scala
- [PHP Wechaty](https://github.com/wechaty/php-wechaty) - Conversational SDK for Chatbot Makers written in PHP
- [.Net(C#) Wechaty](https://github.com/wechaty/dotnet-wechaty) - Conversational SDK for Chatbot Makers written in .NET(C#)
- [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#)
## :raised_hands: Creators
......
......@@ -62,8 +62,21 @@ async function main () {
const wechaty = new Wechaty({ name: token })
const WECHATY_HOSTIE_PORT = 'WECHATY_HOSTIE_PORT'
const port = parseInt(process.env[WECHATY_HOSTIE_PORT] || '0')
let port
if (process.env['WECHATY_HOSTIE_PORT']) {
/**
* https://github.com/wechaty/wechaty/issues/2122
*/
log.warn('Wechaty', [
'',
'WECHATY_HOSTIE_PORT is deprecated.',
'Use WECHATY_PUPPET_SERVER_PORT instead.',
'See: https://github.com/wechaty/wechaty/issues/2122',
].join(' '))
port = parseInt(process.env['WECHATY_HOSTIE_PORT'])
} else if (process.env['WECHATY_PUPPET_SERVER_PORT']) {
port = parseInt(process.env['WECHATY_PUPPET_SERVER_PORT'])
}
const options: IoClientOptions = {
token,
......
#!/usr/bin/env node
import { PUPPET_DEPENDENCIES } from '../src/puppet-config'
async function main () {
const puppetNameList = Object.keys(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)
# Wechaty v0.48.2 Documentation
# Wechaty v0.62.1 Documentation
- Website - <https://wechaty.js.org>
- Docs Site - <https://wechaty.js.org/docs/>
......@@ -384,7 +384,8 @@ All WeChat rooms(groups) will be encapsulated as a Room.
* [.sync()](#Room+sync)<code>Promise.&lt;void&gt;</code>
* [.say(textOrContactOrFileOrUrlOrMini, [mention])](#Room+say)<code>Promise.&lt;(void\|Message)&gt;</code>
* [.add(contact)](#Room+add)<code>Promise.&lt;void&gt;</code>
* [.del(contact)](#Room+del)<code>Promise.&lt;void&gt;</code>
* [.remove(contact)](#Room+remove)<code>Promise.&lt;void&gt;</code>
* ~~[.del()](#Room+del)~~
* [.quit()](#Room+quit)<code>Promise.&lt;void&gt;</code>
* [.topic([newTopic])](#Room+topic)<code>Promise.&lt;(string\|void)&gt;</code>
* [.announce([text])](#Room+announce)<code>Promise.&lt;(void\|string)&gt;</code>
......@@ -515,10 +516,10 @@ if (room) {
}
}
```
<a name="Room+del"></a>
<a name="Room+remove"></a>
### room.del(contact) ⇒ <code>Promise.&lt;void&gt;</code>
Delete a contact from the room
### room.remove(contact) ⇒ <code>Promise.&lt;void&gt;</code>
Remove a contact from the room
It works only when the bot is the owner of the room
> Tips:
......@@ -541,12 +542,18 @@ const room = await bot.Room.find({topic: 'WeChat'}) // change 'WeChat'
const contact = await bot.Contact.find({name: 'lijiarui'}) // change 'lijiarui' to any room member in the room you just set
if (room) {
try {
await room.del(contact)
await room.remove(contact)
} catch(e) {
console.error(e)
}
}
```
<a name="Room+del"></a>
### ~~room.del()~~
***Deprecated***
**Kind**: instance method of [<code>Room</code>](#Room)
<a name="Room+quit"></a>
### room.quit() ⇒ <code>Promise.&lt;void&gt;</code>
......@@ -1258,7 +1265,7 @@ Send, receive friend request, and friend confirmation events.
* [.toJSON()](#Friendship+toJSON)<code>FriendshipPayload</code>
* _static_
* [.search(condition)](#Friendship.search)[<code>Promise.&lt;Contact&gt;</code>](#Contact)
* [.add(contact, hello)](#Friendship.add)<code>Promise.&lt;void&gt;</code>
* [.add(contact, options)](#Friendship.add)<code>Promise.&lt;void&gt;</code>
* [.fromJSON()](#Friendship.fromJSON)
<a name="Friendship+accept"></a>
......@@ -1380,7 +1387,7 @@ bot.on('friendship', async friendship => {
Search a Friend by phone or weixin.
The best practice is to search friend request once per minute.
Remeber not to do this too frequently, or your account may be blocked.
Remember not to do this too frequently, or your account may be blocked.
**Kind**: static method of [<code>Friendship</code>](#Friendship)
......@@ -1398,7 +1405,7 @@ await bot.Friendship.add(friend_phone, 'hello')
```
<a name="Friendship.add"></a>
### Friendship.add(contact, hello) ⇒ <code>Promise.&lt;void&gt;</code>
### Friendship.add(contact, options) ⇒ <code>Promise.&lt;void&gt;</code>
Send a Friend Request to a `contact` with message `hello`.
The best practice is to send friend request once per minute.
......@@ -1409,13 +1416,19 @@ Remeber not to do this too frequently, or your account may be blocked.
| Param | Type | Description |
| --- | --- | --- |
| contact | [<code>Contact</code>](#Contact) | Send friend request to contact |
| hello | <code>string</code> | The friend request content |
| options | <code>FriendshipAddOptions</code> | The friend request content |
**Example**
```js
const contact = await bot.Friendship.search({phone: '13112341234'})
await bot.Friendship.add(contact, 'Nice to meet you! I am wechaty bot!')
const memberList = await room.memberList()
for (let i = 0; i < memberList.length; i++) {
await bot.Friendship.add(member, 'Nice to meet you! I am wechaty bot!')
await bot.Friendship.add(member, {
room: room,
hello: `Nice to meet you! I am wechaty bot from room: ${await room.topic()}!`,
})
}
```
<a name="Friendship.fromJSON"></a>
......@@ -1444,7 +1457,8 @@ All wechat messages will be encapsulated as a Message.
* [Message](#Message)
* _instance_
* [.from()](#Message+from)[<code>Contact</code>](#Contact)
* [.talker()](#Message+talker)[<code>Contact</code>](#Contact)
* [.from()](#Message+from)
* [.to()](#Message+to)[<code>Contact</code>](#Contact) \| <code>null</code>
* [.room()](#Message+room)[<code>Room</code>](#Room) \| <code>null</code>
* [.text()](#Message+text)<code>string</code>
......@@ -1454,6 +1468,7 @@ All wechat messages will be encapsulated as a Message.
* [.type()](#Message+type)<code>MessageType</code>
* [.self()](#Message+self)<code>boolean</code>
* [.mentionList()](#Message+mentionList)<code>Promise.&lt;Array.&lt;Contact&gt;&gt;</code>
* ~~[.mention()](#Message+mention)~~
* [.mentionSelf()](#Message+mentionSelf)<code>Promise.&lt;boolean&gt;</code>
* [.forward(to)](#Message+forward)<code>Promise.&lt;void&gt;</code>
* [.date()](#Message+date)
......@@ -1465,10 +1480,10 @@ All wechat messages will be encapsulated as a Message.
* [.find()](#Message.find)
* [.findAll()](#Message.findAll)
<a name="Message+from"></a>
<a name="Message+talker"></a>
### message.from() ⇒ [<code>Contact</code>](#Contact)
Get the sender from a message.
### message.talker() ⇒ [<code>Contact</code>](#Contact)
Get the talker of a message.
**Kind**: instance method of [<code>Message</code>](#Message)
**Example**
......@@ -1476,18 +1491,24 @@ Get the sender from a message.
const bot = new Wechaty()
bot
.on('message', async m => {
const contact = msg.from()
const talker = msg.talker()
const text = msg.text()
const room = msg.room()
if (room) {
const topic = await room.topic()
console.log(`Room: ${topic} Contact: ${contact.name()} Text: ${text}`)
console.log(`Room: ${topic} Contact: ${talker.name()} Text: ${text}`)
} else {
console.log(`Contact: ${contact.name()} Text: ${text}`)
console.log(`Contact: ${talker.name()} Text: ${text}`)
}
})
.start()
```
<a name="Message+from"></a>
### message.from()
**Kind**: instance method of [<code>Message</code>](#Message)
**Depreacated**: Use `message.talker()` to replace `message.from()`
https://github.com/wechaty/wechaty/issues/2094
<a name="Message+to"></a>
### message.to() ⇒ [<code>Contact</code>](#Contact) \| <code>null</code>
......@@ -1715,6 +1736,12 @@ Message event table as follows
const contactList = await message.mentionList()
console.log(contactList)
```
<a name="Message+mention"></a>
### ~~message.mention()~~
***Deprecated***
**Kind**: instance method of [<code>Message</code>](#Message)
<a name="Message+mentionSelf"></a>
### message.mentionSelf() ⇒ <code>Promise.&lt;boolean&gt;</code>
......@@ -1830,7 +1857,7 @@ accept room invitation
* _instance_
* [.accept()](#RoomInvitation+accept)<code>Promise.&lt;void&gt;</code>
* [.inviter()](#RoomInvitation+inviter)[<code>Contact</code>](#Contact)
* [.topic()](#RoomInvitation+topic)[<code>Contact</code>](#Contact)
* [.topic()](#RoomInvitation+topic)<code>string</code>
* [.date()](#RoomInvitation+date)<code>Promise.&lt;Date&gt;</code>
* [.age()](#RoomInvitation+age)<code>number</code>
* [.toJSON()](#RoomInvitation+toJSON)<code>string</code>
......@@ -1874,7 +1901,7 @@ bot.on('room-invite', async roomInvitation => {
```
<a name="RoomInvitation+topic"></a>
### roomInvitation.topic() ⇒ [<code>Contact</code>](#Contact)
### roomInvitation.topic() ⇒ <code>string</code>
Get the room topic from room invitation
**Kind**: instance method of [<code>RoomInvitation</code>](#RoomInvitation)
......
......@@ -34,6 +34,23 @@ import { generate } from 'qrcode-terminal'
*/
const bot = new Wechaty({
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',
// }
})
/**
......
{
"name": "wechaty",
"version": "0.48.13",
"description": "Wechaty is Conversational SDK Chatbot Makers, Powered by TypeScript, Docker, and 💖",
"version": "0.63.3",
"description": "Wechaty is a RPA SDK for Chatbot Makers.",
"main": "dist/src/mod.js",
"typings": "dist/src/mod.d.ts",
"engines": {
......@@ -15,14 +15,14 @@
"scripts": {
"build": "tsc",
"clean": "shx rm -fr dist/*",
"dist": "npm run clean && npm run build",
"dist": "npm-run-all clean build",
"pack": "npm pack",
"docs": "bash -x scripts/generate-docs.sh",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"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 && ts-node scripts/sort-contributiveness.ts < CHANGELOG.md > CHANGELOG.new.md 2>/dev/null && cat CHANGELOG.md >> CHANGELOG.new.md && mv CHANGELOG.new.md CHANGELOG.md",
"doctor": "npm run check-node-version && ts-node bin/doctor",
"check-node-version": "check-node-version --node \">= 12\"",
"lint": "npm run check-node-version && npm run lint:es && npm run lint:ts && npm run lint:sh && npm run lint:md",
"lint": "npm-run-all check-node-version lint:es lint:ts lint:sh lint:md",
"lint:es": "eslint --ignore-pattern node_modules/ --ignore-pattern fixtures/ \"{bin,examples,src,scripts,tests}/**/*.ts\"",
"lint:md": "markdownlint README.md",
"lint:ts": "tsc --noEmit",
......@@ -38,8 +38,7 @@
"typedoc": "bash scripts/typedoc.sh",
"io-client": "ts-node bin/io-client",
"demo": "ts-node examples/ding-dong-bot.ts",
"start": "npm run demo",
"postinstall": "opencollective-postinstall"
"start": "npm run demo"
},
"repository": {
"type": "git",
......@@ -83,60 +82,56 @@
},
"homepage": "https://github.com/wechaty/",
"dependencies": {
"brolog": "^1.12.4",
"clone-class": "^0.7.3",
"cuid": "^2.1.8",
"dotenv": "^8.2.0",
"dotenv": "^10.0.0",
"in-gfw": "^1.2.0",
"json-rpc-peer": "^0.16.0",
"json-rpc-peer": "^0.17.0",
"npm-programmatic": "0.0.12",
"open-graph": "^0.2.4",
"opencollective": "^1.0.3",
"opencollective-postinstall": "^2.0.3",
"pkg-dir": "^4.2.0",
"portfinder": "^1.0.28",
"pkg-dir": "^5.0.0",
"promise-retry": "^2.0.1",
"raven": "^2.6.4",
"read-pkg-up": "^7.0.1",
"state-switch": "^0.9.9",
"typed-emitter": "^1.3.0",
"watchdog": "^0.8.17",
"wechaty-puppet-hostie": "^0.10.0",
"wechaty-puppet": "^0.32.3",
"ws": "^7.3.1"
"wechaty-puppet": "^0.41.1",
"wechaty-puppet-service": "^0.21.8",
"ws": "^7.4.6"
},
"devDependencies": {
"@babel/core": "^7.11.4",
"@babel/node": "^7.10.5",
"@babel/preset-env": "^7.11.0",
"@chatie/eslint-config": "^0.12.1",
"@babel/core": "^7.14.3",
"@babel/node": "^7.14.2",
"@babel/preset-env": "^7.14.4",
"@chatie/eslint-config": "^0.12.4",
"@chatie/git-scripts": "^0.6.2",
"@chatie/semver": "^0.4.7",
"@chatie/tsconfig": "^0.13.0",
"@chatie/tsconfig": "^0.17.2",
"@types/cuid": "^1.3.1",
"@types/dotenv": "^8.2.0",
"@types/glob": "^7.1.3",
"@types/open-graph": "^0.2.0",
"@types/open-graph": "^0.2.1",
"@types/promise-retry": "^1.1.3",
"@types/raven": "^2.5.3",
"@types/retry": "^0.12.0",
"@types/ws": "^7.2.6",
"@types/ws": "^7.4.4",
"apiai": "^4.0.3",
"check-node-version": "^4.0.3",
"check-node-version": "^4.1.0",
"coveralls": "^3.1.0",
"cross-env": "^7.0.2",
"cross-env": "^7.0.3",
"finis": "^0.4.4",
"glob": "^7.1.6",
"jsdoc-to-markdown": "^6.0.1",
"markdownlint-cli": "^0.23.2",
"glob": "^7.1.7",
"jsdoc-to-markdown": "^7.0.1",
"markdownlint-cli": "^0.27.1",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"pkg-jq": "^0.2.4",
"pkg-jq": "^0.2.11",
"qrcode-terminal": "^0.12.0",
"shx": "^0.3.2",
"shx": "^0.3.3",
"sloc": "^0.2.1",
"tstest": "^0.4.10",
"typedoc": "^0.18.0",
"wechaty-puppet-mock": "^0.27.2"
"typed-emitter": "^1.3.1",
"typedoc": "^1.0.0-dev.4 ",
"typescript": "^4.3",
"wechaty-puppet-mock": "^0.29.6"
},
"files_comment__whitelist_npm_publish": "http://stackoverflow.com/a/8617868/1123955",
"files": [
......
......@@ -44,14 +44,14 @@ function processLine (line: string): void {
if (matches) {
// console.info('match:', line)
// console.info(matches)
const link = matches[1] + matches[2]
const contributor = matches[3]
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)
contributeMap[contributor]!.push(link)
// console.info(contributiveness)
} else {
console.error('NO match:', line)
......@@ -61,7 +61,7 @@ function processLine (line: string): void {
function outputContributorMd () {
const MIN_MAINTAINER_COMMIT_NUM = 2
function isMaintainer (committer: string): boolean {
return contributeMap[committer].length >= MIN_MAINTAINER_COMMIT_NUM
return contributeMap[committer]!.length >= MIN_MAINTAINER_COMMIT_NUM
}
const activeContributorList = Object
......@@ -70,7 +70,7 @@ function outputContributorMd () {
.sort(desc)
function desc (committerA: string, committerB: string): number {
return contributeMap[committerB].length - contributeMap[committerA].length
return contributeMap[committerB]!.length - contributeMap[committerA]!.length
}
console.info([
......@@ -83,7 +83,7 @@ function outputContributorMd () {
].join('\n'))
for (const contributor of activeContributorList) {
console.info(`1. @${contributor}: ${contributeMap[contributor].join(' ')}`)
console.info(`1. @${contributor}: ${contributeMap[contributor]!.join(' ')}`)
}
console.info([
......@@ -102,7 +102,7 @@ function outputContributorMd () {
continue
}
if (!activeContributorList.includes(contributor)) {
console.info(`1. @${contributor}: ${contributeMap[contributor].join(' ')}`)
console.info(`1. @${contributor}: ${contributeMap[contributor]!.join(' ')}`)
}
}
console.info()
......
......@@ -66,7 +66,7 @@ class LicenseTransformer extends Transform {
// super(options)
// }
public _transform (chunk: any, _: string /* encoding: string */, done: () => void) {
override _transform (chunk: any, _: string /* encoding: string */, done: () => void) {
if (this.updated) {
this.push(chunk)
} else {
......@@ -127,7 +127,7 @@ class LicenseTransformer extends Transform {
return updatedLineList.join('\n')
}
public _flush (done: () => void) {
override _flush (done: () => void) {
if (this.lineBuf) {
this.push(this.lineBuf)
this.lineBuf = ''
......
......@@ -84,14 +84,14 @@ test('validApiHost()', async t => {
// })
test('systemPuppetName ()', async t => {
const WECHATY_PUPPET_ORIG = process.env.WECHATY_PUPPET
const WECHATY_PUPPET_ORIG = process.env['WECHATY_PUPPET']
delete process.env.WECHATY_PUPPET
t.equal(config.systemPuppetName(), 'wechaty-puppet-puppeteer', 'should get wechaty-puppet-puppeteer as puppet name')
delete process.env['WECHATY_PUPPET']
t.equal(config.systemPuppetName(), 'wechaty-puppet-wechat', 'should get wechaty-puppet-wechat as default puppet name')
process.env.WECHATY_PUPPET = 'wechaty-puppet-mock'
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
process.env['WECHATY_PUPPET'] = WECHATY_PUPPET_ORIG
})
......@@ -31,6 +31,8 @@ import {
log,
} from 'wechaty-puppet'
import { looseInstanceOfClass } from './helper-functions/mod'
import {
PuppetModuleName,
PUPPET_NAME_DEFAULT,
......@@ -55,7 +57,7 @@ Raven
release: VERSION,
tags: {
git_commit: GIT_COMMIT_HASH,
platform: process.env.WECHATY_DOCKER
platform: process.env['WECHATY_DOCKER']
? 'docker'
: os.platform(),
},
......@@ -110,29 +112,33 @@ export interface DefaultSetting {
DEFAULT_PROTOCOL : string,
}
const DEFAULT_SETTING = pkg.wechaty as DefaultSetting
const DEFAULT_SETTING = pkg['wechaty'] as DefaultSetting
export class Config {
public default = DEFAULT_SETTING
public apihost = process.env.WECHATY_APIHOST || DEFAULT_SETTING.DEFAULT_APIHOST
public apihost = process.env['WECHATY_APIHOST'] || DEFAULT_SETTING.DEFAULT_APIHOST
public serviceIp = process.env['WECHATY_PUPPET_SERVICE_IP'] || ''
public systemPuppetName (): PuppetModuleName {
return (
process.env.WECHATY_PUPPET || PUPPET_NAME_DEFAULT
process.env['WECHATY_PUPPET'] || PUPPET_NAME_DEFAULT
).toLowerCase() as PuppetModuleName
}
public name = process.env.WECHATY_NAME
public name = process.env['WECHATY_NAME']
// DO NOT set DEFAULT, because sometimes user do not want to connect to io cloud service
public token = process.env.WECHATY_TOKEN
public token = process.env['WECHATY_TOKEN']
public debug = !!(process.env['WECHATY_DEBUG'])
public debug = !!(process.env.WECHATY_DEBUG)
public httpPort = process.env['PORT']
|| process.env['WECHATY_PORT']
|| DEFAULT_SETTING.DEFAULT_PORT
public httpPort = process.env.PORT || process.env.WECHATY_PORT || DEFAULT_SETTING.DEFAULT_PORT
public docker = !!(process.env.WECHATY_DOCKER)
public docker = !!(process.env['WECHATY_DOCKER'])
constructor () {
log.verbose('Config', 'constructor()')
......@@ -169,15 +175,28 @@ export function qrcodeValueToImageUrl (qrcodeValue: string): string {
}
export function isProduction (): boolean {
return process.env.NODE_ENV === 'production'
|| process.env.NODE_ENV === 'prod'
return process.env['NODE_ENV'] === 'production'
|| process.env['NODE_ENV'] === 'prod'
}
/**
* Huan(202011):
* Create a `looseInstanceOfClass` to check `FileBox` and `Puppet` instances #2090
* https://github.com/wechaty/wechaty/issues/2090
*/
type FileBoxClass = FileBox & {
new (...args: any): FileBox
}
const looseInstanceOfFileBox = looseInstanceOfClass(
FileBox as any as FileBoxClass
)
export {
log,
FileBox,
MemoryCard,
Raven,
looseInstanceOfFileBox,
VERSION,
}
......
import { EventEmitter } from 'events'
import TypedEventEmitter from 'typed-emitter'
import type TypedEventEmitter from 'typed-emitter'
import {
Contact,
......
import { EventEmitter } from 'events'
import TypedEventEmitter from 'typed-emitter'
import type TypedEventEmitter from 'typed-emitter'
import {
Contact,
......
import { EventEmitter } from 'events'
import TypedEventEmitter from 'typed-emitter'
import type TypedEventEmitter from 'typed-emitter'
import {
CHAT_EVENT_DICT,
Puppet,
ScanStatus,
} from 'wechaty-puppet'
......@@ -21,6 +22,7 @@ const WECHATY_EVENT_DICT = {
dong : 'Should be emitted after we call `Wechaty.ding()`',
error : "Will be emitted when there's an Error occurred.",
heartbeat : 'Will be emitted periodically after the Wechaty started. If not, means that the Wechaty had died.',
puppet : 'Will be emitted when the puppet has been set.',
ready : 'All underlined data source are ready for use.',
start : 'Will be emitted after the Wechaty had been started.',
stop : 'Will be emitted after the Wechaty had been stopped.',
......@@ -38,6 +40,7 @@ export type WechatyHeartbeatEventListener = (data: any) => void
export type WechatyLoginEventListener = (user: ContactSelf) => void
export type WechatyLogoutEventListener = (user: ContactSelf, reason?: string) => void
export type WechatyMessageEventListener = (message: Message) => void
export type WechatyPuppetEventListener = (puppet: Puppet) => void
export type WechatyReadyEventListener = () => void
export type WechatyRoomInviteEventListener = (roomInvitation: RoomInvitation) => void
export type WechatyRoomJoinEventListener = (room: Room, inviteeList: Contact[], inviter: Contact, date?: Date) => void
......@@ -201,6 +204,10 @@ export type WechatyStartStopEventListener = () => void
* })
*/
interface WechatyEvents {
'room-invite' : WechatyRoomInviteEventListener
'room-join' : WechatyRoomJoinEventListener
'room-leave' : WechatyRoomLeaveEventListener
'room-topic' : WechatyRoomTopicEventListener
dong : WechatyDongEventListener
error : WechatyErrorEventListener
friendship : WechatyFriendshipEventListener
......@@ -208,11 +215,8 @@ interface WechatyEvents {
login : WechatyLoginEventListener
logout : WechatyLogoutEventListener
message : WechatyMessageEventListener
puppet : WechatyPuppetEventListener
ready : WechatyReadyEventListener
'room-invite' : WechatyRoomInviteEventListener
'room-join' : WechatyRoomJoinEventListener
'room-leave' : WechatyRoomLeaveEventListener
'room-topic' : WechatyRoomTopicEventListener
scan : WechatyScanEventListener
start : WechatyStartStopEventListener
stop : WechatyStartStopEventListener
......
/**
* 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 portfinder from 'portfinder'
/**
*
* @param port is just a suggestion.
* there's no grantuee for the number
*
* The IANA suggested ephemeral port range.
* @see http://en.wikipedia.org/wiki/Ephemeral_ports
*
* const DEFAULT_IANA_RANGE = {min: 49152, max: 65535}
*
*/
export async function getPort (
basePort?: number,
): Promise<number> {
if (basePort) {
portfinder.basePort = basePort
}
return portfinder.getPortPromise()
}
......@@ -24,6 +24,8 @@ export async function openGraph (url: string): Promise<og.Data> {
og(url, (err, meta) => {
if (err) {
reject(err)
} else if (!meta) {
reject(new Error('meta is undefined'))
} else {
resolve(meta)
}
......
......@@ -17,10 +17,10 @@
* limitations under the License.
*
*/
export { getPort } from './impure/get-port'
export { generateToken } from './impure/generate-token'
export { tryWait } from './pure/try-wait'
export { tryWait } from './pure/try-wait'
export { looseInstanceOfClass } from './pure/loose-instance-of-class'
export {
digestEmoji,
plainText,
......@@ -28,4 +28,4 @@ export {
stripHtml,
unescapeHtml,
unifyEmoji,
} from './pure/xml'
} from './pure/xml'
......@@ -18,33 +18,54 @@
* limitations under the License.
*
*/
import { test } from 'tstest'
import test from 'blue-tape'
import { FileBox } from 'file-box'
import net from 'net'
import { looseInstanceOfClass } from './loose-instance-of-class'
import { getPort } from './get-port'
test('looseInstanceOfClass: instanceof', async t => {
class Test {}
const looseInstanceOfTest = looseInstanceOfClass(Test)
const test = new Test()
t.true(looseInstanceOfTest(test), 'should be true for a real Test')
})
test('looseInstanceOfClass: constructor.name', async t => {
class Test {}
const looseInstanceOfTest = looseInstanceOfClass(Test)
const OrigTest = Test
t.equal(Test, OrigTest, 'should be the same class reference at beginning')
{
class Test {}
t.notEqual(OrigTest, Test, 'should has a new Test class different to the original Test class')
const f = new Test()
t.true(looseInstanceOfTest(f), 'should be true for the same name class like Test')
}
})
test('getPort() for an available socket port', async t => {
let port = await getPort()
let ttl = 17
test('looseInstanceOfClass: n/a', async t => {
class Test {}
const looseInstanceOfTest = looseInstanceOfClass(Test)
const serverList = []
const o = {}
t.false(looseInstanceOfTest(o), 'should be false for non-Test: {}')
})
while (ttl-- > 0) {
try {
const server = net.createServer(socket => {
console.info(socket)
})
await new Promise(resolve => server.listen(port, resolve))
serverList.push(server)
test('looseInstanceOfClass for FileBox', async t => {
const f = FileBox.fromQRCode('test')
const looseInstanceOfFileBox = looseInstanceOfClass(FileBox as any as FileBox & { new (...args: any): FileBox })
port = await getPort()
const OrigFileBox = FileBox
{
class FileBox {}
t.notEqual(OrigFileBox, FileBox, 'should be two different FileBox class')
} catch (e) {
t.fail('should not exception: ' + e.message + ', ' + e.stack)
}
t.true(f instanceof OrigFileBox, 'should be instanceof OrigFileBox')
t.false(f instanceof FileBox, 'should not instanceof another FileBox class for one FileBox instance')
t.true(looseInstanceOfFileBox(f), 'should be true for looseInstanceOfFileBox because the class has the same name')
}
serverList.map(server => server.close())
t.pass('should has no exception after loop test')
})
/**
* Huan(202011)
* Create a `looseInstanceOfClass` to check `FileBox` and `Puppet` instances #2090
* https://github.com/wechaty/wechaty/issues/2090
*/
function looseInstanceOfClass<T extends { new (...args: any): any }> (klass: T) {
return (o: any): o is InstanceType<T> => {
if (o instanceof klass) {
return true
} else if (o && o.constructor && o.constructor.name === klass.name) {
return true
}
return false
}
}
export { looseInstanceOfClass }
#!/usr/bin/env ts-node
/**
* Wechaty Chatbot SDK - https://github.com/wechaty/wechaty
*
......
文件模式从 100644 更改为 100755
文件模式从 100644 更改为 100755
......@@ -21,12 +21,12 @@
* DO NOT use `require('../')` here!
* because it will cause a LOOP require ERROR
*/
import { StateSwitch } from 'state-switch'
import { StateSwitch } from 'wechaty-puppet'
import {
PuppetServer,
PuppetServerOptions,
} from 'wechaty-puppet-hostie'
} from 'wechaty-puppet-service'
import { Message } from './user/mod'
......@@ -80,11 +80,11 @@ export class IoClient {
this.state = new StateSwitch('IoClient', { log })
}
private async startHostie () {
log.verbose('IoClient', 'startHostie()')
private async startPuppetServer () {
log.verbose('IoClient', 'startPuppetServer()')
if (this.puppetServer) {
throw new Error('hostie server exists')
throw new Error('puppet server exists')
}
const options: PuppetServerOptions = {
......@@ -96,11 +96,11 @@ export class IoClient {
await this.puppetServer.start()
}
private async stopHostie () {
log.verbose('IoClient', 'stopHostie()')
private async stopPuppetServer () {
log.verbose('IoClient', 'stopPuppetService()')
if (!this.puppetServer) {
throw new Error('hostie server does not exist')
throw new Error('puppet server does not exist')
}
await this.puppetServer.stop()
......@@ -125,7 +125,7 @@ export class IoClient {
await this.options.wechaty.start()
await this.startHostie()
await this.startPuppetServer()
this.state.on(true)
......@@ -137,7 +137,7 @@ export class IoClient {
}
private async hookWechaty (wechaty: Wechaty): Promise<void> {
log.verbose('IoClient', 'initWechaty()')
log.verbose('IoClient', 'hookWechaty()')
if (this.state.off()) {
const e = new Error('state.off() is true, skipped')
......@@ -151,8 +151,8 @@ export class IoClient {
.on('message', msg => this.onMessage(msg))
.on('scan', (url, code) => {
log.info('IoClient', [
`[${code}] ${url}]`,
`Online QR Code Image: https://wechaty.js.org/qrcode/${url}`,
`[${code}] ${url}`,
`Online QR Code Image: https://wechaty.js.org/qrcode/${encodeURIComponent(url)}`,
].join('\n'))
})
}
......@@ -171,7 +171,7 @@ export class IoClient {
}
this.io = new Io({
hostiePort : this.options.port,
servicePort : this.options.port,
token : this.options.token,
wechaty : this.options.wechaty,
})
......@@ -222,7 +222,7 @@ export class IoClient {
this.state.off('pending')
await this.stopIo()
await this.stopHostie()
await this.stopPuppetServer()
await this.options.wechaty.stop()
this.state.off(true)
......
......@@ -34,12 +34,16 @@ import {
test('getPeer()', async t => {
const EXPECTED_PORT = 8788
const server = getPeer({
hostieGrpcPort: EXPECTED_PORT,
serviceGrpcPort: EXPECTED_PORT,
})
const client = new Peer()
server.pipe(client).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')
})
......@@ -47,9 +51,13 @@ test('getPeer()', async t => {
test('exec()', async t => {
const EXPECTED_PORT = 8788
const server = getPeer({
hostieGrpcPort: EXPECTED_PORT,
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 = format.request(42, 'getHostieGrpcPort')
const response = await server.exec(request) as string
// console.info('response: ', response)
......
......@@ -20,14 +20,18 @@ const isJsonRpcResponse = (payload: JsonRpcPayload): payload is JsonRpcPaylo
const isJsonRpcError = (payload: JsonRpcPayload): payload is JsonRpcPayloadError => ('error' in payload)
interface IoPeerOptions {
hostieGrpcPort: number,
serviceGrpcPort: number,
}
const getPeer = (options: IoPeerOptions) => {
const getHostieGrpcPort = () => options.hostieGrpcPort
const getServiceGrpcPort = () => options.serviceGrpcPort
const serviceImpl = {
getHostieGrpcPort,
/**
* 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: JsonRpcPayload): Promise<any> => {
......@@ -45,6 +49,10 @@ const getPeer = (options: IoPeerOptions) => {
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]()
......
/**
* Add typescript definitions #60
* https://github.com/JsCommunity/json-rpc-peer/pull/60
*/
declare module 'json-rpc-peer' {
/// <reference types='json-rpc-protocol' />
......@@ -10,8 +14,8 @@ declare module 'json-rpc-peer' {
export * from 'json-rpc-protocol'
declare module 'json-rpc-peer' {
export class Peer extends EventEmitter implements NodeJS.WritableStream {
module 'json-rpc-peer' {
export default class Peer extends EventEmitter implements NodeJS.WritableStream {
constructor(onmessage?: (message: JsonRpcPayload, data: any) => Promise<any>)
......@@ -59,7 +63,7 @@ declare module 'json-rpc-peer' {
}
export default Peer
// export default Peer
}
}
......@@ -27,9 +27,9 @@ import { Wechaty } from './wechaty'
test('Io restart without problem', async t => {
const io = new Io({
// token must not contain any white spaces
hostiePort: 8788,
token : 'mock_token_in_wechaty/wechaty/src/io.spec.ts',
wechaty : new Wechaty(),
servicePort : 8788,
token : 'mock_token_in_wechaty/wechaty/src/io.spec.ts',
wechaty : new Wechaty(),
})
try {
......
......@@ -17,7 +17,6 @@
* limitations under the License.
*
*/
import { StateSwitch } from 'state-switch'
import WebSocket from 'ws'
import {
......@@ -25,6 +24,8 @@ import {
} from './user/mod'
import {
StateSwitch,
EventScanPayload,
} from 'wechaty-puppet'
......@@ -51,11 +52,11 @@ import {
} from './io-peer/io-peer'
export interface IoOptions {
wechaty: Wechaty,
token: string,
apihost?: string,
protocol?: string,
hostiePort?:number,
wechaty : Wechaty,
token : string,
apihost? : string,
protocol? : string,
servicePort? : number,
}
export const IO_EVENT_DICT = {
......@@ -93,6 +94,20 @@ interface IoEventAny {
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 {
private readonly id : string
......@@ -121,7 +136,7 @@ export class Io {
this.id = options.wechaty.id
this.protocol = options.protocol + '|' + 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,
......@@ -129,9 +144,9 @@ export class Io {
this.id,
)
if (options.hostiePort) {
if (options.servicePort) {
this.jsonRpc = getPeer({
hostieGrpcPort: this.options.hostiePort!,
serviceGrpcPort: this.options.servicePort!,
})
}
......@@ -159,6 +174,7 @@ export class Io {
this.ws = await this.initWebSocket()
this.options.wechaty.on('login', () => { this.scanPayload = undefined })
this.options.wechaty.on('scan', (qrcode, status) => {
this.scanPayload = {
...this.scanPayload,
......@@ -190,8 +206,8 @@ export class Io {
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: user }))
wechaty.on('logout', user => this.send({ name: 'logout', payload: user }))
wechaty.on('login', user => this.send({ name: 'login', payload: user.payload }))
wechaty.on('logout', user => this.send({ name: 'logout', payload: user.payload }))
wechaty.on('message', message => this.ioMessage(message))
// FIXME: payload schema need to be defined universal
......@@ -212,8 +228,8 @@ export class Io {
}
let endpoint = 'wss://' + this.options.apihost + '/v0/websocket'
// XXX quick and dirty: use no ssl for APIHOST other than official
// FIXME: use a configuarable VARIABLE for the domain name at here:
// 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'
}
......@@ -284,26 +300,20 @@ export class Io {
case 'botie':
{
const payload = ioEvent.payload
if (payload.onMessage) {
const script = payload.script
try {
/**
* https://github.com/Chatie/botie/issues/2
* const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor
* const fn = new AsyncFunction('require', 'github', 'context', script)
*/
// eslint-disable-next-line
const fn = eval(script)
if (typeof fn === 'function') {
this.onMessage = fn
} else {
log.warn('Io', 'server pushed function is invalid')
}
} catch (e) {
log.warn('Io', 'server pushed function exception: %s', e)
this.options.wechaty.emit('error', e)
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.emit('error', e)
}
}
break
......@@ -326,10 +336,7 @@ export class Io {
if (wechaty.logonoff()) {
const loginEvent: IoEvent = {
name : 'login',
payload : {
id : wechaty.userSelf().id,
name : wechaty.userSelf().name(),
},
payload : (wechaty.userSelf() as any).payload,
}
await this.send(loginEvent)
}
......@@ -475,7 +482,7 @@ export class Io {
const data = JSON.stringify(
this.eventBuffer.shift(),
)
const p = new Promise((resolve, reject) => ws.send(
const p = new Promise<void>((resolve, reject) => ws.send(
data,
(err: undefined | Error) => {
if (err) {
......@@ -491,7 +498,7 @@ export class Io {
try {
await Promise.all(list)
} catch (e) {
log.error('Io', 'send() exceptio: %s', e.stack)
log.error('Io', 'send() exception: %s', e.stack)
throw e
}
}
......@@ -520,7 +527,7 @@ export class Io {
}
this.ws.close()
await new Promise(resolve => {
await new Promise<void>(resolve => {
if (this.ws) {
this.ws.once('close', resolve)
} else {
......@@ -534,14 +541,24 @@ export class Io {
/**
*
* Prepare to be overwriten by server setting
* Prepare to be overwritten by server setting
*
*/
private async ioMessage (m: Message): Promise<void> {
log.silly('Io', 'ioMessage() is a nop function before be overwriten from cloud')
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: Message): Promise<void> {
log.silly('Io', 'syncMessage(%s)', m)
const messageEvent: IoEvent = {
name : 'message',
payload : (m as any).payload,
}
await this.send(messageEvent)
}
}
......@@ -20,38 +20,56 @@
*/
export const PUPPET_DEPENDENCIES = {
/**
* The following puppets were DEPRECATED
* The following puppets were DEPRECATED before 2020
*/
// 'wechaty-puppet-ioscat' : '^0.5.22', // https://www.npmjs.com/package/wechaty-puppet-ioscat
// 'wechaty-puppet-padchat' : '^0.19.3', // https://www.npmjs.com/package/wechaty-puppet-padchat
// 'wechaty-puppet-padpro' : '^0.3.21', // https://www.npmjs.com/package/wechaty-puppet-padpro
/**
* Scoped puppets
* Deprecated on Dec 2020
* https://github.com/wechaty/puppet-service-providers/issues/11
*/
'@juzibot/wechaty-puppet-donut': '^0.3', // https://www.npmjs.com/package/wechaty-puppet-donut (to be published)
'@juzibot/wechaty-puppet-wxwork': '*', // https://www.npmjs.com/package/wechaty-puppet-wxwork (to be published)
// 'wechaty-puppet-padplus' : '^0.7.30', // https://www.npmjs.com/package/wechaty-puppet-padplus
/**
* Wechaty Internal Puppets: dependence by package.json
* Deprecated on Jan 2021: rename to wechaty-puppet-service
* https://github.com/wechaty/wechaty-puppet-service/issues/118
*
* TODO: Huan(202101): will be removed after Dec 31, 2021
*/
'wechaty-puppet-hostie' : '*', // https://www.npmjs.com/package/wechaty-puppet-hostie
'wechaty-puppet-mock' : '*', // https://www.npmjs.com/package/wechaty-puppet-mock
'wechaty-puppet-hostie' : '*', // https://www.npmjs.com/package/wechaty-puppet-hostie
'wechaty-puppet-puppeteer' : '>=0.24', // https://www.npmjs.com/package/wechaty-puppet-puppeteer
/**
* Wechaty External Puppets
* Wechaty Internal Puppets: dependency by package.json
*/
'wechaty-puppet-padplus' : '^0.7.30', // https://www.npmjs.com/package/wechaty-puppet-padplus
'wechaty-puppet-puppeteer' : '^0.23.1', // https://www.npmjs.com/package/wechaty-puppet-puppeteer
'wechaty-puppet-wechat4u' : '^0.17.4', // https://www.npmjs.com/package/wechaty-puppet-wechat4u
'wechaty-puppet-service' : '>=0.21', // https://www.npmjs.com/package/wechaty-puppet-service
'wechaty-puppet-mock' : '>=0.29', // https://www.npmjs.com/package/wechaty-puppet-mock
/**
* Other
* WeChat External Puppets
*/
'wechaty-puppet-gitter' : '^0.3.1', // https://www.npmjs.com/package/wechaty-puppet-gitter
'wechaty-puppet-official-account' : '^0.2.2', // https://www.npmjs.com/package/wechaty-puppet-official-account
'wechaty-puppet-wechat' : '>=0.28', // https://www.npmjs.com/package/wechaty-puppet-wechat
'wechaty-puppet-wechat4u' : '>=0.17', // https://www.npmjs.com/package/wechaty-puppet-wechat4u
'wechaty-puppet-padlocal' : '>=0.4.1', // https://www.npmjs.com/package/wechaty-puppet-padlocal
'wechaty-puppet-official-account' : '>=0.5', // https://www.npmjs.com/package/wechaty-puppet-official-account
/**
* Non-WeChat External Puppets
*/
'wechaty-puppet-gitter' : '>=0.4.7', // https://www.npmjs.com/package/wechaty-puppet-gitter
'wechaty-puppet-lark' : '>=0.4.5', // https://www.npmjs.com/package/wechaty-puppet-lark
'wechaty-puppet-whatsapp' : '>=0.2.2', // https://www.npmjs.com/package/wechaty-puppet-whatsapp
/**
* Scoped puppets (private)
*/
'@juzibot/wechaty-puppet-donut' : '*', // https://www.npmjs.com/package/wechaty-puppet-donut (to be published)
'@juzibot/wechaty-puppet-wxwork' : '*', // https://www.npmjs.com/package/wechaty-puppet-wxwork (to be published)
}
export type PuppetModuleName = keyof typeof PUPPET_DEPENDENCIES
export const PUPPET_NAME_DEFAULT: PuppetModuleName = 'wechaty-puppet-puppeteer'
// Huan(202004): we change default puppet from puppet-service -> puppet-wechat (with UOS support)
export const PUPPET_NAME_DEFAULT: PuppetModuleName = 'wechaty-puppet-wechat'
......@@ -31,6 +31,8 @@ import {
PuppetOptions,
} from 'wechaty-puppet'
import { looseInstanceOfClass } from './helper-functions/pure/loose-instance-of-class'
import {
log,
} from './config'
......@@ -44,6 +46,13 @@ export interface ResolveOptions {
puppetOptions? : PuppetOptions,
}
/**
* Huan(202011):
* Create a `looseInstanceOfClass` to check `FileBox` and `Puppet` instances #2090
* https://github.com/wechaty/wechaty/issues/2090
*/
const looseInstanceOfPuppet = looseInstanceOfClass(Puppet as any as Puppet & { new (...args: any): Puppet })
export class PuppetManager {
public static async resolve (
......@@ -58,14 +67,14 @@ export class PuppetManager {
/**
* Huan(202001): (DEPRECATED) When we are developing, we might experiencing we have two version of wechaty-puppet installed,
* if `optoins.puppet` is Puppet v1, but the `Puppet` in Wechaty is v2,
* if `options.puppet` is Puppet v1, but the `Puppet` in Wechaty is v2,
* then options.puppet will not instanceof Puppet.
* So I changed here to match not a string as a workaround.
*
* Huan(202020): The wechaty-puppet-xxx must NOT dependencies `wechaty-puppet` so that it can be `instanceof`-ed
* wechaty-puppet-xxx should put `wechaty-puppet` in `devDependencies` and `peerDependencies`.
*/
if (options.puppet instanceof Puppet) {
if (looseInstanceOfPuppet(options.puppet)) {
puppetInstance = await this.resolveInstance(options.puppet)
} else if (typeof options.puppet !== 'string') {
log.error('PuppetManager', 'resolve() %s',
......@@ -85,10 +94,17 @@ export class PuppetManager {
* When we have different puppet with different `constructor()` args.
* For example: PuppetA allow `constructor()` but PuppetB requires `constructor(options)`
*
* SOLUTION: we enforce all the PuppetImplenmentation to have `options` and should not allow default parameter.
* Issue: https://github.com/wechaty/wechaty-puppet/issues/2
* SOLUTION: we enforce all the PuppetImplementation to have `options` and should not allow default parameter.
* Issue: https://github.com/wechaty/wechaty-puppet/issues/2
*/
/**
* Huan(20210313) Issue #2151 - https://github.com/wechaty/wechaty/issues/2151
* error TS2511: Cannot create an instance of an abstract class.
*
* Huan(20210530): workaround by "as any"
*/
puppetInstance = new MyPuppet(options.puppetOptions)
puppetInstance = new (MyPuppet as any)(options.puppetOptions)
}
return puppetInstance
......@@ -219,9 +235,9 @@ export class PuppetManager {
}
// https://github.com/GoogleChrome/puppeteer/issues/1597#issuecomment-351945645
if (gfw && !process.env.PUPPETEER_DOWNLOAD_HOST) {
if (gfw && !process.env['PUPPETEER_DOWNLOAD_HOST']) {
log.info('PuppetManager', 'preInstallPuppeteer() set PUPPETEER_DOWNLOAD_HOST=https://npm.taobao.org/mirrors/')
process.env.PUPPETEER_DOWNLOAD_HOST = 'https://npm.taobao.org/mirrors/'
process.env['PUPPETEER_DOWNLOAD_HOST'] = 'https://npm.taobao.org/mirrors/'
}
}
......
......@@ -51,8 +51,8 @@ class ContactSelf extends Contact {
// super(id)
// }
public async avatar () : Promise<FileBox>
public async avatar (file: FileBox) : Promise<void>
public override async avatar () : Promise<FileBox>
public override async avatar (file: FileBox) : Promise<void>
/**
* GET / SET bot avatar
......@@ -81,7 +81,7 @@ class ContactSelf extends Contact {
* })
*
*/
public async avatar (file?: FileBox): Promise<void | FileBox> {
public override async avatar (file?: FileBox): Promise<void | FileBox> {
log.verbose('Contact', 'avatar(%s)', file ? file.name : '')
if (!file) {
......@@ -142,10 +142,10 @@ class ContactSelf extends Contact {
* }
* })
*/
public name (): string
public name (name: string): Promise<void>
public override name (): string
public override name (name: string): Promise<void>
public name (name?: string): string | Promise<void> {
public override name (name?: string): string | Promise<void> {
log.verbose('ContactSelf', 'name(%s)', name || '')
if (typeof name === 'undefined') {
......@@ -204,8 +204,8 @@ function wechatifyContactSelf (wechaty: Wechaty): typeof ContactSelf {
class WechatifiedContactSelf extends ContactSelf {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -50,7 +50,7 @@ test('findAll()', async t => {
const contactList = await wechaty.Contact.findAll()
t.equal(contactList.length, 1, 'should find 1 contact')
t.equal(contactList[0].name(), EXPECTED_CONTACT_NAME, 'should get name from payload')
t.equal(contactList[0]!.name(), EXPECTED_CONTACT_NAME, 'should get name from payload')
await wechaty.stop()
})
......
......@@ -28,24 +28,25 @@ import {
PayloadType,
} from 'wechaty-puppet'
import { Wechaty } from '../wechaty'
import { Wechaty } from '../wechaty'
import {
Raven,
log,
qrCodeForChatie,
} from '../config'
looseInstanceOfFileBox,
} from '../config'
import {
Sayable,
} from '../types'
} from '../types'
import { Message } from './message'
import { MiniProgram } from './mini-program'
import { Tag } from './tag'
import { UrlLink } from './url-link'
import { ContactEventEmitter } from '../events/contact-events'
import { ContactEventEmitter } from '../events/contact-events'
export const POOL = Symbol('pool')
......@@ -58,8 +59,8 @@ export const POOL = Symbol('pool')
*/
class Contact extends ContactEventEmitter implements Sayable {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
public static Type = ContactType
public static Gender = ContactGender
......@@ -170,7 +171,7 @@ class Contact extends ContactEventEmitter implements Sayable {
let n = 0
for (n = 0; n < contactList.length; n++) {
const contact = contactList[n]
const contact = contactList[n]!
// use puppet.contactValidate() to confirm double confirm that this contactId is valid.
// https://github.com/wechaty/wechaty-puppet-padchat/issues/64
// https://github.com/wechaty/wechaty/issues/1345
......@@ -312,7 +313,7 @@ class Contact extends ContactEventEmitter implements Sayable {
/**
* @ignore
*/
public toString (): string {
public override toString (): string {
if (!this.payload) {
return this.constructor.name
}
......@@ -426,7 +427,7 @@ class Contact extends ContactEventEmitter implements Sayable {
this.id,
something.id,
)
} else if (something instanceof FileBox) {
} else if (looseInstanceOfFileBox(something)) {
/**
* 3. File
*/
......@@ -667,7 +668,11 @@ class Contact extends ContactEventEmitter implements Sayable {
if (!this.payload) {
return null
}
return this.payload.friend || null
if (typeof this.payload.friend === 'boolean') {
return this.payload.friend
} else {
return null
}
}
/**
......@@ -883,8 +888,8 @@ function wechatifyContact (wechaty: Wechaty): typeof Contact {
class WechatifiedContact extends Contact {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -24,8 +24,8 @@ import { log } from '../config'
class Favorite {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
public static list (): Favorite[] {
return []
......@@ -75,8 +75,8 @@ function wechatifyFavorite (wechaty: Wechaty): typeof Favorite {
class WechatifiedFavorite extends Favorite {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -31,6 +31,7 @@ import {
} from '../helper-functions/mod'
import {
FriendshipAddOptions as PuppetFriendshipAddOptions,
FriendshipPayload,
FriendshipType,
FriendshipSearchQueryFilter,
......@@ -44,6 +45,18 @@ import {
Contact,
} from './contact'
import {
Room,
} from './room'
interface FriendshipAddOptionsObject {
room?: Room,
contact?: Contact,
hello?: string,
}
type FriendshipAddOptions = string | FriendshipAddOptionsObject
/**
* Send, receive friend request, and friend confirmation events.
*
......@@ -55,8 +68,8 @@ import {
*/
class Friendship extends EventEmitter implements Acceptable {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
public static Type = FriendshipType
......@@ -75,7 +88,7 @@ class Friendship extends EventEmitter implements Acceptable {
* Search a Friend by phone or weixin.
*
* The best practice is to search friend request once per minute.
* Remeber not to do this too frequently, or your account may be blocked.
* Remember not to do this too frequently, or your account may be blocked.
*
* @param {FriendshipSearchCondition} condition - Search friend by phone or weixin.
* @returns {Promise<Contact>}
......@@ -89,12 +102,12 @@ class Friendship extends EventEmitter implements Acceptable {
*
*/
public static async search (
queryFiter : FriendshipSearchQueryFilter,
queryFilter : FriendshipSearchQueryFilter,
): Promise<null | Contact> {
log.verbose('Friendship', 'static search("%s")',
JSON.stringify(queryFiter),
JSON.stringify(queryFilter),
)
const contactId = await this.wechaty.puppet.friendshipSearch(queryFiter)
const contactId = await this.wechaty.puppet.friendshipSearch(queryFilter)
if (!contactId) {
return null
......@@ -112,24 +125,42 @@ class Friendship extends EventEmitter implements Acceptable {
* Remeber not to do this too frequently, or your account may be blocked.
*
* @param {Contact} contact - Send friend request to contact
* @param {string} hello - The friend request content
* @param {FriendshipAddOptions} options - The friend request content
* @returns {Promise<void>}
*
* @example
* const contact = await bot.Friendship.search({phone: '13112341234'})
* await bot.Friendship.add(contact, 'Nice to meet you! I am wechaty bot!')
*
* const memberList = await room.memberList()
* for (let i = 0; i < memberList.length; i++) {
* await bot.Friendship.add(member, 'Nice to meet you! I am wechaty bot!')
* await bot.Friendship.add(member, {
* room: room,
* hello: `Nice to meet you! I am wechaty bot from room: ${await room.topic()}!`,
* })
* }
*
*/
public static async add (
contact : Contact,
hello : string,
options : FriendshipAddOptions,
): Promise<void> {
log.verbose('Friendship', 'static add(%s, %s)',
contact.id,
hello,
typeof options === 'string' ? options : options.hello,
)
await this.wechaty.puppet.friendshipAdd(contact.id, hello)
if (typeof options === 'string') {
log.warn('Friendship', 'the params hello is deprecated in the next version, please put the attr hello into options object, e.g. { hello: "xxxx" }')
await this.wechaty.puppet.friendshipAdd(contact.id, { hello: options })
} else {
const friendOption: PuppetFriendshipAddOptions = {
contactId: options?.contact?.id,
hello: options.hello,
roomId: options.room && options.room.id,
}
await this.wechaty.puppet.friendshipAdd(contact.id, friendOption)
}
}
public static async del (
......@@ -170,7 +201,7 @@ class Friendship extends EventEmitter implements Acceptable {
}
}
public toString () {
public override toString () {
if (!this.payload) {
return this.constructor.name
}
......@@ -413,8 +444,8 @@ function wechatifyFriendship (wechaty: Wechaty): typeof Friendship {
class WechatifiedFriendship extends Friendship {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -31,8 +31,8 @@ import {
class Image {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
constructor (
public id: string,
......@@ -84,8 +84,8 @@ function wechatifyImage (wechaty: Wechaty): typeof Image {
class WechatifiedImage extends Image {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -95,7 +95,7 @@ test('recalled()', async t => {
const recalledMessage = await message.toRecalled()
t.assert(recalledMessage, 'recalled message should exist.')
t.equal(recalledMessage!.id, EXPECTED_RECALLED_MESSAGE_ID, 'Recalled message should have the right id.')
t.equal(recalledMessage!.from()!.id, EXPECTED_FROM_CONTACT_ID, 'Recalled message should have the right from contact id.')
t.equal(recalledMessage!.talker().id, EXPECTED_FROM_CONTACT_ID, 'Recalled message should have the right from contact id.')
t.equal(recalledMessage!.to()!.id, EXPECTED_TO_CONTACT_ID, 'Recalled message should have the right to contact id.')
t.equal(recalledMessage!.room()!.id, EXPECTED_ROOM_ID, 'Recalled message should have the right room id.')
......
......@@ -26,19 +26,20 @@ import {
MessageType,
} from 'wechaty-puppet'
import { escapeRegExp } from '../helper-functions/pure/escape-regexp'
import { timestampToDate } from '../helper-functions/pure/timestamp-to-date'
import { escapeRegExp } from '../helper-functions/pure/escape-regexp'
import { timestampToDate } from '../helper-functions/pure/timestamp-to-date'
import {
Wechaty,
} from '../wechaty'
} from '../wechaty'
import {
AT_SEPARATOR_REGEX,
FileBox,
log,
Raven,
} from '../config'
looseInstanceOfFileBox,
} from '../config'
import {
Sayable,
} from '../types'
......@@ -55,7 +56,7 @@ import {
import {
MiniProgram,
} from './mini-program'
import { Image } from './image'
import { Image } from './image'
/**
* All wechat messages will be encapsulated as a Message.
......@@ -64,8 +65,8 @@ import { Image } from './image'
*/
class Message extends EventEmitter implements Sayable {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
/**
*
......@@ -100,7 +101,7 @@ class Message extends EventEmitter implements Sayable {
log.warn('Message', 'findAll() got more than one(%d) result', messageList.length)
}
return messageList[0]
return messageList[0]!
}
/**
......@@ -192,7 +193,7 @@ class Message extends EventEmitter implements Sayable {
/**
* @ignore
*/
public toString () {
public override toString () {
if (!this.payload) {
return this.constructor.name
}
......@@ -201,9 +202,8 @@ class Message extends EventEmitter implements Sayable {
'Message',
`#${MessageType[this.type()]}`,
'[',
this.from()
? '🗣' + this.from()
: '',
'🗣',
this.talker(),
this.room()
? '@👥' + this.room()
: '',
......@@ -224,38 +224,34 @@ class Message extends EventEmitter implements Sayable {
return msgStrList.join('')
}
public talker (): Contact {
return this.from()!
}
public conversation (): Contact | Room {
if (this.room()) {
return this.room()!
} else {
return this.from()!
return this.talker()
}
}
/**
* Get the sender from a message.
* Get the talker of a message.
* @returns {Contact}
* @example
* const bot = new Wechaty()
* bot
* .on('message', async m => {
* const contact = msg.from()
* const talker = msg.talker()
* const text = msg.text()
* const room = msg.room()
* if (room) {
* const topic = await room.topic()
* console.log(`Room: ${topic} Contact: ${contact.name()} Text: ${text}`)
* console.log(`Room: ${topic} Contact: ${talker.name()} Text: ${text}`)
* } else {
* console.log(`Contact: ${contact.name()} Text: ${text}`)
* console.log(`Contact: ${talker.name()} Text: ${text}`)
* }
* })
* .start()
*/
public from (): null | Contact {
public talker (): Contact {
if (!this.payload) {
throw new Error('no payload')
}
......@@ -265,13 +261,30 @@ class Message extends EventEmitter implements Sayable {
// return
// }
const fromId = this.payload.fromId
if (!fromId) {
return null
const talkerId = this.payload.fromId
if (!talkerId) {
// Huan(202011): It seems that the fromId will never be null?
// return null
throw new Error('payload.fromId is null?')
}
const from = this.wechaty.Contact.load(fromId)
return from
const talker = this.wechaty.Contact.load(talkerId)
return talker
}
/**
* @depreacated Use `message.talker()` to replace `message.from()`
* https://github.com/wechaty/wechaty/issues/2094
*/
public from (): null | Contact {
log.warn('Message', 'from() is deprecated, use talker() instead. Call stack: %s',
new Error().stack,
)
try {
return this.talker()
} catch (e) {
return null
}
}
/**
......@@ -487,7 +500,7 @@ class Message extends EventEmitter implements Sayable {
log.verbose('Message', 'say(%s)', something)
// const user = this.wechaty.puppet.userSelf()
const from = this.from()
const talker = this.talker()
// const to = this.to()
const room = this.room()
......@@ -497,9 +510,9 @@ class Message extends EventEmitter implements Sayable {
if (room) {
conversation = room
conversationId = room.id
} else if (from) {
conversation = from
conversationId = from.id
} else if (talker) {
conversation = talker
conversationId = talker.id
} else {
throw new Error('neither room nor from?')
}
......@@ -522,8 +535,8 @@ class Message extends EventEmitter implements Sayable {
* Text Message
*/
let mentionIdList
if (from && await this.mentionSelf()) {
mentionIdList = [from.id]
if (talker && await this.mentionSelf()) {
mentionIdList = [talker.id]
}
msgId = await this.wechaty.puppet.messageSendText(
......@@ -539,7 +552,12 @@ class Message extends EventEmitter implements Sayable {
conversationId,
something.id,
)
} else if (something instanceof FileBox) {
} else if (looseInstanceOfFileBox(something)) {
/**
* Be aware of minified codes:
* https://stackoverflow.com/questions/1249531/how-to-get-a-javascript-objects-class#comment60309941_1249554
*/
/**
* File Message
*/
......@@ -633,9 +651,9 @@ class Message extends EventEmitter implements Sayable {
*/
public self (): boolean {
const userId = this.wechaty.puppet.selfId()
const from = this.from()
const talker = this.talker()
return !!from && from.id === userId
return !!talker && talker.id === userId
}
/**
......@@ -734,8 +752,13 @@ class Message extends EventEmitter implements Sayable {
return contactList
}
/**
* @deprecated mention() DEPRECATED. use mentionList() instead.
*/
public async mention (): Promise<Contact[]> {
log.warn('Message', 'mention() DEPRECATED. use mentionList() instead.')
log.warn('Message', 'mention() DEPRECATED. use mentionList() instead. Call stack: %s',
new Error().stack,
)
return this.mentionList()
}
......@@ -1034,8 +1057,8 @@ function wechatifyMessage (wechaty: Wechaty): typeof Message {
class WechatifiedMessage extends Message {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -28,8 +28,8 @@ import {
class MiniProgram {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
/**
*
......@@ -96,8 +96,8 @@ function wechatifyMiniProgram (wechaty: Wechaty): typeof MiniProgram {
class WechatifiedMiniProgram extends MiniProgram {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -23,8 +23,8 @@ import { Contact } from './contact'
class Moment {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
public static post () {
// post new moment
......@@ -51,8 +51,8 @@ function wechatifyMoment (wechaty: Wechaty): typeof Moment {
class WechatifiedMoment extends Moment {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -45,8 +45,8 @@ import { RoomInvitationPayload } from 'wechaty-puppet'
*/
class RoomInvitation implements Acceptable {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
public static load<T extends typeof RoomInvitation> (
this : T,
......@@ -165,7 +165,7 @@ class RoomInvitation implements Acceptable {
/**
* Get the room topic from room invitation
*
* @returns {Contact}
* @returns {string}
* @example
* const bot = new Wechaty()
* bot.on('room-invite', async roomInvitation => {
......@@ -293,8 +293,8 @@ function wechatifyRoomInvitation (wechaty: Wechaty): typeof RoomInvitation {
class WechatifiedRoomInvitation extends RoomInvitation {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -52,7 +52,7 @@ test('findAll()', async t => {
const roomList = await wechaty.Room.findAll()
t.equal(roomList.length, 1, 'should find 1 room')
t.equal(await roomList[0].topic(), EXPECTED_ROOM_TOPIC, 'should get topic from payload')
t.equal(await roomList[0]!.topic(), EXPECTED_ROOM_TOPIC, 'should get topic from payload')
await wechaty.stop()
})
......
......@@ -19,7 +19,7 @@
*/
import { instanceToClass } from 'clone-class'
import { Wechaty } from '../wechaty'
import { Wechaty } from '../wechaty'
import {
FOUR_PER_EM_SPACE,
......@@ -27,10 +27,11 @@ import {
log,
Raven,
} from '../config'
looseInstanceOfFileBox,
} from '../config'
import {
Sayable,
} from '../types'
} from '../types'
import {
guardQrCodeValue,
......@@ -58,8 +59,8 @@ import { RoomEventEmitter } from '../events/room-events'
*/
class Room extends RoomEventEmitter implements Sayable {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
protected static pool: Map<string, Room>
......@@ -204,7 +205,7 @@ class Room extends RoomEventEmitter implements Sayable {
let n = 0
for (n = 0; n < roomList.length; n++) {
const room = roomList[n]
const room = roomList[n]!
// use puppet.roomValidate() to confirm double confirm that this roomId is valid.
// https://github.com/wechaty/wechaty-puppet-padchat/issues/64
// https://github.com/wechaty/wechaty/issues/1345
......@@ -299,7 +300,7 @@ class Room extends RoomEventEmitter implements Sayable {
/**
* @ignore
*/
public toString () {
public override toString () {
if (!this.payload) {
return this.constructor.name
}
......@@ -554,7 +555,7 @@ class Room extends RoomEventEmitter implements Sayable {
text,
mentionList.map(c => c.id),
)
} else if (something instanceof FileBox) {
} else if (looseInstanceOfFileBox(something)) {
/**
* 2. File Message
*/
......@@ -612,7 +613,7 @@ class Room extends RoomEventEmitter implements Sayable {
*/
return this.wechaty.puppet.messageSendText(
this.id,
textList[0],
textList[0]!,
)
// TODO(huan) 20191222 it seems the following code will not happen,
// because it's equal the mentionList.length === 0 situation?
......@@ -647,7 +648,7 @@ class Room extends RoomEventEmitter implements Sayable {
const mentionName = await this.alias(mentionContact) || mentionContact.name()
finalText += textList[i] + '@' + mentionName
} else {
finalText += textList[i] + varList[i]
finalText += textList[i]! + varList[i]!
}
}
finalText += textList[i]
......@@ -797,7 +798,7 @@ class Room extends RoomEventEmitter implements Sayable {
}
/**
* Delete a contact from the room
* Remove a contact from the room
* It works only when the bot is the owner of the room
*
* > Tips:
......@@ -815,18 +816,30 @@ class Room extends RoomEventEmitter implements Sayable {
* const contact = await bot.Contact.find({name: 'lijiarui'}) // change 'lijiarui' to any room member in the room you just set
* if (room) {
* try {
* await room.del(contact)
* await room.remove(contact)
* } catch(e) {
* console.error(e)
* }
* }
*/
public async del (contact: Contact): Promise<void> {
public async remove (contact: Contact): Promise<void> {
log.verbose('Room', 'del(%s)', contact)
await this.wechaty.puppet.roomDel(this.id, contact.id)
// this.delLocal(contact)
}
/**
* @deprecated use remove(contact) instead.
*
* Huan(202106): will be removed after Dec 31, 2023
*/
public async del (contact: Contact): Promise<void> {
log.verbose('Room', 'del(%s)', contact)
log.warn('Room', 'del() is DEPRECATED, use remove() instead.')
return this.remove(contact)
}
// private delLocal(contact: Contact): void {
// log.verbose('Room', 'delLocal(%s)', contact)
......@@ -908,7 +921,7 @@ class Room extends RoomEventEmitter implements Sayable {
let defaultTopic = (memberList[0] && memberList[0].name()) || ''
for (let i = 1; i < 3 && memberList[i]; i++) {
defaultTopic += ',' + memberList[i].name()
defaultTopic += ',' + memberList[i]!.name()
}
return defaultTopic
}
......@@ -1146,7 +1159,7 @@ class Room extends RoomEventEmitter implements Sayable {
if (memberList.length > 1) {
log.warn('Room', 'member(%s) get %d contacts, use the first one by default', JSON.stringify(queryArg), memberList.length)
}
return memberList[0]
return memberList[0]!
}
/**
......@@ -1215,8 +1228,8 @@ function wechatifyRoom (wechaty: Wechaty): typeof Room {
class WechatifiedRoom extends Room {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
......@@ -27,8 +27,8 @@ import { Favorite } from './favorite'
class Tag {
static get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directory. See: https://github.com/wechaty/wechaty/issues/2027') }
static get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
get wechaty (): Wechaty { throw new Error('This class can not be used directly. See: https://github.com/wechaty/wechaty/issues/2027') }
protected static pool: Map<string, Tag>
......@@ -214,8 +214,8 @@ function wechatifyTag (wechaty: Wechaty): typeof Tag {
class WechatifiedTag extends Tag {
static get wechaty () { return wechaty }
get wechaty () { return wechaty }
static override get wechaty () { return wechaty }
override get wechaty () { return wechaty }
}
......
#!/usr/bin/env ts-node
import test from 'tstest'
import { UrlLink } from './url-link'
test('UrlLink', async t => {
const URL = 'https://wechaty.js.org/2020/07/02/wechat-bot-in-ten-minutes'
const EXPECTED_PAYLOAD = {
description: '十分钟实现一个智能问答微信聊天机器人',
thumbnailUrl: 'https://wechaty.js.org/assets/contributors/luweicn/avatar.png',
title: '十分钟实现一个智能问答微信聊天机器人',
url: 'https://wechaty.js.org/2020/07/02/wechat-bot-in-ten-minutes',
}
const urlLink = await UrlLink.create(URL)
t.equal(urlLink.title(), EXPECTED_PAYLOAD.title, 'should have title',)
t.equal(urlLink.description(), EXPECTED_PAYLOAD.description, 'should have description',)
t.equal(urlLink.url(), EXPECTED_PAYLOAD.url, 'should have url',)
t.equal(urlLink.thumbnailUrl(), EXPECTED_PAYLOAD.thumbnailUrl, 'should have thumbnailUrl',)
})
......@@ -62,7 +62,7 @@ class UrlLink {
}
if (Array.isArray(meta.title)) {
title = meta.title[0]
title = meta.title[0]!
} else {
title = meta.title
}
......
......@@ -152,7 +152,7 @@ test.skip('SKIP DEALING WITH THE LISTENER EXCEPTIONS. on(event, Function)', asyn
test.skip('SKIP DEALING WITH THE LISTENER EXCEPTIONS. test async error', async (t) => {
// Do not modify the gloabl Wechaty instance
// Do not modify the global Wechaty instance
class MyWechatyTest extends Wechaty {}
const EXPECTED_ERROR = new Error('test')
......@@ -162,12 +162,12 @@ test.skip('SKIP DEALING WITH THE LISTENER EXCEPTIONS. test async error', async (
})
const asyncErrorFunction = function () {
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
setTimeout(function () {
reject(EXPECTED_ERROR)
}, 100)
// tslint ask resolve must be called,
// so write a fasly value, so that it never called
// so write a falsy value, so that it never called
if (+new Date() < 0) {
resolve()
}
......
......@@ -20,12 +20,13 @@
import cuid from 'cuid'
import os from 'os'
import { StateSwitch } from 'state-switch'
import { instanceToClass } from 'clone-class'
import {
Puppet,
MemoryCard,
StateSwitch,
PUPPET_EVENT_DICT,
PuppetEventName,
......@@ -133,7 +134,9 @@ const PUPPET_MEMORY_NAME = 'puppet'
*/
class Wechaty extends WechatyEventEmitter implements Sayable {
public static readonly VERSION = VERSION
static readonly VERSION = VERSION
static readonly log = log
readonly log = log
public readonly state : StateSwitch
private readonly readyState : StateSwitch
......@@ -173,16 +176,16 @@ class Wechaty extends WechatyEventEmitter implements Sayable {
protected wechatifiedTag? : typeof Tag
protected wechatifiedUrlLink? : typeof UrlLink
public get Contact () : typeof Contact { return guardWechatify(this.wechatifiedContact) }
public get ContactSelf () : typeof ContactSelf { return guardWechatify(this.wechatifiedContactSelf) }
public get Friendship () : typeof Friendship { return guardWechatify(this.wechatifiedFriendship) }
public get Image () : typeof Image { return guardWechatify(this.wechatifiedImage) }
public get Message () : typeof Message { return guardWechatify(this.wechatifiedMessage) }
public get MiniProgram () : typeof MiniProgram { return guardWechatify(this.wechatifiedMiniProgram) }
public get Room () : typeof Room { return guardWechatify(this.wechatifiedRoom) }
public get RoomInvitation () : typeof RoomInvitation { return guardWechatify(this.wechatifiedRoomInvitation) }
public get Tag () : typeof Tag { return guardWechatify(this.wechatifiedTag) }
public get UrlLink () : typeof UrlLink { return guardWechatify(this.wechatifiedUrlLink) }
get Contact () : typeof Contact { return guardWechatify(this.wechatifiedContact) }
get ContactSelf () : typeof ContactSelf { return guardWechatify(this.wechatifiedContactSelf) }
get Friendship () : typeof Friendship { return guardWechatify(this.wechatifiedFriendship) }
get Image () : typeof Image { return guardWechatify(this.wechatifiedImage) }
get Message () : typeof Message { return guardWechatify(this.wechatifiedMessage) }
get MiniProgram () : typeof MiniProgram { return guardWechatify(this.wechatifiedMiniProgram) }
get Room () : typeof Room { return guardWechatify(this.wechatifiedRoom) }
get RoomInvitation () : typeof RoomInvitation { return guardWechatify(this.wechatifiedRoomInvitation) }
get Tag () : typeof Tag { return guardWechatify(this.wechatifiedTag) }
get UrlLink () : typeof UrlLink { return guardWechatify(this.wechatifiedUrlLink) }
/**
* Get the global instance of Wechaty
......@@ -311,7 +314,7 @@ class Wechaty extends WechatyEventEmitter implements Sayable {
/**
* @ignore
*/
public toString () {
public override toString () {
if (!this.options) {
return this.constructor.name
}
......@@ -332,7 +335,7 @@ class Wechaty extends WechatyEventEmitter implements Sayable {
return this.options.name || 'wechaty'
}
public on (event: WechatyEventName, listener: (...args: any[]) => any): this {
public override on (event: WechatyEventName, listener: (...args: any[]) => any): this {
log.verbose('Wechaty', 'on(%s, listener) registering... listenerCount: %s',
event,
this.listenerCount(event),
......@@ -454,39 +457,55 @@ class Wechaty extends WechatyEventEmitter implements Sayable {
case 'friendship':
puppet.on('friendship', async payload => {
const friendship = this.Friendship.load(payload.friendshipId)
await friendship.ready()
this.emit('friendship', friendship)
friendship.contact().emit('friendship', friendship)
try {
await friendship.ready()
this.emit('friendship', friendship)
friendship.contact().emit('friendship', friendship)
} catch (e) {
this.emit('error', e)
}
})
break
case 'login':
puppet.on('login', async payload => {
const contact = this.ContactSelf.load(payload.contactId)
await contact.ready()
this.emit('login', contact)
try {
await contact.ready()
this.emit('login', contact)
} catch (e) {
this.emit('error', e)
}
})
break
case 'logout':
puppet.on('logout', async payload => {
const contact = this.ContactSelf.load(payload.contactId)
await contact.ready()
this.emit('logout', contact, payload.data)
try {
await contact.ready()
this.emit('logout', contact, payload.data)
} catch (e) {
this.emit('error', e)
}
})
break
case 'message':
puppet.on('message', async payload => {
const msg = this.Message.load(payload.messageId)
await msg.ready()
this.emit('message', msg)
const room = msg.room()
if (room) {
room.emit('message', msg)
} else {
this.userSelf().emit('message', msg)
try {
await msg.ready()
this.emit('message', msg)
const room = msg.room()
if (room) {
room.emit('message', msg)
} else {
msg.talker().emit('message', msg)
}
} catch (e) {
this.emit('error', e)
}
})
break
......@@ -510,60 +529,71 @@ class Wechaty extends WechatyEventEmitter implements Sayable {
case 'room-join':
puppet.on('room-join', async payload => {
const room = this.Room.load(payload.roomId)
await room.sync()
try {
await room.sync()
const inviteeList = payload.inviteeIdList.map(id => this.Contact.load(id))
await Promise.all(inviteeList.map(c => c.ready()))
const inviteeList = payload.inviteeIdList.map(id => this.Contact.load(id))
await Promise.all(inviteeList.map(c => c.ready()))
const inviter = this.Contact.load(payload.inviterId)
await inviter.ready()
const date = timestampToDate(payload.timestamp)
const inviter = this.Contact.load(payload.inviterId)
await inviter.ready()
const date = timestampToDate(payload.timestamp)
this.emit('room-join', room, inviteeList, inviter, date)
room.emit('join', inviteeList, inviter, date)
this.emit('room-join', room, inviteeList, inviter, date)
room.emit('join', inviteeList, inviter, date)
} catch (e) {
this.emit('error', e)
}
})
break
case 'room-leave':
puppet.on('room-leave', async payload => {
const room = this.Room.load(payload.roomId)
/**
* See: https://github.com/wechaty/wechaty/pull/1833
*/
await room.sync()
const leaverList = payload.removeeIdList.map(id => this.Contact.load(id))
await Promise.all(leaverList.map(c => c.ready()))
const remover = this.Contact.load(payload.removerId)
await remover.ready()
const date = timestampToDate(payload.timestamp)
this.emit('room-leave', room, leaverList, remover, date)
room.emit('leave', leaverList, remover, date)
try {
const room = this.Room.load(payload.roomId)
// issue #254
const selfId = this.puppet.selfId()
if (selfId && payload.removeeIdList.includes(selfId)) {
await this.puppet.dirtyPayload(PayloadType.Room, payload.roomId)
await this.puppet.dirtyPayload(PayloadType.RoomMember, payload.roomId)
/**
* See: https://github.com/wechaty/wechaty/pull/1833
*/
await room.sync()
const leaverList = payload.removeeIdList.map(id => this.Contact.load(id))
await Promise.all(leaverList.map(c => c.ready()))
const remover = this.Contact.load(payload.removerId)
await remover.ready()
const date = timestampToDate(payload.timestamp)
this.emit('room-leave', room, leaverList, remover, date)
room.emit('leave', leaverList, remover, date)
// issue #254
const selfId = this.puppet.selfId()
if (selfId && payload.removeeIdList.includes(selfId)) {
await this.puppet.dirtyPayload(PayloadType.Room, payload.roomId)
await this.puppet.dirtyPayload(PayloadType.RoomMember, payload.roomId)
}
} catch (e) {
this.emit('error', e)
}
})
break
case 'room-topic':
puppet.on('room-topic', async payload => {
const room = this.Room.load(payload.roomId)
await room.sync()
const changer = this.Contact.load(payload.changerId)
await changer.ready()
const date = timestampToDate(payload.timestamp)
this.emit('room-topic', room, payload.newTopic, payload.oldTopic, changer, date)
room.emit('topic', payload.newTopic, payload.oldTopic, changer, date)
try {
const room = this.Room.load(payload.roomId)
await room.sync()
const changer = this.Contact.load(payload.changerId)
await changer.ready()
const date = timestampToDate(payload.timestamp)
this.emit('room-topic', room, payload.newTopic, payload.oldTopic, changer, date)
room.emit('topic', payload.newTopic, payload.oldTopic, changer, date)
} catch (e) {
this.emit('error', e)
}
})
break
......@@ -579,31 +609,35 @@ class Wechaty extends WechatyEventEmitter implements Sayable {
case 'dirty':
/**
* https://github.com/wechaty/wechaty-puppet-hostie/issues/43
* https://github.com/wechaty/wechaty-puppet-service/issues/43
*/
puppet.on('dirty', async ({ payloadType, payloadId }) => {
switch (payloadType) {
case PayloadType.RoomMember:
case PayloadType.Contact:
await this.Contact.load(payloadId).sync()
break
case PayloadType.Room:
await this.Room.load(payloadId).sync()
break
/**
* Huan(202008): noop for the following
*/
case PayloadType.Friendship:
// Friendship has no payload
break
case PayloadType.Message:
// Message does not need to dirty (?)
break
case PayloadType.Unknown:
default:
throw new Error('unknown payload type: ' + payloadType)
try {
switch (payloadType) {
case PayloadType.RoomMember:
case PayloadType.Contact:
await this.Contact.load(payloadId).sync()
break
case PayloadType.Room:
await this.Room.load(payloadId).sync()
break
/**
* Huan(202008): noop for the following
*/
case PayloadType.Friendship:
// Friendship has no payload
break
case PayloadType.Message:
// Message does not need to dirty (?)
break
case PayloadType.Unknown:
default:
throw new Error('unknown payload type: ' + payloadType)
}
} catch (e) {
this.emit('error', e)
}
})
break
......@@ -993,10 +1027,34 @@ class Wechaty extends WechatyEventEmitter implements Sayable {
/**
* @ignore
*/
public async reset (reason?: string): Promise<void> {
log.verbose('Wechaty', 'reset() because %s', reason || 'no reason')
await this.puppet.stop()
await this.puppet.start()
public reset (reason?: string): void {
log.verbose('Wechaty', 'reset() with reason: %s, call stack: %s',
reason || 'no reason',
// https://stackoverflow.com/a/2060330/1123955
new Error().stack,
)
this.puppet.stop()
.then(() => this.puppet.start())
.finally(() => {
log.verbose('Wechaty', 'reset() done.')
})
.catch(e => {
log.warn('Wechaty', 'reset() rejection: %s', e && e.message)
/**
* Dealing with https://github.com/wechaty/wechaty/issues/2197
*/
setTimeout(
() => this.reset(),
Math.floor(
(
10 + 10 * Math.random()
) * 1000
)
)
})
}
public unref (): void {
......
......@@ -31,17 +31,17 @@ function getBotList (): Wechaty[] {
// new Wechaty({ puppet: 'wechaty-puppet-puppeteer' }),
]
if (process.env.WECHATY_PUPPET_HOSTIE_TOKEN) {
if (process.env.WECHATY_PUPPET_SERVICE_TOKEN) {
botList.push(
new Wechaty({
puppet: 'wechaty-puppet-hostie',
puppet: 'wechaty-puppet-service',
})
)
}
if (process.env.WECHATY_PUPPET_PADPLUS_TOKEN) {
if (process.env.WECHATY_PUPPET_PADLOCAL_TOKEN) {
botList.push(
new Wechaty({
puppet: 'wechaty-puppet-padplus',
puppet: 'wechaty-puppet-padlocal',
})
)
}
......