├── .codeclimate.yml
├── .editorconfig
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── codeclimate.yml
│ ├── dispatch.yml
│ └── go.yml
├── .gitignore
├── .vscode
└── settings.json
├── CONTRIBUTING.md
├── LICENSE
├── MAINTAINERS
├── Makefile
├── NOTICE
├── README.md
├── VERSION
├── docs
└── images
│ ├── go-wechaty.png
│ └── goland.png
├── examples
└── ding-dong-bot.go
├── go.mod
├── go.sum
├── wechaty-puppet-mock
└── puppet_mock.go
├── wechaty-puppet-service
├── ca.go
├── config.go
├── envvars.go
├── filebox.go
├── grpc.go
├── helper.go
├── options.go
├── puppet_service.go
├── puppet_service_test.go
├── resolver.go
└── service_endpoint.go
├── wechaty-puppet
├── events
│ └── events.go
├── file-box
│ ├── file_box.go
│ ├── fileboxtype_string.go
│ ├── testdata
│ │ └── .gitignore
│ └── type.go
├── filebox
│ ├── file_box.go
│ ├── file_box_base64.go
│ ├── file_box_file.go
│ ├── file_box_qrcode.go
│ ├── file_box_stream.go
│ ├── file_box_test.go
│ ├── file_box_unknown.go
│ ├── file_box_url.go
│ ├── file_box_uuid.go
│ ├── file_box_uuid_test.go
│ ├── testdata
│ │ └── dchaofei.txt
│ ├── type.go
│ └── type_string.go
├── helper
│ ├── array.go
│ ├── async.go
│ ├── base64.go
│ ├── file.go
│ ├── file_test.go
│ ├── fix_unknown_message.go
│ ├── http_client.go
│ ├── parase_recalled_msg.go
│ └── testdata
│ │ └── a.txt
├── log
│ └── log.go
├── memory-card
│ ├── memory_card.go
│ ├── memory_card_test.go
│ ├── storage
│ │ ├── backend.go
│ │ ├── file.go
│ │ ├── file_test.go
│ │ ├── nop.go
│ │ └── testdata
│ │ │ └── .gitignore
│ └── testdata
│ │ └── .gitignore
├── message_adapter.go
├── option.go
├── puppet.go
└── schemas
│ ├── contact.go
│ ├── contactgender_string.go
│ ├── contacttype_string.go
│ ├── events.go
│ ├── friendship.go
│ ├── friendshiptype_string.go
│ ├── image.go
│ ├── imagetype_string.go
│ ├── location.go
│ ├── message.go
│ ├── messagetype_string.go
│ ├── mini_program.go
│ ├── payload.go
│ ├── payloadtype_string.go
│ ├── puppet.go
│ ├── puppeteventname_string.go
│ ├── room.go
│ ├── room_invitation.go
│ ├── scanstatus_string.go
│ ├── type.go
│ └── url_link.go
└── wechaty
├── accessory.go
├── config.go
├── config
└── config.go
├── event.go
├── factory
├── config.go
├── contact.go
├── friendship.go
├── image.go
├── message.go
├── room.go
├── room_invitation.go
├── tag.go
└── url_link.go
├── interface
├── accessory.go
├── contact.go
├── contact_self.go
├── friendship.go
├── image.go
├── message.go
├── mini_program.go
├── room.go
├── room_invitation.go
├── tag.go
├── url_link.go
└── wechaty.go
├── option.go
├── plugin.go
├── user
├── config.go
├── contact.go
├── contact_self.go
├── friendship.go
├── image.go
├── location.go
├── message.go
├── mini_program.go
├── room.go
├── room_invitation.go
├── tag.go
└── url_link.go
├── wechaty.go
└── wechaty_test.go
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 |
3 | # For more configuration items, please refer to: https://docs.codeclimate.com/docs/maintainability#section-checks
4 | checks:
5 | argument-count: # 方法或函数最多参数个数,过多请考虑通过结构体传递
6 | config:
7 | threshold: 6
8 | complex-logic: # 难以理解的布尔逻辑,过多请考虑 switch 或拆分
9 | config:
10 | threshold: 4
11 | file-lines: # 文件最多行数,过多请拆分相关文件
12 | config:
13 | threshold: 1000
14 | method-complexity: # 函数和方法的逻辑复杂度
15 | config:
16 | threshold: 14
17 | method-count: # 结构体的方法限制
18 | config:
19 | threshold: 60
20 | method-lines: # 单个方法最多行数,过多请进行拆分
21 | config:
22 | threshold: 45
23 | nested-control-flow: # 深度嵌套的控制结构,请尽快返回结果,避免深度嵌套
24 | config:
25 | threshold: 6
26 | return-statements: # 函数或方法返回次数,过多请考虑拆分
27 | config:
28 | threshold: 12
29 | similar-code: # 相似代码检查
30 | config:
31 | threshold: 70
32 | identical-code: # 相同代码检查
33 | config:
34 | threshold: 25
35 |
36 | plugins:
37 | # "Gofmt's style is no one's favorite, yet gofmt is everyone's favorite." - The Go Proverbs
38 | gofmt:
39 | enabled: true
40 | golint:
41 | enabled: true
42 | govet:
43 | enabled: true
44 |
45 | # Excluded folders or files
46 | exclude_patterns:
47 | - examples
48 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | max_line_length = 0
14 | trim_trailing_whitespace = false
15 |
16 | # 4 tab indentation
17 | [Makefile]
18 | indent_style = tab
19 | indent_size = 4
20 |
21 | [{*.go,go.mod,go.sum}]
22 | indent_style = tab
23 | indent_size = 4
24 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | #
2 | # https://help.github.com/articles/about-codeowners/
3 | #
4 |
5 | * @wechaty/go
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug, question
6 | assignees: dchaofei, dingdayu
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Runtime environment**
27 | - OS: [e.g. Win,MacOS,Linux; darwin,freebsd]
28 | - ARCH: [e.g. amd64,arm]
29 | - Puppet: [e.g. hostie, padplus]
30 | - Version [e.g. v0.1.2]
31 |
32 | **Console output**
33 | Please put the detailed log in the label below:
34 |
35 |
36 |
37 |
38 | **Additional context**
39 | Add any other context about the problem here.
40 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: "[Feature]"
5 | labels: enhancement
6 | assignees: dchaofei, dingdayu, huan
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Please rate Feature**
20 | Please consider the scope of application and the difficulty of development.
21 |
22 | **Additional context**
23 | Add any other context or screenshots about the feature request here.
24 |
--------------------------------------------------------------------------------
/.github/workflows/codeclimate.yml:
--------------------------------------------------------------------------------
1 | name: Codeclimate
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 |
7 | jobs:
8 | build:
9 | name: coverage
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Set up Go 1.18
13 | uses: actions/setup-go@v1
14 | with:
15 | go-version: 1.18
16 | id: go
17 |
18 | - name: Check out code into the Go module directory
19 | uses: actions/checkout@v2
20 |
21 | - name: Codeclimate
22 | run: |
23 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./test-reporter
24 | chmod +x ./test-reporter
25 | ./test-reporter before-build
26 | go test -coverprofile c.out `go list ./... | grep -v /vendor/` -v -count=1 -coverpkg=./...
27 | sed -i "s/github.com\/wechaty\/go-wechaty\///g" c.out
28 | ./test-reporter format-coverage -t gocov
29 | ./test-reporter upload-coverage
30 | env:
31 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
32 |
--------------------------------------------------------------------------------
/.github/workflows/dispatch.yml:
--------------------------------------------------------------------------------
1 | name: Send Dispatch
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 |
9 | jobs:
10 | send:
11 | name: Send Dispatch
12 | runs-on: ubuntu-latest
13 | steps:
14 |
15 | # Dispatch to update go-wechaty-getting-started deps.
16 | - name: Repository Dispatch
17 | uses: peter-evans/repository-dispatch@v1.1.1
18 | with:
19 | token: ${{ secrets.REPO_ACCESS_TOKEN }}
20 | repository: wechaty/go-wechaty-getting-started
21 | event-type: updatedeps
22 | client-payload: '{"tag":"${{ github.ref_name }}"}'
23 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | name: Test
8 | runs-on: ubuntu-latest
9 | steps:
10 |
11 | - name: Set up Go 1.18
12 | uses: actions/setup-go@v1
13 | with:
14 | go-version: 1.18
15 | id: go
16 |
17 | - name: Check out code into the Go module directory
18 | uses: actions/checkout@v2
19 |
20 | - name: Install dependencies
21 | run: make install
22 |
23 | - name: Go Vet
24 | run: |
25 | go mod download
26 | go vet ./...
27 |
28 | - name: Go Test
29 | run: make test
30 |
31 | - name: Build
32 | run: go build -o ding-dong -v ./examples/ding-dong-bot.go
33 |
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 | .idea/
17 | .vscode
18 | .DS_Store
19 |
20 | coverage.out
21 | report.json
22 | tests_report.xml
23 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[go]": {
3 | "editor.formatOnSave": false,
4 | },
5 | "editor.fontFamily": "'Fira Code iScript', Consolas, 'Courier New', monospace",
6 | "editor.fontLigatures": true,
7 |
8 | "editor.tokenColorCustomizations": {
9 | "textMateRules": [
10 | {
11 | "scope": [
12 | //following will be in italics (=Pacifico)
13 | "comment",
14 | // "entity.name.type.class", //class names
15 | "keyword", //import, export, return…
16 | "support.class.builtin.js", //String, Number, Boolean…, this, super
17 | "storage.modifier", //static keyword
18 | "storage.type.class.js", //class keyword
19 | "storage.type.function.js", // function keyword
20 | "storage.type.js", // Variable declarations
21 | "keyword.control.import.js", // Imports
22 | "keyword.control.from.js", // From-Keyword
23 | "entity.name.type.js", // new … Expression
24 | "keyword.control.flow.js", // await
25 | "keyword.control.conditional.js", // if
26 | "keyword.control.loop.js", // for
27 | "keyword.operator.new.js", // new
28 | ],
29 | "settings": {
30 | "fontStyle": "italic",
31 | },
32 | },
33 | {
34 | "scope": [
35 | //following will be excluded from italics (My theme (Monokai dark) has some defaults I don't want to be in italics)
36 | "invalid",
37 | "keyword.operator",
38 | "constant.numeric.css",
39 | "keyword.other.unit.px.css",
40 | "constant.numeric.decimal.js",
41 | "constant.numeric.json",
42 | "entity.name.type.class.js"
43 | ],
44 | "settings": {
45 | "fontStyle": "",
46 | },
47 | }
48 | ]
49 | },
50 |
51 | "files.exclude": {
52 | "dist/": true,
53 | "doc/": true,
54 | "node_modules/": true,
55 | "package/": true,
56 | },
57 | "alignment": {
58 | "operatorPadding": "right",
59 | "indentBase": "firstline",
60 | "surroundSpace": {
61 | "colon": [1, 1], // The first number specify how much space to add to the left, can be negative. The second number is how much space to the right, can be negative.
62 | "assignment": [1, 1], // The same as above.
63 | "arrow": [1, 1], // The same as above.
64 | "comment": 2, // Special how much space to add between the trailing comment and the code.
65 | // If this value is negative, it means don't align the trailing comment.
66 | }
67 | },
68 | "editor.formatOnSave": false,
69 | "python.pythonPath": "python3",
70 | "eslint.validate": [
71 | "javascript",
72 | "typescript",
73 | ],
74 | }
75 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | Wechaty is a community driven open source project and we welcome any contributor.
4 |
5 | We have also prepared a nomination award, which can be apply after joining the organization.
6 |
7 | ## Before
8 |
9 | ### Sign the CLA
10 |
11 | Click the Sign in with Github to agree button to sign the CLA. See an example [here](https://cla-assistant.io/wechaty/go-wechaty).
12 |
13 | [Why CLA?](https://qastack.cn/software/168020/how-signing-out-a-cla-prevents-legal-issues-in-open-source-projects)
14 |
15 | ### Environment configuration
16 |
17 | You also need to begin to prepare the Go development environment.
18 |
19 | 1. download archive
20 | 2. extract archive
21 | 3. configure the path to an environment variable
22 |
23 | Yes, it is so simple, you can check it [here](https://golang.org/doc/install)
24 |
25 | ## Action
26 |
27 | Action is the best inspiration, contribution is the best result.
28 |
29 | > Yes, this sentence was just made up by me. I hope express to you that the community is inclusive and open, and it is a relaxed environment.
30 |
31 | ### Choose a Topic
32 |
33 | If you find a bug in the code, or something that can be improved, or invalid and redundant code; you can fork the project into your own repository, modify the code, and then submit the PR.
34 |
35 | However, if you modify multiple codes at the same time, please submit them in the form of a theme, so that we can discuss separately and merge into the trunk in stages.
36 |
37 | ### How to Write Go Code
38 |
39 | If you are new to Go, I hope [](https://golang.org/doc/code.html) can help you.
40 |
41 | If you already have relevant experience, hope the following list can unify our coding style:
42 | 1. Effective Go: [https://golang.google.cn/doc/effective\_go.html](https://golang.google.cn/doc/effective\_go.html)
43 | 2. Uber Go Style Guide: [https://github.com/uber-go/guide/blob/master/style.md](https://github.com/uber-go/guide/blob/master/style.md)([译文](https://github.com/gocn/translator/blob/master/2019/w38_uber_go_style_guide.md))
44 | 3. Go Code Review Comments: [https://github.com/golang/go/wiki/CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments)
45 |
46 | At the same time, we have configured related robots on PR to check the syntax and code style of the code.
47 |
48 | ## After
49 |
50 | ### Join Github Org Team
51 |
52 | If you have two PRs that are merged and valid, you will be invited to join Team, of course, the choice is yours.
53 |
54 | ### Nomination
55 |
56 | After joining the github team, please submit an application in the issue below: [Wechaty Contributors Nomination](https://github.com/wechaty/PMC/issues/16)
57 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2018 Wechaty Contributors
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/MAINTAINERS:
--------------------------------------------------------------------------------
1 | Huan LI (李卓桓)
2 | Xiaoyu DING (丁小雨)
3 | Bojie LI (李博杰)
4 | Chaofei DING (丁超飞)
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Go Wechaty
2 | #
3 | # GitHb: https://github.com/wechaty/python-wechaty
4 | # Author: Huan LI git.io/zixia
5 | #
6 |
7 | SOURCE_GLOB=$(wildcard bin/*.go src/**/*.go tests/**/*.go examples/*.go)
8 | VERSION=$(shell cat VERSION)
9 |
10 | .PHONY: all
11 | all : clean lint
12 |
13 | .PHONY: clean
14 | clean:
15 | rm -fr dist/*
16 | echo "clean what?"
17 |
18 | .PHONY: lint
19 | lint: golint
20 |
21 | .PHONY: golint
22 | golint:
23 | ~/go/bin/golint wechaty
24 | ~/go/bin/golint wechaty-puppet
25 | ~/go/bin/golint wechaty-puppet-service
26 |
27 | .PHONY: install
28 | install:
29 | go install golang.org/x/lint/golint@latest
30 |
31 | .PHONY: gotest
32 | gotest:
33 | go test `go list ./... | grep -v /vendor/` -count=1 -coverpkg=./...
34 |
35 | .PHONY: test
36 | test: golint gotest
37 |
38 | .PHONY: bot
39 | bot:
40 | go run examples/ding-dong-bot.go
41 |
42 | .PHONY: version
43 | version:
44 | @newVersion=$$(awk -F. '{print $$1"."$$2"."$$3+1}' < VERSION) \
45 | && echo $${newVersion} > VERSION \
46 | && echo VERSION := \'$${newVersion}\' > src/version.go \
47 | && git add VERSION src/version.py \
48 | && git commit -m "$${newVersion}" > /dev/null \
49 | && git tag "v$${newVersion}" \
50 | && echo "Bumped version to $${newVersion}"
51 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Go Wechaty Chatbot SDK
2 | Copyright 2020 Wechaty Contributors.
3 |
4 | This product includes software developed at
5 | The Wechaty Organization (https://github.com/wechaty).
6 |
7 | This software contains code derived from the Stackoverflow,
8 | including various modifications by GitHub.
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-wechaty
2 |
3 | 
4 | [](https://github.com/wechaty/go-wechaty/actions?query=workflow%3AGo)
5 | [](https://codeclimate.com/github/wechaty/go-wechaty/maintainability)
6 |
7 | 
8 |
9 | [](https://github.com/wechaty/go-wechaty-getting-started)
10 | [](https://github.com/wechaty/go-wechaty)
11 |
12 | ## Connecting Chatbots
13 |
14 | [](https://github.com/Wechaty/wechaty)
15 |
16 | Wechaty is a RPA SDK for Wechat **Individual** Account that can help you create a chatbot in 6 lines of Go.
17 |
18 | ## Voice of the Developers
19 |
20 | > "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)
21 | > — @Gcaufy, Tencent Engineer, Author of [WePY](https://github.com/Tencent/wepy)
22 | >
23 | > "太好用,好用的想哭"
24 | > — @xinbenlv, Google Engineer, Founder of HaoShiYou.org
25 | >
26 | > "最好的微信开发库" [link](http://weibo.com/3296245513/Ec4iNp9Ld?type=comment)
27 | > — @Jarvis, Baidu Engineer
28 | >
29 | > "Wechaty让运营人员更多的时间思考如何进行活动策划、留存用户,商业变现" [link](http://mp.weixin.qq.com/s/dWHAj8XtiKG-1fIS5Og79g)
30 | > — @lijiarui, Founder & CEO of Juzi.BOT.
31 | >
32 | > "If you know js ... try Wechaty, it's easy to use."
33 | > — @Urinx Uri Lee, Author of [WeixinBot(Python)](https://github.com/Urinx/WeixinBot)
34 |
35 | See more at [Wiki:Voice Of Developer](https://github.com/Wechaty/wechaty/wiki/Voice%20Of%20Developer)
36 |
37 | ## Join Us
38 |
39 | 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 _go wechaty_, join our **Wechaty Go Developers' Home**.
40 |
41 | 
42 |
43 | Scan now, because other Wechaty Go developers want to talk with you too! (secret code: _go wechaty_)
44 |
45 | ## Usage
46 |
47 | ```go
48 | package main
49 |
50 | import (
51 | "fmt"
52 | "github.com/wechaty/go-wechaty/wechaty"
53 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
54 | "github.com/wechaty/go-wechaty/wechaty/user"
55 | )
56 |
57 | func main() {
58 | wechaty.NewWechaty().
59 | OnScan(func(context *wechaty.Context, qrCode string, status schemas.ScanStatus, data string) {
60 | fmt.Printf("Scan QR Code to login: %s\nhttps://wechaty.github.io/qrcode/%s\n", status, qrCode)
61 | }).
62 | OnLogin(func(context *wechaty.Context, user *user.ContactSelf) {
63 | fmt.Printf("User %s logined\n", user)
64 | }).
65 | OnMessage(func(context *wechaty.Context, message *user.Message) {
66 | fmt.Printf("Message: %s\n", message)
67 | }).DaemonStart()
68 | }
69 | ```
70 |
71 | ## Requirements
72 |
73 | 1. Go 1.18+
74 |
75 | ## Install
76 |
77 | ```shell
78 | # go get wechaty
79 |
80 | go get github.com/wechaty/go-wechaty
81 | ```
82 |
83 | ## Development
84 |
85 | ```sh
86 | make install
87 | make test
88 | ```
89 |
90 | ## QA
91 | - wechaty-puppet-service: WECHATY_PUPPET_SERVICE_TOKEN not found ?
92 | - go-wechaty is the go language implementation of [wechaty](https://github.com/wechaty/wechaty) (TypeScript). Puppet is required to start wechaty, but it is currently known that puppets are written in TypeScript language. In order to enable go-wechaty to use these puppets, we can use wechaty-gateway to convert puppets into grpc service, let go-wechaty connect to the grpc service, go-wechaty -> wechaty-gateway -> puppet, document: https://wechaty.js.org/docs/puppet-services/diy/
93 | - puppet list: https://wechaty.js.org/docs/puppet-providers/
94 |
95 | ## See Also
96 |
97 | - [Learn Go in 12 Minutes](https://www.youtube.com/watch?v=C8LgvuEBraI)
98 | - [How to Write Go Code](https://golang.org/doc/code.html)
99 | - [Journey from OO language to Golang - Sergey Kibish @DevFest Switzerland 2018](https://www.youtube.com/watch?v=1ZjvhGfpwJ8)
100 | - [The Go Blog - Publishing Go Modules](https://blog.golang.org/publishing-go-modules)
101 | - [Effective Go](https://golang.org/doc/effective_go.html)
102 |
103 | ### Golang for Node.js Developer
104 |
105 | - [Golang for Node.js Developers - Examples of Golang examples compared to Node.js for learning](https://github.com/miguelmota/golang-for-nodejs-developers)
106 | - [Learning Go as a Node.js Developer](https://nemethgergely.com/learning-go-as-a-nodejs-developer/)
107 | - [Golang Tutorial for Node.js Developers](https://blog.risingstack.com/golang-tutorial-for-nodejs-developers-getting-started/)
108 |
109 | ## History
110 |
111 | ### master
112 |
113 | ### v0.4 (Jun 19, 2020)
114 |
115 | Go Wechaty Scala Wechaty **BETA** Released!
116 |
117 | Read more from our Multi-language Wechaty Beta Release event from our blog:
118 |
119 | - [Multi Language Wechaty Beta Release Announcement!](https://wechaty.js.org/2020/06/19/multi-language-wechaty-beta-release/)
120 |
121 | ### v0.1 (Apr 03 2020)
122 |
123 | 1. Welcome our second and third Go Wechaty contributors:
124 | - Bojie LI (李博杰) [#9](https://github.com/wechaty/go-wechaty/pull/9)
125 | - Chaofei DING (丁超飞) [#20](https://github.com/wechaty/go-wechaty/pull/20)
126 | 1. Enable [GitHub Actions](https://github.com/wechaty/go-wechaty/actions?query=workflow%3AGo)
127 | 1. Enable linting: [golint](https://github.com/golang/lint)
128 | 1. Enable testing: [testing](https://golang.org/pkg/testing/)
129 | 1. Add Makefile for easy developing
130 | 1. Re-structure module directories: from `src/wechaty` to `wechaty`
131 | 1. Rename example bot to `examples/ding-dong-bot.go`
132 |
133 | ### v0.0.1 (Mar 12, 2020)
134 |
135 | 1. Project created.
136 | 1. Welcome our first Go Wechaty contributor:
137 | - Xiaoyu DING (丁小雨) [#2](https://github.com/wechaty/go-wechaty/pull/2)
138 |
139 | ## Related Projects
140 |
141 | - [Wechaty](https://github.com/wechaty/wechaty) - Conversatioanl AI Chatot SDK for Wechaty Individual Accounts (TypeScript)
142 | - [Python Wechaty](https://github.com/wechaty/python-wechaty) - Python WeChaty Conversational AI Chatbot SDK for Wechat Individual Accounts (Python)
143 | - [Go Wechaty](https://github.com/wechaty/go-wechaty) - Go WeChaty Conversational AI Chatbot SDK for Wechat Individual Accounts (Go)
144 | - [Java Wechaty](https://github.com/wechaty/java-wechaty) - Java WeChaty Conversational AI Chatbot SDK for Wechat Individual Accounts (Java)
145 | - [Scala Wechaty](https://github.com/wechaty/scala-wechaty) - Scala WeChaty Conversational AI Chatbot SDK for WechatyIndividual Accounts (Scala)
146 |
147 | ## Badge
148 |
149 | [](https://github.com/wechaty/go-wechaty)
150 |
151 | ```md
152 | [](https://github.com/wechaty/go-wechaty)
153 | ```
154 |
155 | ## Contributors
156 |
157 | [](https://sourcerer.io/fame/huan/wechaty/go-wechaty/links/0)
158 | [](https://sourcerer.io/fame/huan/wechaty/go-wechaty/links/1)
159 | [](https://sourcerer.io/fame/huan/wechaty/go-wechaty/links/2)
160 | [](https://sourcerer.io/fame/huan/wechaty/go-wechaty/links/3)
161 | [](https://sourcerer.io/fame/huan/wechaty/go-wechaty/links/4)
162 | [](https://sourcerer.io/fame/huan/wechaty/go-wechaty/links/5)
163 | [](https://sourcerer.io/fame/huan/wechaty/go-wechaty/links/6)
164 | [](https://sourcerer.io/fame/huan/wechaty/go-wechaty/links/7)
165 |
166 | 1. [@SilkageNet](https://github.com/SilkageNet) - Bojie LI (李博杰)
167 | 1. [@huan](https://github.com/huan) - Huan LI (李卓桓)
168 |
169 | ## Creators
170 |
171 | - [@dchaofei](https://github.com/dchaofei) - Chaofei DING (丁超飞)
172 | - [@dingdayu](https://github.com/dingdayu) - Xiaoyu DING (丁小雨)
173 |
174 | ## Copyright & License
175 |
176 | - Code & Docs © 2020 Wechaty Contributors
177 | - Code released under the Apache-2.0 License
178 | - Docs released under Creative Commons
179 |
180 | ## Thanks
181 |
182 |
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 0.2.0
2 |
--------------------------------------------------------------------------------
/docs/images/go-wechaty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wechaty/go-wechaty/dd2d312935802edc721b2b7c1719713e39f0bdab/docs/images/go-wechaty.png
--------------------------------------------------------------------------------
/docs/images/goland.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wechaty/go-wechaty/dd2d312935802edc721b2b7c1719713e39f0bdab/docs/images/goland.png
--------------------------------------------------------------------------------
/examples/ding-dong-bot.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/url"
7 | "os"
8 | "time"
9 |
10 | "github.com/mdp/qrterminal/v3"
11 | "github.com/wechaty/go-wechaty/wechaty"
12 | wp "github.com/wechaty/go-wechaty/wechaty-puppet"
13 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
14 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
15 | "github.com/wechaty/go-wechaty/wechaty/user"
16 | )
17 |
18 | func main() {
19 | var bot = wechaty.NewWechaty(wechaty.WithPuppetOption(wp.Option{
20 | Token: "",
21 | }))
22 |
23 | bot.OnScan(onScan).OnLogin(func(ctx *wechaty.Context, user *user.ContactSelf) {
24 | fmt.Printf("User %s logined\n", user.Name())
25 | }).OnMessage(onMessage).OnLogout(func(ctx *wechaty.Context, user *user.ContactSelf, reason string) {
26 | fmt.Printf("User %s logouted: %s\n", user, reason)
27 | })
28 |
29 | bot.DaemonStart()
30 | }
31 |
32 | func onMessage(ctx *wechaty.Context, message *user.Message) {
33 | log.Println(message)
34 |
35 | if message.Self() {
36 | log.Println("Message discarded because its outgoing")
37 | return
38 | }
39 |
40 | if message.Age() > 2*60*time.Second {
41 | log.Println("Message discarded because its TOO OLD(than 2 minutes)")
42 | return
43 | }
44 |
45 | if message.Type() != schemas.MessageTypeText || message.Text() != "#ding" {
46 | log.Println("Message discarded because it does not match #ding")
47 | return
48 | }
49 |
50 | // 1. reply text 'dong'
51 | _, err := message.Say("dong")
52 | if err != nil {
53 | log.Println(err)
54 | return
55 | }
56 | log.Println("REPLY with text: dong")
57 |
58 | // 2. reply image(qrcode image)
59 | fileBox := filebox.FromUrl("https://wechaty.github.io/wechaty/images/bot-qr-code.png")
60 | _, err = message.Say(fileBox)
61 | if err != nil {
62 | log.Println(err)
63 | return
64 | }
65 |
66 | log.Printf("REPLY with image: %s\n", fileBox)
67 |
68 | // 3. reply url link
69 | urlLink := user.NewUrlLink(&schemas.UrlLinkPayload{
70 | Description: "Go Wechaty is a Conversational SDK for Chatbot Makers Written in Go",
71 | ThumbnailUrl: "https://wechaty.js.org/img/icon.png",
72 | Title: "wechaty/go-wechaty",
73 | Url: "https://github.com/wechaty/go-wechaty",
74 | })
75 | _, err = message.Say(urlLink)
76 | if err != nil {
77 | log.Println(err)
78 | return
79 | }
80 | log.Printf("REPLY with urlLink: %s\n", urlLink)
81 | }
82 |
83 | func onScan(ctx *wechaty.Context, qrCode string, status schemas.ScanStatus, data string) {
84 | if status == schemas.ScanStatusWaiting || status == schemas.ScanStatusTimeout {
85 | qrterminal.GenerateHalfBlock(qrCode, qrterminal.L, os.Stdout)
86 |
87 | qrcodeImageUrl := fmt.Sprintf("https://wechaty.js.org/qrcode/%s", url.QueryEscape(qrCode))
88 | fmt.Printf("onScan: %s - %s\n", status, qrcodeImageUrl)
89 | return
90 | }
91 | fmt.Printf("onScan: %s\n", status)
92 | }
93 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/wechaty/go-wechaty
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/hashicorp/golang-lru v0.5.4
7 | github.com/lucsky/cuid v1.0.2
8 | github.com/mdp/qrterminal/v3 v3.0.0
9 | github.com/otiai10/opengraph v1.1.1
10 | github.com/sirupsen/logrus v1.9.0
11 | github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
12 | github.com/tuotoo/qrcode v0.0.0-20190222102259-ac9c44189bf2
13 | github.com/wechaty/go-grpc v1.5.2
14 | google.golang.org/grpc v1.45.0
15 | google.golang.org/protobuf v1.27.1
16 | )
17 |
18 | require (
19 | github.com/golang/protobuf v1.5.2 // indirect
20 | github.com/google/uuid v1.1.2 // indirect
21 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.0 // indirect
22 | github.com/maruel/rs v0.0.0-20150922171536-2c81c4312fe4 // indirect
23 | github.com/stretchr/testify v1.8.0 // indirect
24 | github.com/willf/bitset v1.1.10 // indirect
25 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
26 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
27 | golang.org/x/text v0.3.7 // indirect
28 | google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
29 | rsc.io/qr v0.2.0 // indirect
30 | )
31 |
--------------------------------------------------------------------------------
/wechaty-puppet-mock/puppet_mock.go:
--------------------------------------------------------------------------------
1 | package wechaty_puppet_mock
2 |
3 | import (
4 | wechatyPuppet "github.com/wechaty/go-wechaty/wechaty-puppet"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
6 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
7 | )
8 |
9 | var _ wechatyPuppet.IPuppetAbstract = &PuppetMock{}
10 |
11 | type PuppetMock struct {
12 | *wechatyPuppet.Puppet
13 | }
14 |
15 | func (p *PuppetMock) MessageLocation(messageID string) (*schemas.LocationPayload, error) {
16 | //TODO implement me
17 | panic("implement me")
18 | }
19 |
20 | func (p *PuppetMock) MessageSendLocation(conversationID string, payload *schemas.LocationPayload) (string, error) {
21 | //TODO implement me
22 | panic("implement me")
23 | }
24 |
25 | func NewPuppetMock(option wechatyPuppet.Option) (*PuppetMock, error) {
26 | puppetAbstract, err := wechatyPuppet.NewPuppet(option)
27 | if err != nil {
28 | return nil, err
29 | }
30 | puppetMock := &PuppetMock{
31 | Puppet: puppetAbstract,
32 | }
33 | puppetAbstract.SetPuppetImplementation(puppetMock)
34 | return puppetMock, nil
35 | }
36 |
37 | func (p *PuppetMock) Start() error {
38 | go func() {
39 | // emit scan
40 | p.Emit(schemas.PuppetEventNameScan, &schemas.EventScanPayload{
41 | BaseEventPayload: schemas.BaseEventPayload{},
42 | Status: schemas.ScanStatusWaiting,
43 | QrCode: "https://not-exist.com",
44 | })
45 | }()
46 | return nil
47 | }
48 |
49 | func (p PuppetMock) MessageImage(messageID string, imageType schemas.ImageType) (*filebox.FileBox, error) {
50 | panic("implement me")
51 | }
52 |
53 | func (p PuppetMock) FriendshipRawPayload(friendshipID string) (*schemas.FriendshipPayload, error) {
54 | panic("implement me")
55 | }
56 |
57 | func (p PuppetMock) FriendshipAccept(friendshipID string) error {
58 | panic("implement me")
59 | }
60 |
61 | func (p PuppetMock) RoomInvitationRawPayload(roomInvitationID string) (*schemas.RoomInvitationPayload, error) {
62 | panic("implement me")
63 | }
64 |
65 | func (p PuppetMock) RoomInvitationAccept(roomInvitationID string) error {
66 | panic("implement me")
67 | }
68 |
69 | func (p PuppetMock) MessageSendText(conversationID string, text string, mentionIdList ...string) (string, error) {
70 | panic("implement me")
71 | }
72 |
73 | func (p PuppetMock) MessageSendContact(conversationID string, contactID string) (string, error) {
74 | panic("implement me")
75 | }
76 |
77 | func (p PuppetMock) MessageSendFile(conversationID string, fileBox *filebox.FileBox) (string, error) {
78 | panic("implement me")
79 | }
80 |
81 | func (p PuppetMock) MessageSendURL(conversationID string, urlLinkPayload *schemas.UrlLinkPayload) (string, error) {
82 | panic("implement me")
83 | }
84 |
85 | func (p PuppetMock) MessageSendMiniProgram(conversationID string, urlLinkPayload *schemas.MiniProgramPayload) (string, error) {
86 | panic("implement me")
87 | }
88 |
89 | func (p *PuppetMock) Stop() {
90 | panic("implement me")
91 | }
92 |
93 | func (p *PuppetMock) Logout() error {
94 | panic("implement me")
95 | }
96 |
97 | func (p *PuppetMock) Ding(data string) {
98 | panic("implement me")
99 | }
100 |
101 | func (p *PuppetMock) SetContactAlias(contactID string, alias string) error {
102 | panic("implement me")
103 | }
104 |
105 | func (p *PuppetMock) ContactAlias(contactID string) (string, error) {
106 | panic("implement me")
107 | }
108 |
109 | func (p *PuppetMock) ContactList() ([]string, error) {
110 | panic("implement me")
111 | }
112 |
113 | func (p *PuppetMock) ContactQRCode(contactID string) (string, error) {
114 | panic("implement me")
115 | }
116 |
117 | func (p *PuppetMock) SetContactAvatar(contactID string, fileBox *filebox.FileBox) error {
118 | panic("implement me")
119 | }
120 |
121 | func (p *PuppetMock) ContactAvatar(contactID string) (*filebox.FileBox, error) {
122 | panic("implement me")
123 | }
124 |
125 | func (p *PuppetMock) ContactRawPayload(contactID string) (*schemas.ContactPayload, error) {
126 | panic("implement me")
127 | }
128 |
129 | func (p *PuppetMock) SetContactSelfName(name string) error {
130 | panic("implement me")
131 | }
132 |
133 | func (p *PuppetMock) ContactSelfQRCode() (string, error) {
134 | panic("implement me")
135 | }
136 |
137 | func (p *PuppetMock) SetContactSelfSignature(signature string) error {
138 | panic("implement me")
139 | }
140 |
141 | func (p *PuppetMock) MessageMiniProgram(messageID string) (*schemas.MiniProgramPayload, error) {
142 | panic("implement me")
143 | }
144 |
145 | func (p *PuppetMock) MessageContact(messageID string) (string, error) {
146 | panic("implement me")
147 | }
148 |
149 | func (p *PuppetMock) MessageRecall(messageID string) (bool, error) {
150 | panic("implement me")
151 | }
152 |
153 | func (p *PuppetMock) MessageFile(id string) (*filebox.FileBox, error) {
154 | panic("implement me")
155 | }
156 |
157 | func (p *PuppetMock) MessageRawPayload(id string) (*schemas.MessagePayload, error) {
158 | panic("implement me")
159 | }
160 |
161 | func (p *PuppetMock) MessageURL(messageID string) (*schemas.UrlLinkPayload, error) {
162 | panic("implement me")
163 | }
164 |
165 | func (p *PuppetMock) RoomRawPayload(id string) (*schemas.RoomPayload, error) {
166 | panic("implement me")
167 | }
168 |
169 | func (p *PuppetMock) RoomList() ([]string, error) {
170 | panic("implement me")
171 | }
172 |
173 | func (p *PuppetMock) RoomDel(roomID, contactID string) error {
174 | panic("implement me")
175 | }
176 |
177 | func (p *PuppetMock) RoomAvatar(roomID string) (*filebox.FileBox, error) {
178 | panic("implement me")
179 | }
180 |
181 | func (p *PuppetMock) RoomAdd(roomID, contactID string) error {
182 | panic("implement me")
183 | }
184 |
185 | func (p *PuppetMock) SetRoomTopic(roomID string, topic string) error {
186 | panic("implement me")
187 | }
188 |
189 | func (p *PuppetMock) RoomTopic(roomID string) (string, error) {
190 | panic("implement me")
191 | }
192 |
193 | func (p *PuppetMock) RoomCreate(contactIDList []string, topic string) (string, error) {
194 | panic("implement me")
195 | }
196 |
197 | func (p *PuppetMock) RoomQuit(roomID string) error {
198 | panic("implement me")
199 | }
200 |
201 | func (p *PuppetMock) RoomQRCode(roomID string) (string, error) {
202 | panic("implement me")
203 | }
204 |
205 | func (p *PuppetMock) RoomMemberList(roomID string) ([]string, error) {
206 | panic("implement me")
207 | }
208 |
209 | func (p *PuppetMock) RoomMemberRawPayload(roomID string, contactID string) (*schemas.RoomMemberPayload, error) {
210 | panic("implement me")
211 | }
212 |
213 | func (p *PuppetMock) SetRoomAnnounce(roomID, text string) error {
214 | panic("implement me")
215 | }
216 |
217 | func (p *PuppetMock) RoomAnnounce(roomID string) (string, error) {
218 | panic("implement me")
219 | }
220 |
221 | func (p *PuppetMock) FriendshipSearchPhone(phone string) (string, error) {
222 | panic("implement me")
223 | }
224 |
225 | func (p *PuppetMock) FriendshipSearchWeixin(weixin string) (string, error) {
226 | panic("implement me")
227 | }
228 |
229 | func (p *PuppetMock) FriendshipAdd(contactID, hello string) (err error) {
230 | panic("implement me")
231 | }
232 |
233 | func (p *PuppetMock) TagContactAdd(id, contactID string) (err error) {
234 | panic("implement me")
235 | }
236 |
237 | func (p *PuppetMock) TagContactRemove(id, contactID string) (err error) {
238 | panic("implement me")
239 | }
240 |
241 | func (p *PuppetMock) TagContactDelete(id string) (err error) {
242 | panic("implement me")
243 | }
244 |
245 | func (p *PuppetMock) TagContactList(contactID string) ([]string, error) {
246 | panic("implement me")
247 | }
248 |
249 | func (p *PuppetMock) MessageRawMiniProgramPayload(messageID string) (*schemas.MiniProgramPayload, error) {
250 | panic("implement me")
251 | }
252 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/ca.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | import "context"
4 |
5 | // TLSInsecureServerCertCommonName Common Name:
6 | // - Server Name Indication (SNI)
7 | // - Case insensitive
8 | // Wechaty Token format: `${SNI}/${UUIDv4}`
9 | const TLSInsecureServerCertCommonName = "insecure"
10 |
11 | // TLSCaCert Wechaty Certificate Authority Repo:
12 | /* https://github.com/wechaty/dotenv/tree/main/ca
13 |
14 | The SSL_ROOT_CERT is a root certificate generated by and for wechaty community.
15 |
16 | Because it's the default root cert for the puppet service,
17 | so all the Polyglot Wechaty SDK should set this cert to be trusted by default.
18 |
19 | Update:
20 | - Huan(202108): init, expired in 3650 days (after 2031/07)
21 | */
22 | const TLSCaCert = `-----BEGIN CERTIFICATE-----
23 | MIIFxTCCA62gAwIBAgIUYddLAoa8JnLzJ80l2u5vGuFsaEIwDQYJKoZIhvcNAQEL
24 | BQAwcjELMAkGA1UEBhMCVVMxFjAUBgNVBAgMDVNhbiBGcmFuY2lzY28xEjAQBgNV
25 | BAcMCVBhbG8gQWx0bzEQMA4GA1UECgwHV2VjaGF0eTELMAkGA1UECwwCQ0ExGDAW
26 | BgNVBAMMD3dlY2hhdHktcm9vdC1jYTAeFw0yMTA4MDkxNTQ4NTJaFw0zMTA4MDcx
27 | NTQ4NTJaMHIxCzAJBgNVBAYTAlVTMRYwFAYDVQQIDA1TYW4gRnJhbmNpc2NvMRIw
28 | EAYDVQQHDAlQYWxvIEFsdG8xEDAOBgNVBAoMB1dlY2hhdHkxCzAJBgNVBAsMAkNB
29 | MRgwFgYDVQQDDA93ZWNoYXR5LXJvb3QtY2EwggIiMA0GCSqGSIb3DQEBAQUAA4IC
30 | DwAwggIKAoICAQDulLjOZhzQ58TSQ7TfWNYgdtWhlc+5L9MnKb1nznVRhzAkZo3Q
31 | rPLRW/HDjlv2OEbt4nFLaQgaMmc1oJTUVGDBDlrzesI/lJh7z4eA/B0z8eW7f6Cw
32 | /TGc8lgzHvq7UIE507QYPhvfSejfW4Prw+90HJnuodriPdMGS0n9AR37JPdQm6sD
33 | iMFeEvhHmM2SXRo/o7bll8UDZi81DoFu0XuTCx0esfCX1W5QWEmAJ5oAdjWxJ23C
34 | lxI1+EjwBQKXGqp147VP9+pwpYW5Xxpy870kctPBHKjCAti8Bfo+Y6dyWz2UAd4w
35 | 4BFRD+18C/TgX+ECl1s9fsHMY15JitcSGgAIz8gQX1OelECaTMRTQfNaSnNW4LdS
36 | sXMQEI9WxAU/W47GCQFmwcJeZvimqDF1QtflHSaARD3O8tlbduYqTR81LJ63bPoy
37 | 9e1pdB6w2bVOTlHunE0YaGSJERALVc1xz40QpPGcZ52mNCb3PBg462RQc77yv/QB
38 | x/P2RC1y0zDUF2tP9J29gTatWq6+D4MhfEk2flZNyzAgJbDuT6KAIJGzOB1ZJ/MG
39 | o1gS13eTuZYw24LElrhd1PrR6OHK+lkyYzqUPYMulUg4HzaZIDclfHKwAC4lecKm
40 | zC5q9jJB4m4SKMKdzxvpIOfdahoqsZMg34l4AavWRqPTpwEU0C0dboNA/QIDAQAB
41 | o1MwUTAdBgNVHQ4EFgQU0rey3QPklTOgdhMJ9VIA6KbZ5bAwHwYDVR0jBBgwFoAU
42 | 0rey3QPklTOgdhMJ9VIA6KbZ5bAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
43 | AQsFAAOCAgEAx2uyShx9kLoB1AJ8x7Vf95v6PX95L/4JkJ1WwzJ9Dlf3BcCI7VH7
44 | Fp1dnQ6Ig7mFqSBDBAUUBWAptAnuqIDcgehI6XAEKxW8ZZRxD877pUNwZ/45tSC4
45 | b5U5y9uaiNK7oC3LlDCsB0291b3KSOtevMeDFoh12LcliXAkdIGGTccUxrH+Cyij
46 | cBOc+EKGJFBdLqcjLDU4M6QdMMMFOdfXyAOSpYuWGYqrxqvxQjAjvianEyMpNZWM
47 | lajggJqiPhfF67sZTB2yzvRTmtHdUq7x+iNOVonOBcCHu31aGxa9Py91XEr9jaIQ
48 | EBdl6sycLxKo8mxF/5tyUOns9+919aWNqTOUBmI15D68bqhhOVNyvsb7aVURIt5y
49 | 6A7Sj4gSBR9P22Ba6iFZgbvfLn0zKLzjlBonUGlSPf3rSIYUkawICtDyYPvK5mi3
50 | mANgIChMiOw6LYCPmmUVVAWU/tDy36kr9ZV9YTIZRYAkWswsJB340whjuzvZUVaG
51 | DgW45GPR6bGIwlFZeqCwXLput8Z3C8Sw9bE9vjlB2ZCpjPLmWV/WbDlH3J3uDjgt
52 | 9PoALW0sOPhHfYklH4/rrmsSWMYTUuGS/HqxrEER1vpIOOb0hIiAWENDT/mruq22
53 | VqO8MHX9ebjInSxPmhYOlrSZrOgEcogyMB4Z0SOtKVqPnkWmdR5hatU=
54 | -----END CERTIFICATE-----`
55 |
56 | type callCredToken struct {
57 | token string
58 | }
59 |
60 | func (r callCredToken) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
61 | return map[string]string{
62 | "authorization": "Wechaty " + r.token,
63 | }, nil
64 | }
65 |
66 | func (r callCredToken) RequireTransportSecurity() bool {
67 | return true
68 | }
69 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/config.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | import (
4 | logger "github.com/wechaty/go-wechaty/wechaty-puppet/log"
5 | )
6 |
7 | var log = logger.L.WithField("module", "wechaty-puppet-service")
8 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/envvars.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | import (
4 | "errors"
5 | "os"
6 | )
7 |
8 | // ErrTokenNotFound err token not found
9 | var ErrTokenNotFound = errors.New("wechaty-puppet-service: WECHATY_PUPPET_SERVICE_TOKEN not found")
10 |
11 | func envServiceToken(token string) (string, error) {
12 | if token != "" {
13 | return token, nil
14 | }
15 |
16 | token = os.Getenv("WECHATY_PUPPET_SERVICE_TOKEN")
17 | if token != "" {
18 | return token, nil
19 | }
20 |
21 | return "", ErrTokenNotFound
22 | }
23 |
24 | func envEndpoint(endpoint string) string {
25 | if endpoint != "" {
26 | return endpoint
27 | }
28 |
29 | endpoint = os.Getenv("WECHATY_PUPPET_SERVICE_ENDPOINT")
30 | if endpoint != "" {
31 | return endpoint
32 | }
33 |
34 | return ""
35 | }
36 |
37 | func envAuthority(authority string) string {
38 | if authority != "" {
39 | return authority
40 | }
41 |
42 | authority = os.Getenv("WECHATY_PUPPET_SERVICE_AUTHORITY")
43 | if authority != "" {
44 | return authority
45 | }
46 |
47 | return "api.chatie.io"
48 | }
49 |
50 | func envNoTLSInsecureClient(disable bool) bool {
51 | return disable || os.Getenv("WECHATY_PUPPET_SERVICE_NO_TLS_INSECURE_CLIENT") == "true"
52 | }
53 |
54 | func envTLSServerName(serverName string) string {
55 | if serverName != "" {
56 | return serverName
57 | }
58 |
59 | return os.Getenv("WECHATY_PUPPET_SERVICE_TLS_SERVER_NAME")
60 | }
61 |
62 | func envTLSCaCert(caCert string) string {
63 | if caCert != "" {
64 | return caCert
65 | }
66 | caCert = os.Getenv("WECHATY_PUPPET_SERVICE_TLS_CA_CERT")
67 | if caCert != "" {
68 | return caCert
69 | }
70 | return TLSCaCert
71 | }
72 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/filebox.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | pbwechaty "github.com/wechaty/go-grpc/wechaty"
7 | pbwechatypuppet "github.com/wechaty/go-grpc/wechaty/puppet"
8 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
9 | "github.com/wechaty/go-wechaty/wechaty-puppet/helper"
10 | "io"
11 | )
12 |
13 | // ErrNoName err no name
14 | var ErrNoName = errors.New("no name")
15 |
16 | // NewFileBoxFromMessageFileStream ...
17 | func NewFileBoxFromMessageFileStream(client pbwechaty.Puppet_MessageFileStreamClient) (*filebox.FileBox, error) {
18 | recv, err := client.Recv()
19 | if err != nil {
20 | return nil, err
21 | }
22 | name := recv.FileBoxChunk.GetName()
23 | if name == "" {
24 | return nil, ErrNoName
25 | }
26 |
27 | return filebox.FromStream(NewMessageFile(client), filebox.WithName(name)), nil
28 | }
29 |
30 | // MessageFile 把 grpc 流包装到 io.Reader 接口
31 | type MessageFile struct {
32 | client pbwechaty.Puppet_MessageFileStreamClient
33 | buffer bytes.Buffer
34 | done bool
35 | }
36 |
37 | // Read 把 grpc 流包装到 io.Reader 接口
38 | func (m *MessageFile) Read(p []byte) (n int, err error) {
39 | if m.done {
40 | return m.buffer.Read(p)
41 | }
42 |
43 | for {
44 | if m.buffer.Len() >= len(p) {
45 | break
46 | }
47 | recv, err := m.client.Recv()
48 | if err == io.EOF {
49 | m.done = true
50 | break
51 | }
52 | if err != nil {
53 | return 0, err
54 | }
55 | _, err = m.buffer.Write(recv.FileBoxChunk.GetData())
56 | if err != nil {
57 | return 0, err
58 | }
59 | }
60 | return m.buffer.Read(p)
61 | }
62 |
63 | // NewMessageFile ...
64 | func NewMessageFile(client pbwechaty.Puppet_MessageFileStreamClient) *MessageFile {
65 | return &MessageFile{
66 | client: client,
67 | buffer: bytes.Buffer{},
68 | done: false,
69 | }
70 | }
71 |
72 | // MessageSendFile 把 grpc 流包装到 io.Writer 接口
73 | type MessageSendFile struct {
74 | client pbwechaty.Puppet_MessageSendFileStreamClient
75 | fileBox *filebox.FileBox
76 | count int
77 | }
78 |
79 | // Write 把 grpc 流包装到 io.Writer 接口
80 | func (m *MessageSendFile) Write(p []byte) (n int, err error) {
81 | if len(p) == 0 {
82 | return 0, nil
83 | }
84 | fileDataRequest := &pbwechatypuppet.MessageSendFileStreamRequest{
85 | FileBoxChunk: &pbwechatypuppet.FileBoxChunk{
86 | Data: p,
87 | Name: nil,
88 | },
89 | }
90 | m.count++
91 | if err := m.client.Send(fileDataRequest); err != nil {
92 | return 0, err
93 | }
94 | return len(p), nil
95 | }
96 |
97 | // ToMessageSendFileWriter 把 grpc 流包装到 io.Writer 接口
98 | func ToMessageSendFileWriter(client pbwechaty.Puppet_MessageSendFileStreamClient, conversationID string, fileBox *filebox.FileBox) (io.Writer, error) {
99 | // 发送 conversationID
100 | {
101 | idRequest := &pbwechatypuppet.MessageSendFileStreamRequest{
102 | ConversationId: &conversationID,
103 | }
104 | if err := client.Send(idRequest); err != nil {
105 | return nil, err
106 | }
107 | }
108 |
109 | // 发送 fileName
110 | {
111 | fileNameRequest := &pbwechatypuppet.MessageSendFileStreamRequest{
112 | FileBoxChunk: &pbwechatypuppet.FileBoxChunk{
113 | Name: &fileBox.Name,
114 | },
115 | }
116 | if err := client.Send(fileNameRequest); err != nil {
117 | return nil, err
118 | }
119 | }
120 | return &MessageSendFile{
121 | client: client,
122 | fileBox: fileBox,
123 | }, nil
124 | }
125 |
126 | // DownloadFile 把 grpc download 流包装到 io.Reader 接口
127 | type DownloadFile struct {
128 | client pbwechaty.Puppet_DownloadClient
129 | buffer bytes.Buffer
130 | done bool
131 | }
132 |
133 | // Read 把 grpc download 流包装到 io.Reader 接口
134 | func (m *DownloadFile) Read(p []byte) (n int, err error) {
135 | if m.done {
136 | return m.buffer.Read(p)
137 | }
138 |
139 | for {
140 | if m.buffer.Len() >= len(p) {
141 | break
142 | }
143 | recv, err := m.client.Recv()
144 | if err == io.EOF {
145 | m.done = true
146 | break
147 | }
148 | if err != nil {
149 | return 0, err
150 | }
151 | _, err = m.buffer.Write(recv.Chunk)
152 | if err != nil {
153 | return 0, err
154 | }
155 | }
156 | return m.buffer.Read(p)
157 | }
158 |
159 | // NewDownloadFile 把 grpc download 流包装到 io.Reader 接口
160 | func NewDownloadFile(client pbwechaty.Puppet_DownloadClient) *DownloadFile {
161 | return &DownloadFile{
162 | client: client,
163 | buffer: bytes.Buffer{},
164 | done: false,
165 | }
166 | }
167 |
168 | /**
169 | * for testing propose, use 20KB as the threshold
170 | * after stable we should use a value between 64KB to 256KB as the threshold
171 | */
172 | const passThroughThresholdBytes = 20 * 1024 //nolint:unused, deadcode, varcheck // TODO 未来会被用到
173 |
174 | /**
175 | * 1. Green:
176 | * Can be serialized directly
177 | */
178 | var greenFileBoxTypes = helper.ArrayInt{
179 | filebox.TypeUrl,
180 | filebox.TypeUuid,
181 | filebox.TypeQRCode,
182 | }
183 |
184 | /**
185 | * 2. Yellow:
186 | * Can be serialized directly, if the size is less than a threshold
187 | * if it's bigger than the threshold,
188 | * then it should be convert to a UUID file box before send out
189 | */
190 | var yellowFileBoxTypes = helper.ArrayInt{
191 | filebox.TypeBase64,
192 | }
193 |
194 | func serializeFileBox(box *filebox.FileBox) (*filebox.FileBox, error) {
195 | if canPassthrough(box) {
196 | return box, nil
197 | }
198 | reader, err := box.ToReader()
199 | if err != nil {
200 | return nil, err
201 | }
202 | uuid, err := filebox.FromStream(reader).ToUuid()
203 | if err != nil {
204 | return nil, err
205 | }
206 | return filebox.FromUuid(uuid, filebox.WithName(box.Name)), nil
207 | }
208 |
209 | func canPassthrough(box *filebox.FileBox) bool {
210 | if greenFileBoxTypes.InArray(int(box.Type())) {
211 | return true
212 | }
213 |
214 | if !yellowFileBoxTypes.InArray(int(box.Type())) {
215 | return false
216 | }
217 |
218 | // checksize
219 | return true
220 | }
221 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/grpc.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | import (
4 | pbwechaty "github.com/wechaty/go-grpc/wechaty"
5 | "google.golang.org/grpc"
6 | "google.golang.org/grpc/connectivity"
7 | "google.golang.org/grpc/credentials"
8 | "google.golang.org/grpc/credentials/insecure"
9 | "time"
10 | )
11 |
12 | func (p *PuppetService) startGrpcClient() error {
13 | var err error
14 | var creds credentials.TransportCredentials
15 | var callOptions []grpc.CallOption
16 | if p.disableTLS {
17 | // TODO 目前不支持 tls,不用打印这个提醒
18 | //log.Warn("PuppetService.startGrpcClient TLS: disabled (INSECURE)")
19 | creds = insecure.NewCredentials()
20 | } else {
21 | callOptions = append(callOptions, grpc.PerRPCCredentials(callCredToken{token: p.token}))
22 | creds, err = p.createCred()
23 | if err != nil {
24 | return err
25 | }
26 | }
27 |
28 | dialOptions := []grpc.DialOption{
29 | grpc.WithTransportCredentials(creds),
30 | grpc.WithDefaultCallOptions(callOptions...),
31 | grpc.WithResolvers(wechatyResolver()),
32 | }
33 |
34 | if p.disableTLS {
35 | // Deprecated: this block will be removed after Dec 21, 2022.
36 | dialOptions = append(dialOptions, grpc.WithAuthority(p.token))
37 | }
38 |
39 | conn, err := grpc.Dial(p.endpoint, dialOptions...)
40 | if err != nil {
41 | return err
42 | }
43 | p.grpcConn = conn
44 |
45 | go p.autoReconnectGrpcConn()
46 |
47 | p.grpcClient = pbwechaty.NewPuppetClient(conn)
48 | return nil
49 | }
50 |
51 | func (p *PuppetService) autoReconnectGrpcConn() {
52 | <-p.started
53 | isClose := false
54 | ticker := p.newGrpcReconnectTicket()
55 | defer ticker.Stop()
56 | for {
57 | select {
58 | case <-ticker.C:
59 | connState := p.grpcConn.GetState()
60 | // 重新连接成功
61 | if isClose && connectivity.Ready == connState {
62 | isClose = false
63 | log.Warn("PuppetService.autoReconnectGrpcConn grpc reconnection successful")
64 | if err := p.startGrpcStream(); err != nil {
65 | log.Errorf("PuppetService.autoReconnectGrpcConn startGrpcStream err:%s", err.Error())
66 | }
67 | }
68 |
69 | if p.grpcConn.GetState() == connectivity.Idle {
70 | isClose = true
71 | p.grpcConn.Connect()
72 | log.Warn("PuppetService.autoReconnectGrpcConn grpc reconnection...")
73 | }
74 | case <-p.stop:
75 | return
76 | }
77 | }
78 | }
79 |
80 | func (p *PuppetService) newGrpcReconnectTicket() *time.Ticker {
81 | interval := 2 * time.Second
82 | if p.opts.GrpcReconnectInterval > 0 {
83 | interval = p.opts.GrpcReconnectInterval
84 | }
85 | return time.NewTicker(interval)
86 | }
87 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/helper.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | import (
4 | "google.golang.org/protobuf/types/known/timestamppb"
5 | "time"
6 | )
7 |
8 | func grpcTimestampToGoTime(t *timestamppb.Timestamp) time.Time {
9 | // 不同的 puppet 返回的时间格式不一致, 需要做转换兼容
10 | // padlocal 返回的是秒,puppet-service 当作是毫秒单位转为秒(除以1000),所以这里 t.Seconds 就只剩下七位,另外3为被分配到 t.Nanos 去了
11 | // https://github.com/wechaty/puppet-service/blob/4de1024ee9b615af6c44674f684a84dd8c11ae9e/src/pure-functions/timestamp.ts#L7-L17
12 |
13 | // 这里我们判断 t.Seconds 是否为7位来特殊处理
14 | //TODO(dchaofei): 未来时间戳每增加一位这里就要判断加一位,那就是200多年之后的事情了,到时还有人在用 wechaty 吗?(2023-09-09)
15 | if t.Seconds/10000000 < 1 {
16 | second := t.Seconds*1000 + int64(t.Nanos)/1000000
17 | return time.Unix(second, 0)
18 | }
19 |
20 | return t.AsTime().Local()
21 | }
22 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/options.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | import (
4 | wechatypuppet "github.com/wechaty/go-wechaty/wechaty-puppet"
5 | "time"
6 | )
7 |
8 | // TLSConfig tls config
9 | type TLSConfig struct {
10 | CaCert string
11 | ServerName string
12 |
13 | Disable bool // only for compatible with old clients/servers
14 | }
15 |
16 | // Options puppet-service options
17 | type Options struct {
18 | wechatypuppet.Option
19 |
20 | GrpcReconnectInterval time.Duration
21 | Authority string
22 | TLS TLSConfig
23 | }
24 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/puppet_service_test.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | // TODO 建议 mock http
4 | //func TestPuppetService_discoverServiceIP(t *testing.T) {
5 | // type fields struct {
6 | // Puppet *wechatyPuppet.Puppet
7 | // grpcConn *grpc.ClientConn
8 | // grpcClient wechaty.PuppetClient
9 | // eventStream wechaty.Puppet_EventClient
10 | // }
11 | // tests := []struct {
12 | // name string
13 | // fields fields
14 | // wantS string
15 | // wantErr bool
16 | // }{
17 | // {
18 | // name: "0.0.0.0",
19 | // fields: fields{
20 | // Puppet: &wechatyPuppet.Puppet{
21 | // Option: &wechatyPuppet.Option{Token: "__TOKEN__"},
22 | // },
23 | // },
24 | // wantS: "0.0.0.0",
25 | // wantErr: false,
26 | // },
27 | // {
28 | // name: "timeout",
29 | // fields: fields{
30 | // Puppet: &wechatyPuppet.Puppet{
31 | // Option: &wechatyPuppet.Option{Token: "__TOKEN__", Timeout: 1 * time.Nanosecond},
32 | // },
33 | // },
34 | // wantS: "",
35 | // wantErr: true,
36 | // },
37 | // }
38 | // for _, tt := range tests {
39 | // t.Run(tt.name, func(t *testing.T) {
40 | // p := &PuppetService{
41 | // Puppet: tt.fields.Puppet,
42 | // grpcConn: tt.fields.grpcConn,
43 | // grpcClient: tt.fields.grpcClient,
44 | // eventStream: tt.fields.eventStream,
45 | // }
46 | // gotS, err := p.discoverServiceIP()
47 | // if (err != nil) != tt.wantErr {
48 | // t.Errorf("discoverServiceIP() error = %v, wantErr %v", err, tt.wantErr)
49 | // return
50 | // }
51 | // if gotS != tt.wantS {
52 | // t.Errorf("discoverServiceIP() gotS = %v, want %v", gotS, tt.wantS)
53 | // }
54 | // })
55 | // }
56 | //}
57 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/resolver.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "google.golang.org/grpc/resolver"
7 | "google.golang.org/grpc/resolver/manual"
8 | "io/ioutil"
9 | "net/http"
10 | "strings"
11 | )
12 |
13 | func wechatyResolver() resolver.Builder {
14 | r := manual.NewBuilderWithScheme("wechaty")
15 | r.BuildCallback = resolverBuildCallBack
16 | return r
17 | }
18 |
19 | func resolverBuildCallBack(target resolver.Target, conn resolver.ClientConn, options resolver.BuildOptions) {
20 | // target.URL.Host `api.chatie.io` in `wechaty://api.chatie.io/__token__`
21 | // target.URL.Path `__token__` in `wechaty://api.chatie.io/__token__`
22 | log.Trace("resolverBuildCallBack()")
23 | uri := fmt.Sprintf("https://%s/v0/hosties%s", target.URL.Host, target.URL.Path)
24 | address, err := discoverAPI(uri)
25 | if err != nil {
26 | conn.ReportError(err)
27 | return
28 | }
29 | if address == nil || address.Host == "" {
30 | conn.ReportError(fmt.Errorf(`token %s does not exist`, strings.TrimLeft(target.URL.Path, "/")))
31 | return
32 | }
33 | err = conn.UpdateState(resolver.State{
34 | Addresses: []resolver.Address{{
35 | Addr: fmt.Sprintf("%s:%d", address.Host, address.Port),
36 | }},
37 | })
38 | if err != nil {
39 | log.Error("resolverBuildCallBack UpdateState err: ", err.Error())
40 | return
41 | }
42 | }
43 |
44 | type serviceAddress struct {
45 | Host string
46 | Port int
47 | }
48 |
49 | func discoverAPI(uri string) (*serviceAddress, error) {
50 | response, err := http.Get(uri)
51 | if err != nil {
52 | return nil, fmt.Errorf("discoverAPI http.Get() %w", err)
53 | }
54 | defer response.Body.Close()
55 |
56 | // 4xx
57 | if response.StatusCode >= 400 && response.StatusCode < 500 {
58 | return nil, nil
59 | }
60 |
61 | // 2xx
62 | if response.StatusCode < 200 || response.StatusCode >= 300 {
63 | return nil, fmt.Errorf("discoverAPI http.Get() status:%s %w", response.Status, err)
64 | }
65 | data, err := ioutil.ReadAll(response.Body)
66 | if err != nil {
67 | return nil, fmt.Errorf("discoverAPI ioutil.ReadAll %w", err)
68 | }
69 |
70 | r := &serviceAddress{}
71 | if err := json.Unmarshal(data, r); err != nil {
72 | return nil, fmt.Errorf("discoverAPI json.Unmarshal %w", err)
73 | }
74 | return r, nil
75 | }
76 |
--------------------------------------------------------------------------------
/wechaty-puppet-service/service_endpoint.go:
--------------------------------------------------------------------------------
1 | package puppetservice
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | var (
9 | // ErrNotToken token not found error
10 | // Deprecated
11 | ErrNotToken = errors.New("wechaty-puppet-service: token not found. See: ")
12 | )
13 |
14 | // ServiceEndPoint api.chatie.io endpoint api response
15 | // Deprecated
16 | type ServiceEndPoint struct {
17 | IP string `json:"ip"`
18 | Port int `json:"port,omitempty"`
19 | }
20 |
21 | // IsValid EndPoint is valid
22 | func (p *ServiceEndPoint) IsValid() bool {
23 | return len(p.IP) > 0 && p.IP != "0.0.0.0"
24 | }
25 |
26 | // Target Export IP+Port
27 | func (p *ServiceEndPoint) Target() string {
28 | port := p.Port
29 | if p.Port == 0 {
30 | port = 8788
31 | }
32 | return fmt.Sprintf("%s:%d", p.IP, port)
33 | }
34 |
--------------------------------------------------------------------------------
/wechaty-puppet/file-box/file_box.go:
--------------------------------------------------------------------------------
1 | // file_box
2 | // Deprecated: use filebox package
3 |
4 | package file_box
5 |
6 | import (
7 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
8 | )
9 |
10 | // FileBox ...
11 | // Deprecated: use filebox.FileBox
12 | type FileBox = filebox.FileBox
13 |
14 | var (
15 | // FromJSON ...
16 | // Deprecated: use filebox.FileBox
17 | FromJSON = filebox.FromJSON
18 |
19 | // FromBase64 ...
20 | // Deprecated: use filebox.FromBase64
21 | FromBase64 = filebox.FromBase64
22 |
23 | // FromUrl ...
24 | // Deprecated: use filebox.FromUrl
25 | FromUrl = filebox.FromUrl
26 |
27 | // FromFile ...
28 | // Deprecated: use filebox.FromFile
29 | FromFile = filebox.FromFile
30 |
31 | // FromQRCode ...
32 | // Deprecated: use filebox.FromQRCode
33 | FromQRCode = filebox.FromQRCode
34 |
35 | // FromStream ...
36 | // Deprecated: use filebox.FromStream
37 | FromStream = filebox.FromStream
38 | )
39 |
--------------------------------------------------------------------------------
/wechaty-puppet/file-box/fileboxtype_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=FileBoxType"; DO NOT EDIT.
2 |
3 | package file_box
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[FileBoxTypeUnknown-0]
12 | }
13 |
14 | const _FileBoxType_name = "FileBoxTypeUnknown"
15 |
16 | var _FileBoxType_index = [...]uint8{0, 18}
17 |
18 | func (i FileBoxType) String() string {
19 | if i >= FileBoxType(len(_FileBoxType_index)-1) {
20 | return "FileBoxType(" + strconv.FormatInt(int64(i), 10) + ")"
21 | }
22 | return _FileBoxType_name[_FileBoxType_index[i]:_FileBoxType_index[i+1]]
23 | }
24 |
--------------------------------------------------------------------------------
/wechaty-puppet/file-box/testdata/.gitignore:
--------------------------------------------------------------------------------
1 | test.text
2 |
--------------------------------------------------------------------------------
/wechaty-puppet/file-box/type.go:
--------------------------------------------------------------------------------
1 | package file_box
2 |
3 | import "net/http"
4 |
5 | // FileBoxOptionsCommon ...
6 | // Deprecated: use filebox package
7 | type FileBoxOptionsCommon struct {
8 | Name string `json:"Name"`
9 | Metadata map[string]interface{} `json:"metadata"`
10 | BoxType FileBoxType `json:"boxType"`
11 | }
12 |
13 | // FileBoxOptionsBase64 ...
14 | // Deprecated: use filebox package
15 | type FileBoxOptionsBase64 struct {
16 | Base64 string `json:"base64"`
17 | }
18 |
19 | // FileBoxOptionsUrl ...
20 | // Deprecated: use filebox package
21 | type FileBoxOptionsUrl struct {
22 | RemoteUrl string `json:"remoteUrl"`
23 | Headers http.Header `json:"headers"`
24 | }
25 |
26 | // FileBoxOptionsQRCode ...
27 | // Deprecated: use filebox package
28 | type FileBoxOptionsQRCode struct {
29 | QrCode string `json:"qrCode"`
30 | }
31 |
32 | // FileBoxOptions ...
33 | // Deprecated: use filebox package
34 | type FileBoxOptions struct {
35 | FileBoxOptionsCommon
36 | FileBoxOptionsBase64
37 | FileBoxOptionsQRCode
38 | FileBoxOptionsUrl
39 | }
40 |
41 | //go:generate stringer -type=FileBoxType
42 | // FileBoxType ...
43 | // Deprecated: use filebox package
44 | type FileBoxType uint8
45 |
46 | const (
47 | FileBoxTypeUnknown FileBoxType = 0
48 |
49 | // Deprecated: use filebox package
50 | FileBoxTypeBase64 = 1
51 | // Deprecated: use filebox package
52 | FileBoxTypeUrl = 2
53 | // Deprecated: use filebox package
54 | FileBoxTypeQRCode = 3
55 | // Deprecated: use filebox package
56 | FileBoxTypeBuffer = 4
57 | // Deprecated: use filebox package
58 | FileBoxTypeFile = 5
59 | // Deprecated: use filebox package
60 | FileBoxTypeStream = 6
61 | )
62 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import (
4 | "bufio"
5 | "encoding/base64"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "github.com/tuotoo/qrcode"
10 | "github.com/wechaty/go-wechaty/wechaty-puppet/helper"
11 | logger "github.com/wechaty/go-wechaty/wechaty-puppet/log"
12 | "io"
13 | "io/ioutil"
14 | "mime"
15 | "net/url"
16 | "os"
17 | path2 "path"
18 | "path/filepath"
19 | "strings"
20 | )
21 |
22 | var (
23 | // ErrToJSON err to json
24 | ErrToJSON = errors.New("FileBox.toJSON() only support TypeUrl,TypeQRCode,TypeBase64, TypeUuid")
25 | // ErrNoBase64Data no base64 data
26 | ErrNoBase64Data = errors.New("no Base64 data")
27 | // ErrNoUrl no url
28 | ErrNoUrl = errors.New("no url")
29 | // ErrNoPath no path
30 | ErrNoPath = errors.New("no path")
31 | // ErrNoQRCode no QR Code
32 | ErrNoQRCode = errors.New("no QR Code")
33 | // ErrNoUuid no uuid
34 | ErrNoUuid = errors.New("no uuid")
35 | )
36 |
37 | var log = logger.L.WithField("module", "filebox")
38 |
39 | type fileImplInterface interface {
40 | toJSONMap() (map[string]interface{}, error)
41 | toReader() (io.Reader, error)
42 | }
43 |
44 | // FileBox struct
45 | type FileBox struct {
46 | fileImpl fileImplInterface
47 | Name string
48 | metadata map[string]interface{}
49 | boxType Type
50 | mediaType string
51 | size int64
52 | md5 string
53 |
54 | err error
55 | }
56 |
57 | func newFileBox(boxType Type, fileImpl fileImplInterface, options Options) *FileBox {
58 | fb := &FileBox{
59 | fileImpl: fileImpl,
60 | Name: options.Name,
61 | metadata: options.Metadata,
62 | boxType: boxType,
63 | size: options.Size,
64 | md5: options.Md5,
65 | }
66 | if fb.metadata == nil {
67 | fb.metadata = make(map[string]interface{})
68 | }
69 | fb.correctName()
70 | fb.guessMediaType()
71 | return fb
72 | }
73 |
74 | func (fb *FileBox) correctName() {
75 | if strings.HasSuffix(fb.Name, ".silk") || strings.HasSuffix(fb.Name, ".slk") {
76 | log.Warn("detect that you want to send voice file which should be .sil pattern. So we help you rename it.")
77 | if strings.HasSuffix(fb.Name, ".silk") {
78 | fb.Name = strings.ReplaceAll(fb.Name, ".silk", ".sil")
79 | }
80 | if strings.HasSuffix(fb.Name, ".slk") {
81 | fb.Name = strings.ReplaceAll(fb.Name, ".slk", ".sil")
82 | }
83 | }
84 | }
85 |
86 | func (fb *FileBox) guessMediaType() {
87 | if strings.HasSuffix(fb.Name, ".sil") {
88 | fb.mediaType = "audio/silk"
89 | if _, ok := fb.metadata["voiceLength"]; !ok {
90 | log.Warn("detect that you want to send voice file, but no voiceLength setting, " +
91 | `so use the default setting: 1000,` +
92 | `you should set it manually: filebox.WithMetadata(map[string]interface{}{"voiceLength": 2000})`)
93 | fb.metadata["voiceLength"] = 1000
94 | }
95 | } else {
96 | fb.mediaType = mime.TypeByExtension(filepath.Ext(fb.Name))
97 | }
98 | }
99 |
100 | // FromJSON create FileBox from JSON
101 | func FromJSON(s string) *FileBox {
102 | options := new(Options)
103 | if err := json.Unmarshal([]byte(s), options); err != nil {
104 | err = fmt.Errorf("FromJSON json.Unmarshal: %w", err)
105 | return newFileBox(TypeUnknown, &fileBoxUnknown{}, newOptions()).setErr(err)
106 | }
107 |
108 | // 对未来要弃用的 json.BoxTypeDeprecated 做兼容处理
109 | if options.BoxTypeDeprecated != 0 && options.BoxType == 0 {
110 | options.BoxType = options.BoxTypeDeprecated
111 | }
112 |
113 | switch options.BoxType {
114 | case TypeBase64:
115 | return FromBase64(options.Base64, WithOptions(*options))
116 | case TypeQRCode:
117 | return FromQRCode(options.QrCode, WithOptions(*options))
118 | case TypeUrl:
119 | return FromUrl(options.RemoteUrl, WithOptions(*options))
120 | case TypeUuid:
121 | return FromUuid(options.Uuid, WithOptions(*options))
122 | default:
123 | err := fmt.Errorf("FromJSON invalid value boxType: %v", options.BoxType)
124 | return newFileBox(TypeUnknown, &fileBoxUnknown{}, newOptions()).setErr(err)
125 | }
126 | }
127 |
128 | // FromBase64 create FileBox from Base64
129 | func FromBase64(encode string, options ...Option) *FileBox {
130 | var err error
131 | if encode == "" {
132 | err = fmt.Errorf("FromBase64 %w", ErrNoBase64Data)
133 | }
134 |
135 | o := newOptions(options...)
136 | if o.Name == "" {
137 | o.Name = "base64.dat"
138 | }
139 | o.Size = helper.Base64OrigLength(encode)
140 | return newFileBox(TypeBase64,
141 | newFileBoxBase64(encode), o).setErr(err)
142 | }
143 |
144 | // FromUrl create FileBox from url
145 | func FromUrl(urlString string, options ...Option) *FileBox {
146 | var err error
147 | if urlString == "" {
148 | err = fmt.Errorf("FromUrl %w", ErrNoUrl)
149 | }
150 |
151 | o := newOptions(options...)
152 | if o.Name == "" && err == nil {
153 | if u, e := url.Parse(urlString); e != nil {
154 | err = e
155 | } else {
156 | o.Name = strings.TrimLeft(u.Path, "/")
157 | }
158 | }
159 | return newFileBox(TypeUrl,
160 | newFileBoxUrl(urlString, o.Headers), o).setErr(err)
161 | }
162 |
163 | // FromFile create FileBox from file
164 | func FromFile(path string, options ...Option) *FileBox {
165 | var err error
166 | if path == "" {
167 | err = fmt.Errorf("FromFile %w", ErrNoPath)
168 | }
169 |
170 | o := newOptions(options...)
171 | if o.Name == "" {
172 | o.Name = path2.Base(path)
173 | }
174 |
175 | if err == nil {
176 | if file, e := os.Stat(path); e != nil {
177 | err = e
178 | } else {
179 | o.Size = file.Size()
180 | }
181 | }
182 |
183 | return newFileBox(TypeFile,
184 | newFileBoxFile(path), o).setErr(err)
185 | }
186 |
187 | // FromQRCode create FileBox from QRCode
188 | func FromQRCode(qrCode string, options ...Option) *FileBox {
189 | var err error
190 | if qrCode == "" {
191 | err = fmt.Errorf("FromQRCode %w", ErrNoQRCode)
192 | }
193 |
194 | return newFileBox(TypeQRCode,
195 | newFileBoxQRCode(qrCode),
196 | newOptions(append(options, WithName("qrcode.png"))...)).setErr(err)
197 | }
198 |
199 | // FromStream from io.Reader
200 | func FromStream(reader io.Reader, options ...Option) *FileBox {
201 | o := newOptions(options...)
202 | if o.Name == "" {
203 | o.Name = "stream.dat"
204 | }
205 | return newFileBox(TypeStream,
206 | newFileBoxStream(reader), o)
207 | }
208 |
209 | func FromUuid(uuid string, options ...Option) *FileBox {
210 | var err error
211 | if uuid == "" {
212 | err = fmt.Errorf("FromUuid %w", ErrNoUuid)
213 | }
214 |
215 | o := newOptions(options...)
216 | if o.Name == "" {
217 | o.Name = uuid + ".dat"
218 | }
219 | return newFileBox(TypeUuid, newFileBoxUuid(uuid), o).setErr(err)
220 | }
221 |
222 | // ToJSON to json string
223 | func (fb *FileBox) ToJSON() (string, error) {
224 | if fb.err != nil {
225 | return "", fb.err
226 | }
227 |
228 | jsonMap := map[string]interface{}{
229 | "name": fb.Name,
230 | "metadata": fb.metadata,
231 | "type": fb.boxType,
232 | "boxType": fb.boxType, //Deprecated
233 | "size": fb.size,
234 | "md5": fb.md5,
235 | "mediaType": fb.mediaType,
236 | }
237 |
238 | switch fb.boxType {
239 | case TypeUrl, TypeQRCode, TypeBase64, TypeUuid:
240 | break
241 | default:
242 | return "", ErrToJSON
243 | }
244 | implJsonMap, err := fb.fileImpl.toJSONMap()
245 | if err != nil {
246 | return "", err
247 | }
248 | for k, v := range implJsonMap {
249 | jsonMap[k] = v
250 | }
251 | marshal, err := json.Marshal(jsonMap)
252 | return string(marshal), err
253 | }
254 |
255 | // ToFile save to file
256 | func (fb *FileBox) ToFile(filePath string, overwrite bool) error {
257 | if fb.err != nil {
258 | return fb.err
259 | }
260 |
261 | if filePath == "" {
262 | filePath = fb.Name
263 | }
264 | path, err := os.Getwd()
265 | if err != nil {
266 | return err
267 | }
268 | fullPath := filepath.Join(path, filePath)
269 | if !overwrite && helper.FileExists(fullPath) {
270 | return os.ErrExist
271 | }
272 |
273 | reader, err := fb.ToReader()
274 | if err != nil {
275 | return err
276 | }
277 |
278 | writer := bufio.NewReader(reader)
279 | file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, os.ModePerm)
280 | if err != nil {
281 | return err
282 | }
283 | if _, err := writer.WriteTo(file); err != nil {
284 | return err
285 | }
286 | return nil
287 | }
288 |
289 | // ToBytes to bytes
290 | func (fb *FileBox) ToBytes() ([]byte, error) {
291 | if fb.err != nil {
292 | return nil, fb.err
293 | }
294 |
295 | reader, err := fb.ToReader()
296 | if err != nil {
297 | return nil, err
298 | }
299 | return ioutil.ReadAll(reader)
300 | }
301 |
302 | // ToBase64 to base64 string
303 | func (fb *FileBox) ToBase64() (string, error) {
304 | if fb.err != nil {
305 | return "", fb.err
306 | }
307 |
308 | if fb.boxType == TypeBase64 {
309 | return fb.fileImpl.(*fileBoxBase64).base64Data, nil
310 | }
311 |
312 | fileBytes, err := fb.ToBytes()
313 | if err != nil {
314 | return "", err
315 | }
316 | return base64.StdEncoding.EncodeToString(fileBytes), nil
317 | }
318 |
319 | // ToDataURL to dataURL
320 | func (fb *FileBox) ToDataURL() (string, error) {
321 | if fb.err != nil {
322 | return "", fb.err
323 | }
324 |
325 | toBase64, err := fb.ToBase64()
326 | if err != nil {
327 | return "", nil
328 | }
329 | return fmt.Sprintf("data:%s;base64,%s", fb.mediaType, toBase64), nil
330 | }
331 |
332 | // ToQRCode to QRCode
333 | func (fb *FileBox) ToQRCode() (string, error) {
334 | if fb.err != nil {
335 | return "", fb.err
336 | }
337 |
338 | reader, err := fb.ToReader()
339 | if err != nil {
340 | return "", err
341 | }
342 | decode, err := qrcode.Decode(reader)
343 | if err != nil {
344 | return "", nil
345 | }
346 | return decode.Content, nil
347 | }
348 |
349 | // ToUuid to uuid
350 | func (fb *FileBox) ToUuid() (string, error) {
351 | if fb.err != nil {
352 | return "", fb.err
353 | }
354 |
355 | if fb.boxType == TypeUuid {
356 | return fb.fileImpl.(*fileBoxUuid).uuid, nil
357 | }
358 |
359 | reader, err := fb.ToReader()
360 | if err != nil {
361 | return "", err
362 | }
363 |
364 | if uuidFromStream == nil {
365 | return "", errors.New("need to use filebox.SetUuidSaver() before dealing with UUID")
366 | }
367 |
368 | return uuidFromStream(reader)
369 | }
370 |
371 | // String ...
372 | func (fb *FileBox) String() string {
373 | return fmt.Sprintf("FileBox#%s<%s>", fb.boxType, fb.Name)
374 | }
375 |
376 | // ToReader to io.Reader
377 | func (fb *FileBox) ToReader() (io.Reader, error) {
378 | if fb.err != nil {
379 | return nil, fb.err
380 | }
381 |
382 | return fb.fileImpl.toReader()
383 | }
384 |
385 | // Type get type
386 | func (fb *FileBox) Type() Type {
387 | return fb.boxType
388 | }
389 |
390 | // MetaData get metadata
391 | func (fb *FileBox) MetaData() map[string]interface{} {
392 | // TODO deep copy?
393 | return fb.metadata
394 | }
395 |
396 | // Error ret err
397 | func (fb *FileBox) Error() error {
398 | return fb.err
399 | }
400 |
401 | func (fb *FileBox) setErr(err error) *FileBox {
402 | fb.err = err
403 | return fb
404 | }
405 |
406 | func (fb *FileBox) Size() {
407 |
408 | }
409 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box_base64.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "io"
7 | "strings"
8 | )
9 |
10 | var _ fileImplInterface = &fileBoxBase64{}
11 |
12 | type fileBoxBase64 struct {
13 | base64Data string
14 | }
15 |
16 | func newFileBoxBase64(base64Data string) *fileBoxBase64 {
17 | return &fileBoxBase64{base64Data: base64Data}
18 | }
19 |
20 | func (fb *fileBoxBase64) toJSONMap() (map[string]interface{}, error) {
21 | if fb.base64Data == "" {
22 | return nil, fmt.Errorf("fileBoxBase64.toJSONMap %w", ErrNoBase64Data)
23 | }
24 |
25 | return map[string]interface{}{
26 | "base64": fb.base64Data,
27 | }, nil
28 | }
29 |
30 | func (fb *fileBoxBase64) toBytes() ([]byte, error) { //nolint:unused
31 | dec, err := base64.StdEncoding.DecodeString(fb.base64Data)
32 | if err != nil {
33 | return nil, err
34 | }
35 | return dec, nil
36 | }
37 |
38 | func (fb *fileBoxBase64) toReader() (io.Reader, error) {
39 | reader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(fb.base64Data))
40 | return reader, nil
41 | }
42 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box_file.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import (
4 | "encoding/base64"
5 | "io"
6 | "io/ioutil"
7 | "os"
8 | )
9 |
10 | var _ fileImplInterface = &fileBoxFile{}
11 |
12 | type fileBoxFile struct {
13 | path string
14 | }
15 |
16 | func newFileBoxFile(path string) *fileBoxFile {
17 | return &fileBoxFile{path: path}
18 | }
19 |
20 | func (fb *fileBoxFile) toJSONMap() (map[string]interface{}, error) {
21 | return nil, nil
22 | }
23 |
24 | func (fb *fileBoxFile) toBytes() ([]byte, error) { //nolint:unused
25 | file, err := ioutil.ReadFile(fb.path)
26 | if err != nil {
27 | return nil, err
28 | }
29 | return file, nil
30 | }
31 |
32 | func (fb *fileBoxFile) toBase64() (string, error) { //nolint:unused
33 | file, err := fb.toBytes()
34 | if err != nil {
35 | return "", err
36 | }
37 | return base64.StdEncoding.EncodeToString(file), nil
38 | }
39 |
40 | func (fb *fileBoxFile) toReader() (io.Reader, error) {
41 | return os.Open(fb.path)
42 | }
43 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box_qrcode.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/skip2/go-qrcode"
7 | "io"
8 | )
9 |
10 | var _ fileImplInterface = &fileBoxQRCode{}
11 |
12 | type fileBoxQRCode struct {
13 | qrCode string
14 | }
15 |
16 | func newFileBoxQRCode(qrCode string) *fileBoxQRCode {
17 | return &fileBoxQRCode{qrCode: qrCode}
18 | }
19 |
20 | func (fb *fileBoxQRCode) toJSONMap() (map[string]interface{}, error) {
21 | if fb.qrCode == "" {
22 | return nil, fmt.Errorf("fileBoxQRCode.toJSONMap %w", ErrNoQRCode)
23 | }
24 |
25 | return map[string]interface{}{
26 | "qrCode": fb.qrCode,
27 | }, nil
28 | }
29 |
30 | func (fb *fileBoxQRCode) toBytes() ([]byte, error) {
31 | qr, err := qrcode.New(fb.qrCode, qrcode.Medium)
32 | if err != nil {
33 | return nil, err
34 | }
35 | return qr.PNG(256)
36 | }
37 |
38 | func (fb *fileBoxQRCode) toReader() (io.Reader, error) {
39 | byteData, err := fb.toBytes()
40 | if err != nil {
41 | return nil, err
42 | }
43 | return bytes.NewReader(byteData), nil
44 | }
45 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box_stream.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import (
4 | "io"
5 | )
6 |
7 | var _ fileImplInterface = &fileBoxStream{}
8 |
9 | type fileBoxStream struct {
10 | Reader io.Reader
11 | }
12 |
13 | func newFileBoxStream(reader io.Reader) *fileBoxStream {
14 | return &fileBoxStream{Reader: reader}
15 | }
16 |
17 | func (fb *fileBoxStream) toJSONMap() (map[string]interface{}, error) {
18 | return nil, nil
19 | }
20 |
21 | func (fb *fileBoxStream) toBytes() ([]byte, error) { // nolint:unused
22 | panic("im")
23 | }
24 |
25 | func (fb *fileBoxStream) toReader() (io.Reader, error) {
26 | return fb.Reader, nil
27 | }
28 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box_test.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func TestFromJSON(t *testing.T) {
10 | t.Run("FromJSON json.Unmarshal err", func(t *testing.T) {
11 | if err := FromJSON("abcd").Error(); !strings.Contains(err.Error(), "FromJSON json.Unmarshal") {
12 | t.Error(err)
13 | }
14 | })
15 | t.Run("FromJSON invalid value boxType", func(t *testing.T) {
16 | if err := FromJSON("{}").Error(); !strings.Contains(err.Error(), "FromJSON invalid value boxType") {
17 | t.Error(err)
18 | }
19 | })
20 | t.Run("FromJSON success", func(t *testing.T) {
21 | jsonText := `{"base64":"RmlsZUJveEJhc2U2NAo=","boxType":1,"md5":"","metadata":null,"name":"test.txt","size":14,"type":1}`
22 | if err := FromJSON(jsonText).Error(); err != nil {
23 | t.Error(err)
24 | }
25 | })
26 | }
27 |
28 | func TestFromBase64(t *testing.T) {
29 | t.Run("FromBase64 no base64 data", func(t *testing.T) {
30 | if err := FromBase64("").Error(); !errors.Is(err, ErrNoBase64Data) {
31 | t.Error(err)
32 | }
33 | })
34 | t.Run("FromBase64 success", func(t *testing.T) {
35 | fileBox := FromBase64("RmlsZUJveEJhc2U2NAo=")
36 | if err := fileBox.Error(); err != nil {
37 | t.Error(err)
38 | }
39 | })
40 | }
41 |
42 | func TestFromUrl(t *testing.T) {
43 | t.Run("FromUrl no url", func(t *testing.T) {
44 | if err := FromUrl("").Error(); !errors.Is(err, ErrNoUrl) {
45 | t.Error(err)
46 | }
47 | })
48 | t.Run("FromUrl success", func(t *testing.T) {
49 | fileBox := FromUrl("https://github.com//dchaofei.jpg?t=123")
50 | if err := fileBox.Error(); err != nil {
51 | t.Error(err)
52 | }
53 | want := "dchaofei.jpg"
54 | if fileBox.Name != want {
55 | t.Errorf("got %s want %s", fileBox.Name, want)
56 | }
57 | })
58 | }
59 |
60 | func TestFromFile(t *testing.T) {
61 | t.Run("FromFile no path", func(t *testing.T) {
62 | if err := FromFile("").Error(); !errors.Is(err, ErrNoPath) {
63 | t.Error(err)
64 | }
65 | })
66 | t.Run("FromFile success", func(t *testing.T) {
67 | fileBox := FromFile("testdata/dchaofei.txt")
68 | if err := fileBox.Error(); err != nil {
69 | t.Error(err)
70 | }
71 |
72 | wantName := "dchaofei.txt"
73 | if wantName != fileBox.Name {
74 | t.Errorf("got %s want %s", fileBox.Name, wantName)
75 | }
76 | })
77 | }
78 |
79 | func TestFromQRCode(t *testing.T) {
80 | t.Run("FromQRCode no QR code", func(t *testing.T) {
81 | if err := FromQRCode("").Error(); !errors.Is(err, ErrNoQRCode) {
82 | t.Error(err)
83 | }
84 | })
85 | t.Run("FromQRCode success", func(t *testing.T) {
86 | fileBox := FromQRCode("hello")
87 | if err := fileBox.Error(); err != nil {
88 | t.Error(err)
89 | }
90 | })
91 | }
92 |
93 | func TestFromUuid(t *testing.T) {
94 | t.Run("FromUuid no uuid", func(t *testing.T) {
95 | if err := FromUuid("").Error(); !errors.Is(err, ErrNoUuid) {
96 | t.Error(err)
97 | }
98 | })
99 | t.Run("FromUuid success", func(t *testing.T) {
100 | fileBox := FromUuid("xxx-xxx-xxx")
101 | if err := fileBox.Error(); err != nil {
102 | t.Error(err)
103 | }
104 | })
105 | }
106 |
107 | func TestFileBox_ToJSON(t *testing.T) {
108 | t.Run("ToJSON success", func(t *testing.T) {
109 | const base64Encode = "RmlsZUJveEJhc2U2NAo="
110 | const base64Filename = "test.txt"
111 | const want = `{"base64":"RmlsZUJveEJhc2U2NAo=","boxType":1,"md5":"","mediaType":"text/plain; charset=utf-8","metadata":{},"name":"test.txt","size":14,"type":1}`
112 | jsonString, err := FromBase64(base64Encode, WithName(base64Filename)).ToJSON()
113 | if err != nil {
114 | t.Error(err)
115 | }
116 | if jsonString != want {
117 | t.Errorf("got【%s】, want [%s]", jsonString, want)
118 | }
119 |
120 | newBase64, err := FromJSON(want).ToBase64()
121 | if err != nil {
122 | t.Error(err)
123 | }
124 | if newBase64 != base64Encode {
125 | t.Errorf("got【%s】, want [%s]", newBase64, base64Encode)
126 | }
127 | })
128 |
129 | t.Run("ToJSON for not supported type", func(t *testing.T) {
130 | if _, err := FromFile("testdata/dchaofei.txt").ToJSON(); err != ErrToJSON {
131 | t.Error(err)
132 | }
133 | })
134 | }
135 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box_unknown.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import "io"
4 |
5 | var _ fileImplInterface = &fileBoxUnknown{}
6 |
7 | type fileBoxUnknown struct {
8 | }
9 |
10 | func (f fileBoxUnknown) toJSONMap() (map[string]interface{}, error) {
11 | //TODO implement me
12 | panic("implement me")
13 | }
14 |
15 | func (f fileBoxUnknown) toReader() (io.Reader, error) {
16 | //TODO implement me
17 | panic("implement me")
18 | }
19 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box_url.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/wechaty/go-wechaty/wechaty-puppet/helper"
7 | "io"
8 | "io/ioutil"
9 | "net/http"
10 | )
11 |
12 | type fileBoxUrl struct {
13 | remoteUrl string
14 | headers http.Header
15 | }
16 |
17 | func newFileBoxUrl(remoteUrl string, headers http.Header) *fileBoxUrl {
18 | return &fileBoxUrl{remoteUrl: remoteUrl, headers: headers}
19 | }
20 |
21 | func (fb *fileBoxUrl) toJSONMap() (map[string]interface{}, error) {
22 | if fb.remoteUrl == "" {
23 | return nil, fmt.Errorf("fileBoxUrl.toJSONMap %w", ErrNoUrl)
24 | }
25 | return map[string]interface{}{
26 | "headers": fb.headers,
27 | "url": fb.remoteUrl,
28 | }, nil
29 | }
30 |
31 | func (fb *fileBoxUrl) toBytes() ([]byte, error) { //nolint:unused
32 | request, err := http.NewRequest(http.MethodGet, fb.remoteUrl, nil)
33 | if err != nil {
34 | return nil, err
35 | }
36 | request.Header = fb.headers
37 | response, err := helper.HttpClient.Do(request)
38 | if err != nil {
39 | return nil, err
40 | }
41 | defer response.Body.Close()
42 | return ioutil.ReadAll(response.Body)
43 | }
44 |
45 | func (fb *fileBoxUrl) toReader() (io.Reader, error) {
46 | request, err := http.NewRequest(http.MethodGet, fb.remoteUrl, nil)
47 | if err != nil {
48 | return nil, err
49 | }
50 | request.Header = fb.headers
51 | response, err := helper.HttpClient.Do(request)
52 | if err != nil {
53 | return nil, err
54 | }
55 | defer response.Body.Close()
56 |
57 | all, err := ioutil.ReadAll(response.Body)
58 | if err != nil {
59 | return nil, err
60 | }
61 | return bytes.NewReader(all), nil
62 | }
63 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box_uuid.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | )
8 |
9 | var _ fileImplInterface = fileBoxUuid{}
10 |
11 | // UuidLoader uuid loader
12 | type UuidLoader func(uuid string) (io.Reader, error)
13 |
14 | // UuidSaver uuid saver
15 | type UuidSaver func(reader io.Reader) (uuid string, err error)
16 |
17 | var uuidToStream UuidLoader
18 | var uuidFromStream UuidSaver
19 |
20 | // SetUuidLoader set uuid loader
21 | func SetUuidLoader(loader UuidLoader) {
22 | uuidToStream = loader
23 | }
24 |
25 | // SetUuidSaver set uuid saver
26 | func SetUuidSaver(saver UuidSaver) {
27 | uuidFromStream = saver
28 | }
29 |
30 | type fileBoxUuid struct {
31 | uuid string
32 | }
33 |
34 | func (f fileBoxUuid) toJSONMap() (map[string]interface{}, error) {
35 | if f.uuid == "" {
36 | return nil, fmt.Errorf("fileBoxUuid.toJSONMap %w", ErrNoUuid)
37 | }
38 | return map[string]interface{}{
39 | "uuid": f.uuid,
40 | }, nil
41 | }
42 |
43 | func (f fileBoxUuid) toReader() (io.Reader, error) {
44 | if uuidToStream == nil {
45 | return nil, errors.New("need to call filebox.setUuidLoader() to set UUID loader first")
46 | }
47 | return uuidToStream(f.uuid)
48 | }
49 |
50 | func newFileBoxUuid(uuid string) *fileBoxUuid {
51 | return &fileBoxUuid{uuid: uuid}
52 | }
53 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/file_box_uuid_test.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import (
4 | "io"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func TestSetUuidLoader(t *testing.T) {
10 | const data = "hello"
11 | const uuid = "xxxx-xxxx"
12 | loader := func(uuid string) (io.Reader, error) {
13 | return strings.NewReader(data), nil
14 | }
15 |
16 | SetUuidLoader(loader)
17 |
18 | bytes, err := FromUuid(uuid).ToBytes()
19 | if err != nil {
20 | t.Log(err)
21 | }
22 | if string(bytes) != data {
23 | t.Errorf("got %s want %s", string(bytes), data)
24 | }
25 | }
26 |
27 | func TestSetUuidSaver(t *testing.T) {
28 | const data = "https://github.com/dchaofei"
29 | const uuid = "xxxx-xxxx"
30 | saver := func(reader io.Reader) (string, error) {
31 | return uuid, nil
32 | }
33 | SetUuidSaver(saver)
34 |
35 | bytes, err := FromFile("testdata/dchaofei.txt").ToUuid()
36 | if err != nil {
37 | t.Log(err)
38 | }
39 | if string(bytes) != uuid {
40 | t.Errorf("got %s want %s", string(bytes), data)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/testdata/dchaofei.txt:
--------------------------------------------------------------------------------
1 | https://github.com/dchaofei
2 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/type.go:
--------------------------------------------------------------------------------
1 | package filebox
2 |
3 | import "net/http"
4 |
5 | type Option func(options *Options)
6 |
7 | // WithName set name
8 | func WithName(name string) Option {
9 | return func(options *Options) {
10 | options.Name = name
11 | }
12 | }
13 |
14 | func WithOptions(o Options) Option {
15 | return func(options *Options) {
16 | *options = o
17 | }
18 | }
19 |
20 | // WithMetadata set metadata
21 | func WithMetadata(metadata map[string]interface{}) Option {
22 | return func(options *Options) {
23 | options.Metadata = metadata
24 | }
25 | }
26 |
27 | // WithMd5 set md5
28 | func WithMd5(md5 string) Option {
29 | return func(options *Options) {
30 | options.Md5 = md5
31 | }
32 | }
33 |
34 | // WithSize set size
35 | func WithSize(size int64) Option {
36 | return func(options *Options) {
37 | options.Size = size
38 | }
39 | }
40 |
41 | // OptionsCommon common options
42 | type OptionsCommon struct {
43 | Name string `json:"name"`
44 | Metadata map[string]interface{} `json:"metadata"`
45 | BoxType Type `json:"type"`
46 | // Deprecated
47 | BoxTypeDeprecated Type `json:"boxType"`
48 | Size int64 `json:"size"`
49 | Md5 string `json:"md5"`
50 | }
51 |
52 | // OptionsBase64 base64
53 | type OptionsBase64 struct {
54 | Base64 string `json:"base64"`
55 | }
56 |
57 | // OptionsUrl url
58 | type OptionsUrl struct {
59 | RemoteUrl string `json:"url"`
60 | Headers http.Header `json:"headers"`
61 | }
62 |
63 | // OptionsQRCode QRCode
64 | type OptionsQRCode struct {
65 | QrCode string `json:"qrCode"`
66 | }
67 |
68 | // OptionsUuid uuid
69 | type OptionsUuid struct {
70 | Uuid string `json:"uuid"`
71 | }
72 |
73 | // Options ...
74 | type Options struct {
75 | OptionsCommon
76 | OptionsBase64
77 | OptionsQRCode
78 | OptionsUrl
79 | OptionsUuid
80 | }
81 |
82 | func newOptions(options ...Option) Options {
83 | option := Options{}
84 | for _, f := range options {
85 | f(&option)
86 | }
87 | return option
88 | }
89 |
90 | //go:generate stringer -type=Type
91 | type Type uint8
92 |
93 | const (
94 | TypeUnknown Type = 0
95 |
96 | TypeBase64 = 1
97 | TypeUrl = 2
98 | TypeQRCode = 3
99 | TypeBuffer = 4
100 | TypeFile = 5
101 | TypeStream = 6
102 | TypeUuid = 7
103 | )
104 |
--------------------------------------------------------------------------------
/wechaty-puppet/filebox/type_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=Type"; DO NOT EDIT.
2 |
3 | package filebox
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[TypeUnknown-0]
12 | }
13 |
14 | const _Type_name = "TypeUnknown"
15 |
16 | var _Type_index = [...]uint8{0, 11}
17 |
18 | func (i Type) String() string {
19 | if i >= Type(len(_Type_index)-1) {
20 | return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
21 | }
22 | return _Type_name[_Type_index[i]:_Type_index[i+1]]
23 | }
24 |
--------------------------------------------------------------------------------
/wechaty-puppet/helper/array.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | type ArrayInt []int
4 |
5 | func (a ArrayInt) InArray(i int) bool {
6 | for _, v := range a {
7 | if v == i {
8 | return true
9 | }
10 | }
11 | return false
12 | }
13 |
--------------------------------------------------------------------------------
/wechaty-puppet/helper/async.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | import (
4 | "math"
5 | "runtime"
6 | "sync"
7 | )
8 |
9 | // DefaultWorkerNum default number of goroutines is twice the number of GOMAXPROCS
10 | var DefaultWorkerNum = runtime.GOMAXPROCS(0) * 2
11 |
12 | type (
13 | // IAsync interface
14 | IAsync interface {
15 | AddTask(task Task)
16 | Result() []AsyncResult
17 | }
18 |
19 | // AsyncResult result struct
20 | AsyncResult struct {
21 | Value interface{}
22 | Err error
23 | }
24 |
25 | async struct {
26 | tasks []Task
27 | wg sync.WaitGroup
28 | maxWorkerNum int
29 | }
30 |
31 | // Task task func
32 | Task func() (interface{}, error)
33 | )
34 |
35 | // NewAsync ...
36 | func NewAsync(maxWorkerNum int) IAsync {
37 | if maxWorkerNum <= 0 {
38 | maxWorkerNum = DefaultWorkerNum
39 | }
40 | return &async{
41 | maxWorkerNum: maxWorkerNum,
42 | wg: sync.WaitGroup{},
43 | }
44 | }
45 |
46 | func (a *async) AddTask(task Task) {
47 | a.tasks = append(a.tasks, task)
48 | }
49 |
50 | func (a *async) Result() []AsyncResult {
51 | taskChan := make(chan Task)
52 | resultChan := make(chan AsyncResult)
53 |
54 | taskNum := len(a.tasks)
55 | workerNum := int(math.Min(float64(taskNum), float64(a.maxWorkerNum)))
56 | a.wg.Add(taskNum)
57 |
58 | for i := 0; i < workerNum; i++ {
59 | go func() {
60 | for task := range taskChan {
61 | result := AsyncResult{}
62 | result.Value, result.Err = task()
63 | resultChan <- result
64 | a.wg.Done()
65 | }
66 | }()
67 | }
68 |
69 | go func() {
70 | for _, v := range a.tasks {
71 | taskChan <- v
72 | }
73 | a.wg.Wait()
74 | close(resultChan)
75 | close(taskChan)
76 | a.tasks = nil
77 | }()
78 |
79 | result := make([]AsyncResult, 0, taskNum)
80 | for v := range resultChan {
81 | result = append(result, v)
82 | }
83 | return result
84 | }
85 |
--------------------------------------------------------------------------------
/wechaty-puppet/helper/base64.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | // Base64OrigLength 计算 base64 数据长度
4 | // https://stackoverflow.com/questions/56140620/how-to-get-original-file-size-from-base64-encode-string
5 | func Base64OrigLength(datas string) int64 {
6 |
7 | l := int64(len(datas))
8 |
9 | // count how many trailing '=' there are (if any)
10 | eq := int64(0)
11 | if l >= 2 {
12 | if datas[l-1] == '=' {
13 | eq++
14 | }
15 | if datas[l-2] == '=' {
16 | eq++
17 | }
18 |
19 | l -= eq
20 | }
21 |
22 | // basically:
23 | // eq == 0 : bits-wasted = 0
24 | // eq == 1 : bits-wasted = 2
25 | // eq == 2 : bits-wasted = 4
26 |
27 | // so orig length == (l*6 - eq*2) / 8
28 |
29 | return (l*3 - eq) / 4
30 | }
31 |
--------------------------------------------------------------------------------
/wechaty-puppet/helper/file.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | import "os"
4 |
5 | func FileExists(path string) bool {
6 | _, err := os.Stat(path)
7 | return !os.IsNotExist(err)
8 | }
9 |
--------------------------------------------------------------------------------
/wechaty-puppet/helper/file_test.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | import "testing"
4 |
5 | func TestFileExists(t *testing.T) {
6 | type args struct {
7 | path string
8 | }
9 | tests := []struct {
10 | name string
11 | args args
12 | want bool
13 | }{
14 | {"exists", args{path: "testdata/a.txt"}, true},
15 | {"not exists", args{path: "no.txt"}, false},
16 | }
17 | for _, tt := range tests {
18 | t.Run(tt.name, func(t *testing.T) {
19 | if got := FileExists(tt.args.path); got != tt.want {
20 | t.Errorf("FileExists() = %v, want %v", got, tt.want)
21 | }
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/wechaty-puppet/helper/http_client.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | import (
4 | "net/http"
5 | "time"
6 | )
7 |
8 | var HttpClient = http.Client{
9 | Transport: nil,
10 | CheckRedirect: nil,
11 | Jar: nil,
12 | Timeout: 5 * time.Second,
13 | }
14 |
--------------------------------------------------------------------------------
/wechaty-puppet/helper/parase_recalled_msg.go:
--------------------------------------------------------------------------------
1 | package helper
2 |
3 | import (
4 | "encoding/xml"
5 | )
6 |
7 | //
8 | //
9 | //wxid_qswi83jdiiw2
10 | //12543453
11 | //500043327888834838 // 元消息id
12 | //
13 | //
14 | //
15 |
16 | // RecalledMsg 撤回消息的 xml 结构体
17 | type RecalledMsg struct {
18 | XMLName xml.Name `xml:"sysmsg"`
19 | Text string `xml:",chardata"`
20 | Type string `xml:"type,attr"`
21 | Revokemsg struct {
22 | Text string `xml:",chardata"`
23 | Session string `xml:"session"`
24 | Msgid string `xml:"msgid"`
25 | Newmsgid string `xml:"newmsgid"`
26 | Replacemsg string `xml:"replacemsg"`
27 | } `xml:"revokemsg"`
28 | }
29 |
30 | // ParseRecalledID 从 xml 中解析撤回的原始消息id
31 | func ParseRecalledID(raw string) string {
32 | msg := &RecalledMsg{}
33 | err := xml.Unmarshal([]byte(raw), msg)
34 | if err != nil {
35 | return raw
36 | }
37 | if msg.Revokemsg.Newmsgid != "" {
38 | return msg.Revokemsg.Newmsgid
39 | }
40 | return raw
41 | }
42 |
--------------------------------------------------------------------------------
/wechaty-puppet/helper/testdata/a.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wechaty/go-wechaty/dd2d312935802edc721b2b7c1719713e39f0bdab/wechaty-puppet/helper/testdata/a.txt
--------------------------------------------------------------------------------
/wechaty-puppet/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "github.com/sirupsen/logrus"
5 | "os"
6 | )
7 |
8 | // L logger
9 | var L = logrus.New()
10 |
11 | // https://github.com/wechaty/wechaty/issues/2167 兼容 wecahty 社区的 log 等级
12 | var logLevels = map[string]string{
13 | "silent": "panic",
14 | "silly": "trace",
15 | "verbose": "trace",
16 | }
17 |
18 | func init() {
19 | level := os.Getenv("WECHATY_LOG")
20 | if v, ok := logLevels[level]; ok {
21 | level = v
22 | }
23 | logLevel, err := logrus.ParseLevel(level)
24 | if err != nil {
25 | logLevel = logrus.InfoLevel
26 | }
27 | L.SetLevel(logLevel)
28 | L.SetFormatter(&logrus.TextFormatter{
29 | ForceColors: false,
30 | DisableColors: false,
31 | ForceQuote: false,
32 | DisableQuote: false,
33 | EnvironmentOverrideColors: false,
34 | DisableTimestamp: false,
35 | FullTimestamp: true,
36 | TimestampFormat: "2006-01-02 15:04:05.000",
37 | DisableSorting: false,
38 | DisableLevelTruncation: false,
39 | PadLevelText: false,
40 | QuoteEmptyFields: false,
41 | CallerPrettyfier: nil,
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/wechaty-puppet/memory-card/memory_card.go:
--------------------------------------------------------------------------------
1 | package memory_card
2 |
3 | import (
4 | "encoding/json"
5 | storage2 "github.com/wechaty/go-wechaty/wechaty-puppet/memory-card/storage"
6 | "sync"
7 | )
8 |
9 | // memory card interface
10 | type IMemoryCard interface {
11 | GetInt64(key string) int64
12 | GetString(key string) string
13 | SetInt64(key string, value int64)
14 | Clear()
15 | Delete(key string)
16 | Has(key string) bool
17 | Load() error
18 | Save() error
19 | Destroy() error
20 | SetString(key string, value string)
21 | Set(key string, value interface{})
22 | }
23 |
24 | // TODO: 我将这个地方调整为 把storage的初始化放内部,原实现者可根据情况调整一下
25 | func NewMemoryCard(name string) (IMemoryCard, error) {
26 | var storage, err = storage2.NewFileStorage(name)
27 | if err != nil {
28 | return nil, err
29 | }
30 | var memoryCard = &MemoryCard{payload: &sync.Map{}, storage: storage}
31 | return memoryCard, nil
32 | }
33 |
34 | // memory card
35 | type MemoryCard struct {
36 | payload *sync.Map
37 | storage storage2.IStorage
38 | }
39 |
40 | func (mc *MemoryCard) GetInt64(key string) int64 {
41 | switch raw := mc.get(key).(type) {
42 | case json.Number:
43 | value, _ := raw.Int64()
44 | return value
45 | case int64:
46 | return raw
47 | default:
48 | return 0
49 | }
50 | }
51 |
52 | func (mc *MemoryCard) GetString(key string) string {
53 | value, ok := mc.get(key).(string)
54 | if ok {
55 | return value
56 | }
57 | return ""
58 | }
59 |
60 | func (mc *MemoryCard) get(key string) interface{} {
61 | v, _ := mc.payload.Load(key)
62 | return v
63 | }
64 |
65 | func (mc *MemoryCard) SetInt64(key string, value int64) {
66 | mc.Set(key, value)
67 | }
68 |
69 | func (mc *MemoryCard) SetString(key string, value string) {
70 | mc.Set(key, value)
71 | }
72 |
73 | func (mc *MemoryCard) Set(key string, value interface{}) {
74 | mc.payload.Store(key, value)
75 | }
76 |
77 | func (mc *MemoryCard) Clear() {
78 | mc.payload = &sync.Map{}
79 | }
80 |
81 | func (mc *MemoryCard) Delete(key string) {
82 | mc.payload.Delete(key)
83 | }
84 |
85 | func (mc *MemoryCard) Has(key string) bool {
86 | _, ok := mc.payload.Load(key)
87 | return ok
88 | }
89 |
90 | func (mc *MemoryCard) Load() error {
91 | raw, err := mc.storage.Load()
92 | if err != nil {
93 | return err
94 | }
95 | for k, v := range raw {
96 | mc.Set(k, v)
97 | }
98 | return nil
99 | }
100 |
101 | func (mc *MemoryCard) Save() error {
102 | raw := map[string]interface{}{}
103 | mc.payload.Range(func(key, value interface{}) bool {
104 | raw[key.(string)] = value
105 | return true
106 | })
107 | return mc.storage.Save(raw)
108 | }
109 |
110 | func (mc *MemoryCard) Destroy() error {
111 | mc.Clear()
112 | return mc.storage.Destroy()
113 | }
114 |
--------------------------------------------------------------------------------
/wechaty-puppet/memory-card/memory_card_test.go:
--------------------------------------------------------------------------------
1 | package memory_card
2 |
3 | import (
4 | "crypto/md5"
5 | "fmt"
6 | "io"
7 | "math/rand"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func TestMemoryCard_GetInt(t *testing.T) {
13 | mc := newMemoryCard(t)
14 |
15 | t.Run("not value return 0", func(t *testing.T) {
16 | got := mc.GetInt64("not")
17 | if got != 0 {
18 | t.Fatalf("got %d expect 0", got)
19 | }
20 | })
21 |
22 | t.Run("Set string return 0", func(t *testing.T) {
23 | key := "set_string_return_0"
24 | mc.SetString(key, "string")
25 | got := mc.GetInt64(key)
26 | if got != 0 {
27 | t.Fatalf("got %d expect 0", got)
28 | }
29 | })
30 |
31 | t.Run("success", func(t *testing.T) {
32 | key := "success"
33 | expect := rand.Int63()
34 | mc.SetInt64(key, expect)
35 | got := mc.GetInt64(key)
36 | if got != expect {
37 | t.Fatalf("got %d expect %d", got, expect)
38 | }
39 | })
40 | }
41 |
42 | func TestMemoryCard_GetString(t *testing.T) {
43 | mc := newMemoryCard(t)
44 | t.Run("not value return empty string", func(t *testing.T) {
45 | got := mc.GetString("not")
46 | if got != "" {
47 | t.Fatalf("got %s expect empty stirng", got)
48 | }
49 | })
50 |
51 | t.Run("Set int return empty string", func(t *testing.T) {
52 | key := "set_int_return_empty_string"
53 | mc.SetInt64(key, 1)
54 | got := mc.GetString(key)
55 | if got != "" {
56 | t.Fatalf("got %s expect empty stirng", got)
57 | }
58 | })
59 |
60 | t.Run("success", func(t *testing.T) {
61 | key := "success"
62 | expect := randString()
63 | mc.SetString(key, expect)
64 | got := mc.GetString(key)
65 | if got != expect {
66 | t.Fatalf("got %s expect %s", got, expect)
67 | }
68 | })
69 | }
70 |
71 | func TestMemoryCard_SetInt(t *testing.T) {
72 | mc := newMemoryCard(t)
73 | key := "success"
74 | expect := rand.Int63()
75 | mc.SetInt64(key, expect)
76 | got := mc.GetInt64(key)
77 | if got != expect {
78 | t.Fatalf("got %d expect %d", got, expect)
79 | }
80 | }
81 |
82 | func TestMemoryCard_SetString(t *testing.T) {
83 | mc := newMemoryCard(t)
84 | key := "success"
85 | expect := randString()
86 | mc.SetString(key, expect)
87 | got := mc.GetString(key)
88 | if got != expect {
89 | t.Fatalf("got %s expect %s", got, expect)
90 | }
91 | }
92 |
93 | func TestMemoryCard_Clear(t *testing.T) {
94 | mc := newMemoryCard(t)
95 | table := []struct {
96 | key string
97 | value int64
98 | }{
99 | {"key1", 1},
100 | {"key2", 2},
101 | }
102 | for _, v := range table {
103 | mc.SetInt64(v.key, v.value)
104 | }
105 | mc.Clear()
106 | for _, v := range table {
107 | got := mc.GetInt64(v.key)
108 | if got != 0 {
109 | t.Fatalf("got %d expect 0", got)
110 | }
111 | }
112 | }
113 |
114 | func TestMemoryCard_Delete(t *testing.T) {
115 | mc := newMemoryCard(t)
116 | table := []struct {
117 | key string
118 | value int64
119 | }{
120 | {"key1", 1},
121 | {"key2", 2},
122 | }
123 | for _, v := range table {
124 | mc.SetInt64(v.key, v.value)
125 | }
126 | mc.Delete(table[0].key)
127 | got := mc.GetInt64(table[0].key)
128 | if got != 0 {
129 | t.Fatalf("got %d expect 0", got)
130 | }
131 | got = mc.GetInt64(table[1].key)
132 | if got != table[1].value {
133 | t.Fatalf("got %d expect d", table[1].value)
134 | }
135 | }
136 |
137 | func TestMemoryCard_Has(t *testing.T) {
138 | mc := newMemoryCard(t)
139 | if false != mc.Has("null") {
140 | t.Fatalf("got true expect false")
141 | }
142 | mc.SetString("key", "value")
143 | if true != mc.Has("key") {
144 | t.Fatalf("got false expect false")
145 | }
146 | }
147 |
148 | func TestMemoryCard_SaveAndLoad(t *testing.T) {
149 | table := []struct {
150 | key string
151 | value int64
152 | }{
153 | {"key0", 0},
154 | {"key1", 1},
155 | {"key2", 2},
156 | }
157 | mc1 := newMemoryCard(t)
158 | for _, v := range table {
159 | mc1.Set(v.key, v.value)
160 | }
161 | err := mc1.Save()
162 | if err != nil {
163 | t.Fatal(err.Error())
164 | }
165 | mc2 := newMemoryCard(t)
166 | err = mc2.Load()
167 | if err != nil {
168 | t.Fatalf(err.Error())
169 | }
170 | for _, v := range table {
171 | got := mc2.GetInt64(v.key)
172 | if got != v.value {
173 | t.Fatalf("got %d expected %d", got, v.value)
174 | }
175 | }
176 | }
177 |
178 | func randString() string {
179 | t := time.Now()
180 | h := md5.New()
181 | _, _ = io.WriteString(h, t.String())
182 | return fmt.Sprintf("%x", h.Sum(nil))
183 | }
184 |
185 | func newMemoryCard(t *testing.T) IMemoryCard {
186 | mc, err := NewMemoryCard("testdata/test")
187 | if err != nil {
188 | t.Fatalf(err.Error())
189 | }
190 | return mc
191 | }
192 |
--------------------------------------------------------------------------------
/wechaty-puppet/memory-card/storage/backend.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | type IStorage interface {
4 | Save(payload map[string]interface{}) error
5 | Load() (map[string]interface{}, error)
6 | Destroy() error
7 | }
8 |
--------------------------------------------------------------------------------
/wechaty-puppet/memory-card/storage/file.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "encoding/json"
5 | helper_functions "github.com/wechaty/go-wechaty/wechaty-puppet/helper"
6 | "io/ioutil"
7 | "os"
8 | "path/filepath"
9 | "strings"
10 | )
11 |
12 | type FileStorage struct {
13 | absFileName string
14 | }
15 |
16 | func NewFileStorage(absFileName string) (*FileStorage, error) {
17 | absFileName, err := handleAbsFileName(absFileName)
18 | if err != nil {
19 | return nil, err
20 | }
21 | return &FileStorage{absFileName: absFileName}, nil
22 | }
23 |
24 | func (f *FileStorage) Save(payload map[string]interface{}) error {
25 | jsonBytes, err := json.Marshal(payload)
26 | if err != nil {
27 | return err
28 | }
29 | return ioutil.WriteFile(f.absFileName, jsonBytes, os.ModePerm)
30 | }
31 |
32 | func (f *FileStorage) Load() (map[string]interface{}, error) {
33 | if !helper_functions.FileExists(f.absFileName) {
34 | return map[string]interface{}{}, nil
35 | }
36 | file, err := os.Open(f.absFileName)
37 | if err != nil {
38 | return nil, err
39 | }
40 | result := map[string]interface{}{}
41 | decoder := json.NewDecoder(file)
42 | decoder.UseNumber()
43 | if err := decoder.Decode(&result); err != nil {
44 | return nil, err
45 | }
46 | return result, nil
47 | }
48 |
49 | func (f *FileStorage) Destroy() error {
50 | return os.Remove(f.absFileName)
51 | }
52 |
53 | func handleAbsFileName(absFileName string) (string, error) {
54 | const suffix = ".memory-card.json"
55 | if !strings.HasSuffix(absFileName, suffix) {
56 | absFileName = absFileName + suffix
57 | }
58 | if !filepath.IsAbs(absFileName) {
59 | dir, err := os.Getwd()
60 | if err != nil {
61 | return "", err
62 | }
63 | absFileName = filepath.Join(dir, absFileName)
64 | }
65 | return absFileName, nil
66 | }
67 |
--------------------------------------------------------------------------------
/wechaty-puppet/memory-card/storage/file_test.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "log"
5 | "reflect"
6 | "testing"
7 | )
8 |
9 | var data = map[string]interface{}{
10 | "key1": "key1",
11 | "key2": "key2",
12 | }
13 |
14 | func TestFileStorage_Save(t *testing.T) {
15 | storage := newFileStorage(t)
16 | err := storage.Save(data)
17 | if err != nil {
18 | t.Fatalf(err.Error())
19 | }
20 | }
21 |
22 | func TestFileStorage_Load(t *testing.T) {
23 | storage := newFileStorage(t)
24 | got, err := storage.Load()
25 | if err != nil {
26 | log.Fatalf(err.Error())
27 | }
28 | if !reflect.DeepEqual(got, data) {
29 | log.Fatalf("got %v expect %v", got, data)
30 | }
31 | }
32 |
33 | func TestNopStorage_Destroy(t *testing.T) {
34 | storage := newFileStorage(t)
35 | err := storage.Destroy()
36 | if err != nil {
37 | t.Fatalf(err.Error())
38 | }
39 | }
40 |
41 | func newFileStorage(t *testing.T) *FileStorage {
42 | storage, err := NewFileStorage("testdata/file")
43 | if err != nil {
44 | t.Fatalf(err.Error())
45 | }
46 | return storage
47 | }
48 |
--------------------------------------------------------------------------------
/wechaty-puppet/memory-card/storage/nop.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | type NopStorage struct {
4 | }
5 |
6 | func (n NopStorage) Save(payload map[string]interface{}) error {
7 | return nil
8 | }
9 |
10 | func (n NopStorage) Load() (map[string]interface{}, error) {
11 | return map[string]interface{}{}, nil
12 | }
13 |
14 | func (n NopStorage) Destroy() {}
15 |
--------------------------------------------------------------------------------
/wechaty-puppet/memory-card/storage/testdata/.gitignore:
--------------------------------------------------------------------------------
1 | file.memory-card.json
2 |
--------------------------------------------------------------------------------
/wechaty-puppet/memory-card/testdata/.gitignore:
--------------------------------------------------------------------------------
1 | *.memory-card.json
2 |
--------------------------------------------------------------------------------
/wechaty-puppet/message_adapter.go:
--------------------------------------------------------------------------------
1 | package wechatypuppet
2 |
3 | import (
4 | "github.com/wechaty/go-wechaty/wechaty-puppet/helper"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | "regexp"
7 | )
8 |
9 | var numRegex = regexp.MustCompile(`^\d+$`)
10 |
11 | var rawMsgAdapter = RawMsgAdapter{}
12 | var unknownMsgAdapter = UnknownMsgAdapter{}
13 | var recalledMsgAdapter = RecalledMsgAdapter{}
14 |
15 | // MsgAdapter 消息适配器
16 | type MsgAdapter interface {
17 | Handle(payload *schemas.MessagePayload)
18 | }
19 |
20 | // NewMsgAdapter 各种 puppet 返回的消息有出入,这里做统一
21 | func NewMsgAdapter(msgType schemas.MessageType) MsgAdapter {
22 | switch msgType {
23 | case schemas.MessageTypeUnknown:
24 | return unknownMsgAdapter
25 | case schemas.MessageTypeRecalled:
26 | return recalledMsgAdapter
27 | }
28 | return rawMsgAdapter
29 | }
30 |
31 | // RawMsgAdapter 不需要处理的消息
32 | type RawMsgAdapter struct{}
33 |
34 | // Handle ~
35 | func (r RawMsgAdapter) Handle(msg *schemas.MessagePayload) {}
36 |
37 | // UnknownMsgAdapter Unknown 类型的消息适配器
38 | type UnknownMsgAdapter struct{}
39 |
40 | // Handle 对 Unknown 类型的消息做适配
41 | func (u UnknownMsgAdapter) Handle(payload *schemas.MessagePayload) {
42 | // 有些消息,puppet 服务端没有解析出来,这里尝试解析
43 | helper.FixUnknownMessage(payload)
44 | }
45 |
46 | // RecalledMsgAdapter 撤回类型的消息适配器
47 | type RecalledMsgAdapter struct{}
48 |
49 | // Handle padlocal 返回的是 xml,需要解析出 msgId
50 | func (r RecalledMsgAdapter) Handle(payload *schemas.MessagePayload) {
51 | if numRegex.MatchString(payload.Text) {
52 | return
53 | }
54 | // padlocal 返回的是 xml,需要解析出 msgId
55 | // https://github.com/wechaty/go-wechaty/issues/87
56 | payload.Text = helper.ParseRecalledID(payload.Text)
57 | }
58 |
--------------------------------------------------------------------------------
/wechaty-puppet/option.go:
--------------------------------------------------------------------------------
1 | package wechatypuppet
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // Option puppet option
8 | type Option struct {
9 | Endpoint string
10 | Timeout time.Duration
11 | Token string
12 |
13 | // Deprecated: move to wechaty-puppet-service
14 | GrpcReconnectInterval time.Duration
15 | }
16 |
17 | // OptionFn func
18 | type OptionFn func(opts *Option)
19 |
20 | // WithEndpoint with Endpoint
21 | func WithEndpoint(endpoint string) OptionFn {
22 | return func(opts *Option) {
23 | opts.Endpoint = endpoint
24 | }
25 | }
26 |
27 | // WithTimeout with Timeout
28 | func WithTimeout(duration time.Duration) OptionFn {
29 | return func(opts *Option) {
30 | opts.Timeout = duration
31 | }
32 | }
33 |
34 | // WithToken with Token
35 | func WithToken(token string) OptionFn {
36 | return func(opts *Option) {
37 | opts.Token = token
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/contact.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | import "regexp"
4 |
5 | //go:generate stringer -type=ContactGender
6 | type ContactGender uint8
7 |
8 | const (
9 | ContactGenderUnknown ContactGender = 0
10 | ContactGenderMale ContactGender = 1
11 | ContactGenderFemale ContactGender = 2
12 | )
13 |
14 | //go:generate stringer -type=ContactType
15 | type ContactType uint8
16 |
17 | const (
18 | ContactTypeUnknown ContactType = 0
19 | ContactTypePersonal ContactType = 1
20 | ContactTypeOfficial ContactType = 2
21 | )
22 |
23 | // ContactQueryFilter use the first non-empty parameter of all parameters to search
24 | type ContactQueryFilter struct {
25 | // 别名过滤
26 | Alias string
27 | // 别名正则表达式过滤
28 | AliasRegexp *regexp.Regexp
29 | // id 过滤
30 | Id string
31 | // 昵称过滤
32 | Name string
33 | // 昵称正则表达式过滤
34 | NameRegexp *regexp.Regexp
35 | WeiXin string
36 | }
37 |
38 | type ContactPayload struct {
39 | Id string
40 | Gender ContactGender
41 | Type ContactType
42 | Name string
43 | Avatar string
44 | Address string
45 | Alias string
46 | City string
47 | Friend bool
48 | Province string
49 | Signature string
50 | Star bool
51 | WeiXin string
52 | }
53 |
54 | type ContactPayloadFilterFunction func(payload *ContactPayload) bool
55 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/contactgender_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=ContactGender"; DO NOT EDIT.
2 |
3 | package schemas
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[ContactGenderUnknown-0]
12 | _ = x[ContactGenderMale-1]
13 | _ = x[ContactGenderFemale-2]
14 | }
15 |
16 | const _ContactGender_name = "ContactGenderUnknownContactGenderMaleContactGenderFemale"
17 |
18 | var _ContactGender_index = [...]uint8{0, 20, 37, 56}
19 |
20 | func (i ContactGender) String() string {
21 | if i >= ContactGender(len(_ContactGender_index)-1) {
22 | return "ContactGender(" + strconv.FormatInt(int64(i), 10) + ")"
23 | }
24 | return _ContactGender_name[_ContactGender_index[i]:_ContactGender_index[i+1]]
25 | }
26 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/contacttype_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=ContactType"; DO NOT EDIT.
2 |
3 | package schemas
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[ContactTypeUnknown-0]
12 | _ = x[ContactTypePersonal-1]
13 | _ = x[ContactTypeOfficial-2]
14 | }
15 |
16 | const _ContactType_name = "ContactTypeUnknownContactTypePersonalContactTypeOfficial"
17 |
18 | var _ContactType_index = [...]uint8{0, 18, 37, 56}
19 |
20 | func (i ContactType) String() string {
21 | if i >= ContactType(len(_ContactType_index)-1) {
22 | return "ContactType(" + strconv.FormatInt(int64(i), 10) + ")"
23 | }
24 | return _ContactType_name[_ContactType_index[i]:_ContactType_index[i+1]]
25 | }
26 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/events.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | //go:generate stringer -type=ScanStatus
4 | type ScanStatus uint8
5 |
6 | const (
7 | ScanStatusUnknown ScanStatus = 0
8 | ScanStatusCancel ScanStatus = 1
9 | ScanStatusWaiting ScanStatus = 2
10 | ScanStatusScanned ScanStatus = 3
11 | ScanStatusConfirmed ScanStatus = 4
12 | ScanStatusTimeout ScanStatus = 5
13 | )
14 |
15 | type EventFriendshipPayload struct {
16 | FriendshipID string
17 | }
18 |
19 | type EventLoginPayload struct {
20 | ContactId string
21 | }
22 |
23 | type EventLogoutPayload struct {
24 | ContactId string
25 | Data string
26 | }
27 |
28 | type EventMessagePayload struct {
29 | MessageId string
30 | }
31 |
32 | type EventRoomInvitePayload struct {
33 | RoomInvitationId string
34 | }
35 |
36 | type EventRoomJoinPayload struct {
37 | InviteeIdList []string
38 | InviterId string
39 | RoomId string
40 | Timestamp int64
41 | }
42 |
43 | type EventRoomLeavePayload struct {
44 | RemoveeIdList []string
45 | RemoverId string
46 | RoomId string
47 | Timestamp int64
48 | }
49 |
50 | type EventRoomTopicPayload struct {
51 | ChangerId string
52 | NewTopic string
53 | OldTopic string
54 | RoomId string
55 | Timestamp int64
56 | }
57 |
58 | type EventScanPayload struct {
59 | BaseEventPayload
60 | Status ScanStatus
61 | QrCode string
62 | }
63 |
64 | type EventDirtyPayload struct {
65 | PayloadType PayloadType
66 | PayloadId string
67 | }
68 |
69 | type BaseEventPayload struct {
70 | Data string
71 | }
72 |
73 | type EventDongPayload = BaseEventPayload
74 |
75 | type EventErrorPayload = BaseEventPayload
76 |
77 | type EventReadyPayload = BaseEventPayload
78 |
79 | type EventResetPayload = BaseEventPayload
80 |
81 | type EventHeartbeatPayload = BaseEventPayload
82 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/friendship.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | //go:generate stringer -type=FriendshipType
4 | type FriendshipType uint8
5 |
6 | const (
7 | FriendshipTypeUnknown FriendshipType = 0
8 | FriendshipTypeConfirm FriendshipType = 1
9 | FriendshipTypeReceive FriendshipType = 2
10 | FriendshipTypeVerify FriendshipType = 3
11 | )
12 |
13 | type FriendshipSceneType uint8
14 |
15 | const (
16 | FriendshipSceneTypeUnknown FriendshipSceneType = 0
17 | FriendshipSceneTypeQQ FriendshipSceneType = 1
18 | FriendshipSceneTypeEmail FriendshipSceneType = 2
19 | FriendshipSceneTypeWeiXin FriendshipSceneType = 3
20 | FriendshipSceneTypeQQTBD FriendshipSceneType = 12 // QQ号搜索
21 | FriendshipSceneTypeRoom FriendshipSceneType = 14
22 | FriendshipSceneTypePhone FriendshipSceneType = 15
23 | FriendshipSceneTypeCard FriendshipSceneType = 17 // 名片分享
24 | FriendshipSceneTypeLocation FriendshipSceneType = 18
25 | FriendshipSceneTypeBottle FriendshipSceneType = 25
26 | FriendshipSceneTypeShaking FriendshipSceneType = 29
27 | FriendshipSceneTypeQRCode FriendshipSceneType = 30
28 | )
29 |
30 | type FriendshipPayloadBase struct {
31 | Id string `json:"id"`
32 | ContactId string `json:"contactId"`
33 | Hello string `json:"hello"`
34 | Timestamp int64 `json:"timestamp"`
35 | }
36 |
37 | type FriendshipPayloadConfirm struct {
38 | FriendshipPayloadBase
39 | Type FriendshipType // FriendshipTypeConfirm
40 | }
41 |
42 | type FriendshipPayloadReceive struct {
43 | FriendshipPayloadBase
44 | Type FriendshipType `json:"type"` // FriendshipTypeReceive
45 | Scene FriendshipSceneType `json:"scene"`
46 | Stranger string `json:"stranger"`
47 | Ticket string `json:"ticket"`
48 | }
49 |
50 | type FriendshipPayloadVerify struct {
51 | FriendshipPayloadBase
52 | Type FriendshipType // FriendshipTypeVerify
53 | }
54 |
55 | type FriendshipPayload struct {
56 | FriendshipPayloadReceive
57 | }
58 |
59 | // FriendshipSearchCondition use the first non-empty parameter of all parameters to search
60 | type FriendshipSearchCondition struct {
61 | Phone string
62 | WeiXin string
63 | }
64 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/friendshiptype_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=FriendshipType"; DO NOT EDIT.
2 |
3 | package schemas
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[FriendshipTypeUnknown-0]
12 | _ = x[FriendshipTypeConfirm-1]
13 | _ = x[FriendshipTypeReceive-2]
14 | _ = x[FriendshipTypeVerify-3]
15 | }
16 |
17 | const _FriendshipType_name = "FriendshipTypeUnknownFriendshipTypeConfirmFriendshipTypeReceiveFriendshipTypeVerify"
18 |
19 | var _FriendshipType_index = [...]uint8{0, 21, 42, 63, 83}
20 |
21 | func (i FriendshipType) String() string {
22 | if i >= FriendshipType(len(_FriendshipType_index)-1) {
23 | return "FriendshipType(" + strconv.FormatInt(int64(i), 10) + ")"
24 | }
25 | return _FriendshipType_name[_FriendshipType_index[i]:_FriendshipType_index[i+1]]
26 | }
27 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/image.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | //go:generate stringer -type=ImageType
4 | type ImageType uint8
5 |
6 | const (
7 | ImageTypeUnknown ImageType = iota
8 | ImageTypeThumbnail
9 | ImageTypeHD
10 | ImageTypeArtwork
11 | )
12 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/imagetype_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=ImageType"; DO NOT EDIT.
2 |
3 | package schemas
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[ImageTypeUnknown-0]
12 | _ = x[ImageTypeThumbnail-1]
13 | _ = x[ImageTypeHD-2]
14 | _ = x[ImageTypeArtwork-3]
15 | }
16 |
17 | const _ImageType_name = "ImageTypeUnknownImageTypeThumbnailImageTypeHDImageTypeArtwork"
18 |
19 | var _ImageType_index = [...]uint8{0, 16, 34, 45, 61}
20 |
21 | func (i ImageType) String() string {
22 | if i >= ImageType(len(_ImageType_index)-1) {
23 | return "ImageType(" + strconv.FormatInt(int64(i), 10) + ")"
24 | }
25 | return _ImageType_name[_ImageType_index[i]:_ImageType_index[i+1]]
26 | }
27 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/location.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | type LocationPayload struct {
4 | Accuracy float32 `json:"accuracy"` // Estimated horizontal accuracy of this location, radial, in meters. (same as Android & iOS API)
5 | Address string `json:"address"` // 北京市北京市海淀区45 Chengfu Rd
6 | Latitude float64 `json:"latitude"` // 39.995120999999997
7 | Longitude float64 `json:"longitude"` // 116.3341
8 | Name string `json:"name"` // 东升乡人民政府(海淀区成府路45号)
9 | }
10 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/message.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | import (
4 | "regexp"
5 | "time"
6 | )
7 |
8 | //go:generate stringer -type=MessageType
9 | type MessageType uint8
10 |
11 | const (
12 | MessageTypeUnknown MessageType = 0
13 | MessageTypeAttachment MessageType = 1
14 | MessageTypeAudio MessageType = 2
15 | MessageTypeContact MessageType = 3
16 | MessageTypeChatHistory MessageType = 4
17 | MessageTypeEmoticon MessageType = 5
18 | MessageTypeImage MessageType = 6
19 | MessageTypeText MessageType = 7
20 | MessageTypeLocation MessageType = 8
21 | MessageTypeMiniProgram MessageType = 9
22 | MessageTypeGroupNote MessageType = 10
23 | MessageTypeTransfer MessageType = 11
24 | MessageTypeRedEnvelope MessageType = 12
25 | MessageTypeRecalled MessageType = 13
26 | MessageTypeURL MessageType = 14
27 | MessageTypeVideo MessageType = 15
28 | )
29 |
30 | type WeChatAppMessageType int
31 |
32 | const (
33 | WeChatAppMessageTypeText WeChatAppMessageType = 1
34 | WeChatAppMessageTypeImg WeChatAppMessageType = 2
35 | WeChatAppMessageTypeAudio WeChatAppMessageType = 3
36 | WeChatAppMessageTypeVideo WeChatAppMessageType = 4
37 | WeChatAppMessageTypeUrl WeChatAppMessageType = 5
38 | WeChatAppMessageTypeAttach WeChatAppMessageType = 6
39 | WeChatAppMessageTypeOpen WeChatAppMessageType = 7
40 | WeChatAppMessageTypeEmoji WeChatAppMessageType = 8
41 | WeChatAppMessageTypeVoiceRemind WeChatAppMessageType = 9
42 | WeChatAppMessageTypeScanGood WeChatAppMessageType = 10
43 | WeChatAppMessageTypeGood WeChatAppMessageType = 13
44 | WeChatAppMessageTypeEmotion WeChatAppMessageType = 15
45 | WeChatAppMessageTypeCardTicket WeChatAppMessageType = 16
46 | WeChatAppMessageTypeRealtimeShareLocation WeChatAppMessageType = 17
47 | WeChatAppMessageTypeChatHistory WeChatAppMessageType = 19
48 | WeChatAppMessageTypeMiniProgram WeChatAppMessageType = 33
49 | WeChatAppMessageTypeTransfers WeChatAppMessageType = 2000
50 | WeChatAppMessageTypeRedEnvelopes WeChatAppMessageType = 2001
51 | WeChatAppMessageTypeReaderType WeChatAppMessageType = 100001
52 | )
53 |
54 | type WeChatMessageType int
55 |
56 | const (
57 | WeChatMessageTypeText WeChatMessageType = 1
58 | WeChatMessageTypeImage WeChatMessageType = 3
59 | WeChatMessageTypeVoice WeChatMessageType = 34
60 | WeChatMessageTypeVerifyMsg WeChatMessageType = 37
61 | WeChatMessageTypePossibleFriendMsg WeChatMessageType = 40
62 | WeChatMessageTypeShareCard WeChatMessageType = 42
63 | WeChatMessageTypeVideo WeChatMessageType = 43
64 | WeChatMessageTypeEmoticon WeChatMessageType = 47
65 | WeChatMessageTypeLocation WeChatMessageType = 48
66 | WeChatMessageTypeApp WeChatMessageType = 49
67 | WeChatMessageTypeVOIPMsg WeChatMessageType = 50
68 | WeChatMessageTypeStatusNotify WeChatMessageType = 51
69 | WeChatMessageTypeVOIPNotify WeChatMessageType = 52
70 | WeChatMessageTypeVOIPInvite WeChatMessageType = 53
71 | WeChatMessageTypeMicroVideo WeChatMessageType = 62
72 | WeChatMessageTypeTransfer WeChatMessageType = 2000 // 转账
73 | WeChatMessageTypeRedEnvelope WeChatMessageType = 2001 // 红包
74 | WeChatMessageTypeMiniProgram WeChatMessageType = 2002 // 小程序
75 | WeChatMessageTypeGroupInvite WeChatMessageType = 2003 // 群邀请
76 | WeChatMessageTypeFile WeChatMessageType = 2004 // 文件消息
77 | WeChatMessageTypeSysNotice WeChatMessageType = 9999
78 | WeChatMessageTypeSys WeChatMessageType = 10000
79 | WeChatMessageTypeRecalled WeChatMessageType = 10002
80 | )
81 |
82 | type MessagePayloadBase struct {
83 | Id string
84 |
85 | // use message id to get rawPayload to get those informations when needed
86 | // contactId string // Contact ShareCard
87 | MentionIdList []string // Mentioned Contacts' Ids
88 |
89 | FileName string
90 | Text string
91 | Timestamp time.Time
92 | Type MessageType
93 |
94 | // 小程序有些消息类型,wechaty服务端解析不处理,框架端解析。 xml type 36 是小程序
95 | FixMiniApp bool
96 |
97 | ReferMessage *ReferMessagePayload
98 | }
99 |
100 | // ReferMessagePayload refer message payload
101 | type ReferMessagePayload struct {
102 | Type MessageType // TODO: 确认是否和 MessageType 一致
103 | SourceMsgID string
104 | TalkerId string
105 | RoomId string
106 | DisplayName string
107 | Content string
108 | Timestamp time.Time
109 | }
110 |
111 | type MessagePayloadRoom struct {
112 | TalkerId string
113 | RoomId string
114 | ListenerId string
115 | }
116 |
117 | type MessagePayloadTo = MessagePayloadRoom
118 |
119 | type MessagePayload struct {
120 | MessagePayloadBase
121 | MessagePayloadRoom
122 | }
123 |
124 | type MessageQueryFilter struct {
125 | TalkerId string
126 | // Deprecated: please use TalkerId
127 | FromId string
128 | Id string
129 | RoomId string
130 | Text string
131 | TextRegExp *regexp.Regexp
132 | // Deprecated: please use ListenerId
133 | ToId string
134 | ListenerId string
135 | Type MessageType
136 | }
137 |
138 | type MessagePayloadFilterFunction func(payload *MessagePayload) bool
139 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/messagetype_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=MessageType"; DO NOT EDIT.
2 |
3 | package schemas
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[MessageTypeUnknown-0]
12 | _ = x[MessageTypeAttachment-1]
13 | _ = x[MessageTypeAudio-2]
14 | _ = x[MessageTypeContact-3]
15 | _ = x[MessageTypeChatHistory-4]
16 | _ = x[MessageTypeEmoticon-5]
17 | _ = x[MessageTypeImage-6]
18 | _ = x[MessageTypeText-7]
19 | _ = x[MessageTypeLocation-8]
20 | _ = x[MessageTypeMiniProgram-9]
21 | _ = x[MessageTypeGroupNote-10]
22 | _ = x[MessageTypeTransfer-11]
23 | _ = x[MessageTypeRedEnvelope-12]
24 | _ = x[MessageTypeRecalled-13]
25 | _ = x[MessageTypeURL-14]
26 | _ = x[MessageTypeVideo-15]
27 | }
28 |
29 | const _MessageType_name = "MessageTypeUnknownMessageTypeAttachmentMessageTypeAudioMessageTypeContactMessageTypeChatHistoryMessageTypeEmoticonMessageTypeImageMessageTypeTextMessageTypeLocationMessageTypeMiniProgramMessageTypeGroupNoteMessageTypeTransferMessageTypeRedEnvelopeMessageTypeRecalledMessageTypeURLMessageTypeVideo"
30 |
31 | var _MessageType_index = [...]uint16{0, 18, 39, 55, 73, 95, 114, 130, 145, 164, 186, 206, 225, 247, 266, 280, 296}
32 |
33 | func (i MessageType) String() string {
34 | if i >= MessageType(len(_MessageType_index)-1) {
35 | return "MessageType(" + strconv.FormatInt(int64(i), 10) + ")"
36 | }
37 | return _MessageType_name[_MessageType_index[i]:_MessageType_index[i+1]]
38 | }
39 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/mini_program.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | import "encoding/json"
4 |
5 | type MiniProgramPayload struct {
6 | Appid string `json:"appid"` // optional, Appid, get from wechat (mp.weixin.qq.com)
7 | Description string `json:"description"` // optional, mini program title
8 | PagePath string `json:"pagePath"` // optional, mini program page path
9 | ThumbUrl string `json:"thumbUrl"` // optional, default picture, convert to thumbnail
10 | Title string `json:"title"` // optional, mini program title
11 | Username string `json:"username"` // original ID, get from wechat (mp.weixin.qq.com)
12 | ThumbKey string `json:"thumbKey"` // original, thumbnailurl and thumbkey will make the headphoto of mini-program better
13 | ShareId string `json:"shareId"` // optional, the unique userId for who share this mini program
14 | IconUrl string `json:"iconUrl"` // optional, mini program icon url
15 | }
16 |
17 | func (m *MiniProgramPayload) ToJson() string {
18 | b, _ := json.Marshal(m)
19 | return string(b)
20 | }
21 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/payload.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | //go:generate stringer -type=PayloadType
4 |
5 | // PayloadType ...
6 | type PayloadType int32
7 |
8 | const (
9 | // PayloadTypeUnknown unknown
10 | PayloadTypeUnknown PayloadType = iota
11 | PayloadTypeMessage
12 | PayloadTypeContact
13 | PayloadTypeRoom
14 | PayloadTypeRoomMember
15 | PayloadTypeFriendship
16 | )
17 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/payloadtype_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=PayloadType"; DO NOT EDIT.
2 |
3 | package schemas
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[PayloadTypeUnknown-0]
12 | _ = x[PayloadTypeMessage-1]
13 | _ = x[PayloadTypeContact-2]
14 | _ = x[PayloadTypeRoom-3]
15 | _ = x[PayloadTypeRoomMember-4]
16 | _ = x[PayloadTypeFriendship-5]
17 | }
18 |
19 | const _PayloadType_name = "PayloadTypeUnknownPayloadTypeMessagePayloadTypeContactPayloadTypeRoomPayloadTypeRoomMemberPayloadTypeFriendship"
20 |
21 | var _PayloadType_index = [...]uint8{0, 18, 36, 54, 69, 90, 111}
22 |
23 | func (i PayloadType) String() string {
24 | if i < 0 || i >= PayloadType(len(_PayloadType_index)-1) {
25 | return "PayloadType(" + strconv.FormatInt(int64(i), 10) + ")"
26 | }
27 | return _PayloadType_name[_PayloadType_index[i]:_PayloadType_index[i+1]]
28 | }
29 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/puppet.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | import pbwechaty "github.com/wechaty/go-grpc/wechaty/puppet"
4 |
5 | //go:generate stringer -type=PuppetEventName
6 | type PuppetEventName uint8
7 |
8 | const (
9 | PuppetEventNameUnknown PuppetEventName = iota
10 | PuppetEventNameFriendship
11 | PuppetEventNameLogin
12 | PuppetEventNameLogout
13 | PuppetEventNameMessage
14 | PuppetEventNameRoomInvite
15 | PuppetEventNameRoomJoin
16 | PuppetEventNameRoomLeave
17 | PuppetEventNameRoomTopic
18 | PuppetEventNameScan
19 |
20 | PuppetEventNameDong
21 | PuppetEventNameError
22 | PuppetEventNameHeartbeat
23 | PuppetEventNameReady
24 | PuppetEventNameReset
25 | PuppetEventNameDirty
26 |
27 | PuppetEventNameStop
28 | PuppetEventNameStart
29 | )
30 |
31 | var eventNames = []PuppetEventName{
32 | PuppetEventNameFriendship,
33 | PuppetEventNameLogin,
34 | PuppetEventNameLogout,
35 | PuppetEventNameMessage,
36 | PuppetEventNameRoomInvite,
37 | PuppetEventNameRoomJoin,
38 | PuppetEventNameRoomLeave,
39 | PuppetEventNameRoomTopic,
40 | PuppetEventNameScan,
41 |
42 | PuppetEventNameDong,
43 | PuppetEventNameError,
44 | PuppetEventNameHeartbeat,
45 | PuppetEventNameReady,
46 | PuppetEventNameReset,
47 | PuppetEventNameDirty,
48 |
49 | PuppetEventNameStop,
50 | PuppetEventNameStart,
51 | }
52 |
53 | func GetEventNames() []PuppetEventName {
54 | return eventNames
55 | }
56 |
57 | var pbEventType2PuppetEventName = map[pbwechaty.EventType]PuppetEventName{
58 | pbwechaty.EventType_EVENT_TYPE_DONG: PuppetEventNameDong,
59 | pbwechaty.EventType_EVENT_TYPE_ERROR: PuppetEventNameError,
60 | pbwechaty.EventType_EVENT_TYPE_HEARTBEAT: PuppetEventNameHeartbeat,
61 | pbwechaty.EventType_EVENT_TYPE_FRIENDSHIP: PuppetEventNameFriendship,
62 | pbwechaty.EventType_EVENT_TYPE_LOGIN: PuppetEventNameLogin,
63 | pbwechaty.EventType_EVENT_TYPE_LOGOUT: PuppetEventNameLogout,
64 | pbwechaty.EventType_EVENT_TYPE_MESSAGE: PuppetEventNameMessage,
65 | pbwechaty.EventType_EVENT_TYPE_READY: PuppetEventNameReady,
66 | pbwechaty.EventType_EVENT_TYPE_ROOM_INVITE: PuppetEventNameRoomInvite,
67 | pbwechaty.EventType_EVENT_TYPE_ROOM_JOIN: PuppetEventNameRoomJoin,
68 | pbwechaty.EventType_EVENT_TYPE_ROOM_LEAVE: PuppetEventNameRoomLeave,
69 | pbwechaty.EventType_EVENT_TYPE_ROOM_TOPIC: PuppetEventNameRoomTopic,
70 | pbwechaty.EventType_EVENT_TYPE_SCAN: PuppetEventNameScan,
71 | pbwechaty.EventType_EVENT_TYPE_RESET: PuppetEventNameReset,
72 | pbwechaty.EventType_EVENT_TYPE_UNSPECIFIED: PuppetEventNameUnknown,
73 | pbwechaty.EventType_EVENT_TYPE_DIRTY: PuppetEventNameDirty,
74 | }
75 |
76 | // PbEventType2PuppetEventName grpc event map wechaty-puppet event name
77 | func PbEventType2PuppetEventName() map[pbwechaty.EventType]PuppetEventName {
78 | return pbEventType2PuppetEventName
79 | }
80 |
81 | var pbEventType2GeneratePayloadFunc = map[pbwechaty.EventType]func() interface{}{
82 | pbwechaty.EventType_EVENT_TYPE_DONG: func() interface{} { return &EventDongPayload{} },
83 | pbwechaty.EventType_EVENT_TYPE_ERROR: func() interface{} { return &EventErrorPayload{} },
84 | pbwechaty.EventType_EVENT_TYPE_HEARTBEAT: func() interface{} { return &EventHeartbeatPayload{} },
85 | pbwechaty.EventType_EVENT_TYPE_FRIENDSHIP: func() interface{} { return &EventFriendshipPayload{} },
86 | pbwechaty.EventType_EVENT_TYPE_LOGIN: func() interface{} { return &EventLoginPayload{} },
87 | pbwechaty.EventType_EVENT_TYPE_LOGOUT: func() interface{} { return &EventLogoutPayload{} },
88 | pbwechaty.EventType_EVENT_TYPE_MESSAGE: func() interface{} { return &EventMessagePayload{} },
89 | pbwechaty.EventType_EVENT_TYPE_READY: func() interface{} { return &EventReadyPayload{} },
90 | pbwechaty.EventType_EVENT_TYPE_ROOM_INVITE: func() interface{} { return &EventRoomInvitePayload{} },
91 | pbwechaty.EventType_EVENT_TYPE_ROOM_JOIN: func() interface{} { return &EventRoomJoinPayload{} },
92 | pbwechaty.EventType_EVENT_TYPE_ROOM_LEAVE: func() interface{} { return &EventRoomLeavePayload{} },
93 | pbwechaty.EventType_EVENT_TYPE_ROOM_TOPIC: func() interface{} { return &EventRoomTopicPayload{} },
94 | pbwechaty.EventType_EVENT_TYPE_SCAN: func() interface{} { return &EventScanPayload{} },
95 | pbwechaty.EventType_EVENT_TYPE_RESET: func() interface{} { return &EventResetPayload{} },
96 | pbwechaty.EventType_EVENT_TYPE_DIRTY: func() interface{} { return &EventDirtyPayload{} },
97 | }
98 |
99 | // PbEventType2GeneratePayloadFunc grpc event map wechaty-puppet event payload
100 | func PbEventType2GeneratePayloadFunc() map[pbwechaty.EventType]func() interface{} {
101 | return pbEventType2GeneratePayloadFunc
102 | }
103 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/puppeteventname_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=PuppetEventName"; DO NOT EDIT.
2 |
3 | package schemas
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[PuppetEventNameUnknown-0]
12 | _ = x[PuppetEventNameFriendship-1]
13 | _ = x[PuppetEventNameLogin-2]
14 | _ = x[PuppetEventNameLogout-3]
15 | _ = x[PuppetEventNameMessage-4]
16 | _ = x[PuppetEventNameRoomInvite-5]
17 | _ = x[PuppetEventNameRoomJoin-6]
18 | _ = x[PuppetEventNameRoomLeave-7]
19 | _ = x[PuppetEventNameRoomTopic-8]
20 | _ = x[PuppetEventNameScan-9]
21 | _ = x[PuppetEventNameDong-10]
22 | _ = x[PuppetEventNameError-11]
23 | _ = x[PuppetEventNameHeartbeat-12]
24 | _ = x[PuppetEventNameReady-13]
25 | _ = x[PuppetEventNameReset-14]
26 | _ = x[PuppetEventNameDirty-15]
27 | _ = x[PuppetEventNameStop-16]
28 | _ = x[PuppetEventNameStart-17]
29 | }
30 |
31 | const _PuppetEventName_name = "PuppetEventNameUnknownPuppetEventNameFriendshipPuppetEventNameLoginPuppetEventNameLogoutPuppetEventNameMessagePuppetEventNameRoomInvitePuppetEventNameRoomJoinPuppetEventNameRoomLeavePuppetEventNameRoomTopicPuppetEventNameScanPuppetEventNameDongPuppetEventNameErrorPuppetEventNameHeartbeatPuppetEventNameReadyPuppetEventNameResetPuppetEventNameDirtyPuppetEventNameStopPuppetEventNameStart"
32 |
33 | var _PuppetEventName_index = [...]uint16{0, 22, 47, 67, 88, 110, 135, 158, 182, 206, 225, 244, 264, 288, 308, 328, 348, 367, 387}
34 |
35 | func (i PuppetEventName) String() string {
36 | if i >= PuppetEventName(len(_PuppetEventName_index)-1) {
37 | return "PuppetEventName(" + strconv.FormatInt(int64(i), 10) + ")"
38 | }
39 | return _PuppetEventName_name[_PuppetEventName_index[i]:_PuppetEventName_index[i+1]]
40 | }
41 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/room.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | import "regexp"
4 |
5 | type RoomMemberQueryFilter struct {
6 | Name string
7 | RoomAlias string
8 | ContactAlias string
9 | }
10 |
11 | type RoomQueryFilter struct {
12 | // 使用 room id 过滤
13 | Id string
14 | // 使用群名称过滤
15 | Topic string
16 | // 群名称正则过滤
17 | TopicRegexp *regexp.Regexp
18 | }
19 |
20 | func (r *RoomQueryFilter) Empty() bool {
21 | return r.Id == "" && r.Topic == "" && r.TopicRegexp == nil
22 | }
23 |
24 | func (r *RoomQueryFilter) All() bool {
25 | return r.Id != "" && r.Topic != "" && r.TopicRegexp != nil
26 | }
27 |
28 | type RoomPayload struct {
29 | Id string
30 | Topic string
31 | Avatar string
32 | MemberIdList []string
33 | OwnerId string
34 | AdminIdList []string
35 | }
36 |
37 | type RoomMemberPayload struct {
38 | Id string
39 | RoomAlias string
40 | InviterId string
41 | Avatar string
42 | Name string
43 | }
44 |
45 | type RoomPayloadFilterFunction func(payload *RoomPayload) bool
46 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/room_invitation.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | import "time"
4 |
5 | type RoomInvitationPayload struct {
6 | Id string `json:"id"`
7 | InviterId string `json:"inviterId"`
8 | RoomId string `json:"roomId"`
9 | Topic string `json:"topic"`
10 | Avatar string `json:"avatar"`
11 | Invitation string `json:"invitation"`
12 | MemberCount int `json:"memberCount"`
13 | MemberIdList []string `json:"memberIdList"`
14 | Timestamp time.Time `json:"timestamp"`
15 | ReceiverId string `json:"receiverId"`
16 | }
17 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/scanstatus_string.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=ScanStatus"; DO NOT EDIT.
2 |
3 | package schemas
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[ScanStatusUnknown-0]
12 | _ = x[ScanStatusCancel-1]
13 | _ = x[ScanStatusWaiting-2]
14 | _ = x[ScanStatusScanned-3]
15 | _ = x[ScanStatusConfirmed-4]
16 | _ = x[ScanStatusTimeout-5]
17 | }
18 |
19 | const _ScanStatus_name = "ScanStatusUnknownScanStatusCancelScanStatusWaitingScanStatusScannedScanStatusConfirmedScanStatusTimeout"
20 |
21 | var _ScanStatus_index = [...]uint8{0, 17, 33, 50, 67, 86, 103}
22 |
23 | func (i ScanStatus) String() string {
24 | if i >= ScanStatus(len(_ScanStatus_index)-1) {
25 | return "ScanStatus(" + strconv.FormatInt(int64(i), 10) + ")"
26 | }
27 | return _ScanStatus_name[_ScanStatus_index[i]:_ScanStatus_index[i+1]]
28 | }
29 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/type.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | // EmitStruct receive puppet emit event
4 | type EmitStruct struct {
5 | EventName PuppetEventName
6 | Payload interface{}
7 | }
8 |
9 | // EventParams wechaty emit params
10 | type EventParams struct {
11 | EventName PuppetEventName
12 | Params []interface{}
13 | }
14 |
--------------------------------------------------------------------------------
/wechaty-puppet/schemas/url_link.go:
--------------------------------------------------------------------------------
1 | package schemas
2 |
3 | import "encoding/json"
4 |
5 | type UrlLinkPayload struct {
6 | Description string `json:"description"`
7 | ThumbnailUrl string `json:"thumbnailUrl"`
8 | Title string `json:"title"`
9 | Url string `json:"url"`
10 | }
11 |
12 | func (u *UrlLinkPayload) ToJson() string {
13 | b, _ := json.Marshal(u)
14 | return string(b)
15 | }
16 |
--------------------------------------------------------------------------------
/wechaty/accessory.go:
--------------------------------------------------------------------------------
1 | package wechaty
2 |
3 | import (
4 | wechatypuppet "github.com/wechaty/go-wechaty/wechaty-puppet"
5 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
6 | )
7 |
8 | // Accessory ...
9 | type Accessory struct {
10 | puppet wechatypuppet.IPuppetAbstract
11 | wechaty *Wechaty
12 | }
13 |
14 | // GetPuppet ...
15 | func (a *Accessory) GetPuppet() wechatypuppet.IPuppetAbstract {
16 | return a.puppet
17 | }
18 |
19 | // GetWechaty ...
20 | func (a *Accessory) GetWechaty() _interface.IWechaty {
21 | return a.wechaty
22 | }
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/wechaty/config.go:
--------------------------------------------------------------------------------
1 | package wechaty
2 |
3 | import logger "github.com/wechaty/go-wechaty/wechaty-puppet/log"
4 |
5 | var log = logger.L.WithField("module", "wechaty")
6 |
--------------------------------------------------------------------------------
/wechaty/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
5 | "regexp"
6 | )
7 |
8 | // AtSepratorRegex mobile: \u2005, PC、mac: \u0020
9 | // Deprecated: use AtSeparatorRegexStr
10 | const AtSepratorRegex = "[\u2005\u0020]"
11 |
12 | // AtSeparatorRegexStr mobile: \u2005, PC、mac: \u0020
13 | const AtSeparatorRegexStr = "[\u2005\u0020]"
14 |
15 | const FourPerEmSpace = string(rune(8197))
16 |
17 | // AtSeparatorRegex regular expression split '@'
18 | var AtSeparatorRegex = regexp.MustCompile(AtSeparatorRegexStr)
19 |
20 | func QRCodeForChatie() *filebox.FileBox {
21 | const chatieOfficialAccountQrcode = "http://weixin.qq.com/r/qymXj7DEO_1ErfTs93y5"
22 | return filebox.FromQRCode(chatieOfficialAccountQrcode)
23 | }
24 |
--------------------------------------------------------------------------------
/wechaty/event.go:
--------------------------------------------------------------------------------
1 | package wechaty
2 |
3 | import (
4 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
5 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
6 | "github.com/wechaty/go-wechaty/wechaty/user"
7 | "time"
8 | )
9 |
10 | type (
11 | // EventDong ...
12 | EventDong func(context *Context, data string)
13 | // EventError ...
14 | EventError func(context *Context, err error)
15 | // EventFriendship ...
16 | EventFriendship func(context *Context, friendship *user.Friendship)
17 | // EventHeartbeat ...
18 | EventHeartbeat func(context *Context, data string)
19 | // EventLogin ...
20 | EventLogin func(context *Context, user *user.ContactSelf)
21 | // EventLogout ...
22 | EventLogout func(context *Context, user *user.ContactSelf, reason string)
23 | // EventMessage ...
24 | EventMessage func(context *Context, message *user.Message)
25 | // EventReady ...
26 | EventReady func(context *Context)
27 | // EventRoomInvite ...
28 | EventRoomInvite func(context *Context, roomInvitation *user.RoomInvitation)
29 | // EventRoomJoin ...
30 | EventRoomJoin func(context *Context, room *user.Room, inviteeList []_interface.IContact, inviter _interface.IContact, date time.Time)
31 | // EventRoomLeave ...
32 | EventRoomLeave func(context *Context, room *user.Room, leaverList []_interface.IContact, remover _interface.IContact, date time.Time)
33 | // EventRoomTopic ...
34 | EventRoomTopic func(context *Context, room *user.Room, newTopic string, oldTopic string, changer _interface.IContact, date time.Time)
35 | // EventScan ...
36 | EventScan func(context *Context, qrCode string, status schemas.ScanStatus, data string)
37 | // EventStart ...
38 | EventStart func(context *Context)
39 | // EventStop ...
40 | EventStop func(context *Context)
41 | )
42 |
--------------------------------------------------------------------------------
/wechaty/factory/config.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import logger "github.com/wechaty/go-wechaty/wechaty-puppet/log"
4 |
5 | var log = logger.L.WithField("module", "wechaty/factory")
6 |
--------------------------------------------------------------------------------
/wechaty/factory/contact.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 |
7 | "github.com/wechaty/go-wechaty/wechaty-puppet/helper"
8 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
9 | "github.com/wechaty/go-wechaty/wechaty/user"
10 | )
11 |
12 | type ContactFactory struct {
13 | _interface.IAccessory
14 | pool *sync.Map
15 | }
16 |
17 | // NewContactFactory ...
18 | func NewContactFactory(accessory _interface.IAccessory) *ContactFactory {
19 | return &ContactFactory{
20 | IAccessory: accessory,
21 | pool: &sync.Map{},
22 | }
23 | }
24 |
25 | // Load query param is string
26 | func (c *ContactFactory) Load(id string) _interface.IContact {
27 | load, ok := c.pool.Load(id)
28 | if !ok {
29 | contact := user.NewContact(id, c.IAccessory)
30 | c.pool.Store(id, contact)
31 | return contact
32 | }
33 | switch v := load.(type) {
34 | case *user.ContactSelf:
35 | return v.Contact
36 | case *user.Contact:
37 | return v
38 | default:
39 | panic(fmt.Sprintf("ContactFactory Load unknow type: %#v", v))
40 | }
41 | }
42 |
43 | // LoadSelf query param is string
44 | func (c *ContactFactory) LoadSelf(id string) _interface.IContactSelf {
45 | load, ok := c.pool.Load(id)
46 | if !ok {
47 | contact := user.NewContactSelf(id, c.IAccessory)
48 | c.pool.Store(id, contact)
49 | return contact
50 | }
51 | switch v := load.(type) {
52 | case *user.ContactSelf:
53 | return v
54 | case *user.Contact:
55 | return &user.ContactSelf{Contact: v}
56 | default:
57 | panic(fmt.Sprintf("ContactFactory LoadSelf unknow type: %#v", v))
58 | }
59 | }
60 |
61 | // Find query params is string or *schemas.ContactQueryFilter
62 | func (c *ContactFactory) Find(query interface{}) _interface.IContact {
63 | contacts := c.FindAll(query)
64 | if len(contacts) == 0 {
65 | return nil
66 | }
67 | if len(contacts) > 1 {
68 | log.Warnf("Contact Find() got more than one(%d) result\n", len(contacts))
69 | }
70 | for _, v := range contacts {
71 | if c.GetPuppet().ContactValidate(v.ID()) {
72 | return v
73 | }
74 | }
75 | return nil
76 | }
77 |
78 | // FindAll query params is string or *schemas.ContactQueryFilter
79 | func (c *ContactFactory) FindAll(query interface{}) []_interface.IContact {
80 | contactIds, err := c.GetPuppet().ContactSearch(query, nil)
81 | if err != nil {
82 | log.Errorf("Contact c.GetPuppet().ContactSearch() rejected: %s\n", err)
83 | return nil
84 | }
85 |
86 | if len(contactIds) == 0 {
87 | return nil
88 | }
89 |
90 | async := helper.NewAsync(helper.DefaultWorkerNum)
91 | for _, id := range contactIds {
92 | id := id
93 | async.AddTask(func() (interface{}, error) {
94 | contact := c.Load(id)
95 | return contact, contact.Ready(false)
96 | })
97 | }
98 |
99 | var contacts []_interface.IContact
100 | for _, v := range async.Result() {
101 | if v.Err != nil {
102 | continue
103 | }
104 | contacts = append(contacts, v.Value.(_interface.IContact))
105 | }
106 | return contacts
107 | }
108 |
109 | // Tags get tags for all contact
110 | func (c *ContactFactory) Tags() []_interface.ITag {
111 | tagIDList, err := c.GetPuppet().TagContactList("")
112 | if err != nil {
113 | log.Errorf("ContactFactory Tags() exception: %s\n", err)
114 | return nil
115 | }
116 | tagList := make([]_interface.ITag, 0, len(tagIDList))
117 | for _, id := range tagIDList {
118 | tagList = append(tagList, c.GetWechaty().Tag().Load(id))
119 | }
120 | return tagList
121 | }
122 |
--------------------------------------------------------------------------------
/wechaty/factory/friendship.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
7 | "github.com/wechaty/go-wechaty/wechaty/user"
8 | )
9 |
10 | type FriendshipFactory struct {
11 | _interface.IAccessory
12 | }
13 |
14 | func (m *FriendshipFactory) Load(id string) _interface.IFriendship {
15 | return user.NewFriendship(id, m.IAccessory)
16 | }
17 |
18 | // Search search a Friend by phone or weixin.
19 | func (m *FriendshipFactory) Search(query *schemas.FriendshipSearchCondition) (_interface.IContact, error) {
20 | contactID, err := m.GetPuppet().FriendshipSearch(query)
21 | if err != nil {
22 | return nil, err
23 | }
24 | if contactID == "" {
25 | return nil, nil
26 | }
27 | contact := m.GetWechaty().Contact().Load(contactID)
28 | err = contact.Ready(false)
29 | if err != nil {
30 | return nil, err
31 | }
32 | return contact, nil
33 | }
34 |
35 | // Add send a Friend Request to a `contact` with message `hello`.
36 | // The best practice is to send friend request once per minute.
37 | // Remember not to do this too frequently, or your account may be blocked.
38 | func (m *FriendshipFactory) Add(contact _interface.IContact, hello string) error {
39 | return m.GetPuppet().FriendshipAdd(contact.ID(), hello)
40 | }
41 |
42 | // FromJSON create friendShip by friendshipJson
43 | func (m *FriendshipFactory) FromJSON(payload string) (_interface.IFriendship, error) {
44 | f := new(schemas.FriendshipPayload)
45 | err := json.Unmarshal([]byte(payload), f)
46 | if err != nil {
47 | return nil, err
48 | }
49 | return m.FromPayload(f)
50 | }
51 |
52 | // FromPayload create friendShip by friendshipPayload
53 | func (m *FriendshipFactory) FromPayload(payload *schemas.FriendshipPayload) (_interface.IFriendship, error) {
54 | m.GetPuppet().SetFriendshipPayload(payload.Id, payload)
55 | friendship := m.Load(payload.Id)
56 | err := friendship.Ready()
57 | if err != nil {
58 | return nil, err
59 | }
60 | return friendship, nil
61 | }
62 |
--------------------------------------------------------------------------------
/wechaty/factory/image.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
5 | "github.com/wechaty/go-wechaty/wechaty/user"
6 | )
7 |
8 | type ImageFactory struct {
9 | _interface.IAccessory
10 | }
11 |
12 | func (i *ImageFactory) Create(id string) _interface.IImage {
13 | return user.NewImages(id, i.IAccessory)
14 | }
15 |
--------------------------------------------------------------------------------
/wechaty/factory/message.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "github.com/wechaty/go-wechaty/wechaty-puppet/helper"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
7 | "github.com/wechaty/go-wechaty/wechaty/user"
8 | )
9 |
10 | type MessageFactory struct {
11 | _interface.IAccessory
12 | }
13 |
14 | func (m *MessageFactory) Load(id string) _interface.IMessage {
15 | return user.NewMessage(id, m.IAccessory)
16 | }
17 |
18 | // Find find message in cache
19 | func (m *MessageFactory) Find(query interface{}) _interface.IMessage {
20 | var q *schemas.MessageQueryFilter
21 | switch v := query.(type) {
22 | case string:
23 | q = &schemas.MessageQueryFilter{Text: v}
24 | case *schemas.MessageQueryFilter:
25 | q = v
26 | default:
27 | log.Error("not support query type")
28 | // TODO 返回 err 更好
29 | return nil
30 | }
31 | messages := m.FindAll(q)
32 | if len(messages) < 1 {
33 | // TODO 返回 err 更好
34 | return nil
35 | }
36 | if len(messages) > 1 {
37 | log.Errorf("Message FindAll() got more than one(%d) result\n", len(messages))
38 | }
39 | return messages[0]
40 | }
41 |
42 | // FindAll Find message in cache
43 | func (m *MessageFactory) FindAll(query *schemas.MessageQueryFilter) []_interface.IMessage {
44 | var err error
45 | defer func() {
46 | if err != nil {
47 | log.Errorf("MessageFactory FindAll rejected: %s\n", err)
48 | }
49 | }()
50 | messageIDs, err := m.GetPuppet().MessageSearch(query)
51 | if err != nil {
52 | return nil
53 | }
54 |
55 | async := helper.NewAsync(helper.DefaultWorkerNum)
56 | for _, id := range messageIDs {
57 | id := id
58 | async.AddTask(func() (interface{}, error) {
59 | message := m.Load(id)
60 | return message, message.Ready()
61 | })
62 | }
63 |
64 | var messages []_interface.IMessage
65 | for _, v := range async.Result() {
66 | if v.Err != nil {
67 | continue
68 | }
69 | messages = append(messages, v.Value.(_interface.IMessage))
70 | }
71 | return messages
72 | }
73 |
--------------------------------------------------------------------------------
/wechaty/factory/room.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "errors"
5 | "sync"
6 |
7 | "github.com/wechaty/go-wechaty/wechaty-puppet/helper"
8 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
9 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
10 | "github.com/wechaty/go-wechaty/wechaty/user"
11 | )
12 |
13 | type RoomFactory struct {
14 | _interface.IAccessory
15 | pool *sync.Map
16 | }
17 |
18 | // NewRoomFactory ...
19 | func NewRoomFactory(accessory _interface.IAccessory) *RoomFactory {
20 | return &RoomFactory{
21 | IAccessory: accessory,
22 | pool: &sync.Map{},
23 | }
24 | }
25 |
26 | // Create a new room.
27 | func (r *RoomFactory) Create(contactList []_interface.IContact, topic string) (_interface.IRoom, error) {
28 | if len(contactList) < 2 {
29 | return nil, errors.New("contactList need at least 2 contact to create a new room")
30 | }
31 | contactIDList := make([]string, len(contactList))
32 | for index, contact := range contactList {
33 | contactIDList[index] = contact.ID()
34 | }
35 | roomID, err := r.GetPuppet().RoomCreate(contactIDList, topic)
36 | if err != nil {
37 | return nil, err
38 | }
39 | return r.Load(roomID), nil
40 | }
41 |
42 | // FindAll query param is string or *schemas.RoomQueryFilter
43 | func (r *RoomFactory) FindAll(query *schemas.RoomQueryFilter) []_interface.IRoom {
44 | roomIDList, err := r.GetPuppet().RoomSearch(query)
45 | if err != nil {
46 | log.Error("RoomFactory err: ", err)
47 | return nil
48 | }
49 | if len(roomIDList) == 0 {
50 | return nil
51 | }
52 | async := helper.NewAsync(helper.DefaultWorkerNum)
53 | for _, id := range roomIDList {
54 | id := id
55 | async.AddTask(func() (interface{}, error) {
56 | room := r.Load(id)
57 | return room, room.Ready(false)
58 | })
59 | }
60 | var roomList []_interface.IRoom
61 | for _, v := range async.Result() {
62 | if v.Err != nil {
63 | continue
64 | }
65 | roomList = append(roomList, v.Value.(_interface.IRoom))
66 | }
67 | return roomList
68 | }
69 |
70 | // Find query params is string or *schemas.RoomQueryFilter
71 | func (r *RoomFactory) Find(query interface{}) _interface.IRoom {
72 | var q *schemas.RoomQueryFilter
73 | switch v := query.(type) {
74 | case string:
75 | q = &schemas.RoomQueryFilter{Topic: v}
76 | case *schemas.RoomQueryFilter:
77 | q = v
78 | default:
79 | log.Errorf("not support query type %T\n", query)
80 | // TODO 应该返回 err
81 | return nil
82 | }
83 | roomList := r.FindAll(q)
84 | if len(roomList) == 0 {
85 | return nil
86 | }
87 | for _, room := range roomList {
88 | if r.GetPuppet().RoomValidate(room.ID()) {
89 | return room
90 | }
91 | }
92 | // TODO 应该返回 err
93 | return nil
94 | }
95 |
96 | // Load query param is string
97 | func (r *RoomFactory) Load(id string) _interface.IRoom {
98 | load, ok := r.pool.Load(id)
99 | if ok {
100 | return load.(*user.Room)
101 | }
102 | room := user.NewRoom(id, r.IAccessory)
103 | r.pool.Store(id, room)
104 | return room
105 | }
106 |
--------------------------------------------------------------------------------
/wechaty/factory/room_invitation.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
7 | "github.com/wechaty/go-wechaty/wechaty/user"
8 | )
9 |
10 | type RoomInvitationFactory struct {
11 | _interface.IAccessory
12 | }
13 |
14 | func (r *RoomInvitationFactory) Load(id string) _interface.IRoomInvitation {
15 | return user.NewRoomInvitation(id, r.IAccessory)
16 | }
17 |
18 | func (r *RoomInvitationFactory) FromJSON(s string) (_interface.IRoomInvitation, error) {
19 | payload := new(schemas.RoomInvitationPayload)
20 | err := json.Unmarshal([]byte(s), payload)
21 | if err != nil {
22 | return nil, err
23 | }
24 | return r.FromPayload(payload), nil
25 | }
26 |
27 | func (r *RoomInvitationFactory) FromPayload(payload *schemas.RoomInvitationPayload) _interface.IRoomInvitation {
28 | r.GetPuppet().SetRoomInvitationPayload(payload)
29 | return r.Load(payload.Id)
30 | }
31 |
--------------------------------------------------------------------------------
/wechaty/factory/tag.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
5 | "github.com/wechaty/go-wechaty/wechaty/user"
6 | "sync"
7 | )
8 |
9 | type TagFactory struct {
10 | _interface.IAccessory
11 | pool *sync.Map
12 | }
13 |
14 | // NewTagFactory ...
15 | func NewTagFactory(accessory _interface.IAccessory) *TagFactory {
16 | return &TagFactory{
17 | IAccessory: accessory,
18 | pool: &sync.Map{},
19 | }
20 | }
21 |
22 | func (r *TagFactory) Load(id string) _interface.ITag {
23 | load, ok := r.pool.Load(id)
24 | if ok {
25 | return load.(*user.Tag)
26 | }
27 | tag := user.NewTag(id, r.IAccessory)
28 | r.pool.Store(id, tag)
29 | return tag
30 | }
31 |
32 | func (r *TagFactory) Get(tag string) _interface.ITag {
33 | return r.Load(tag)
34 | }
35 |
36 | func (r *TagFactory) Delete(tag _interface.ITag) error {
37 | return r.GetPuppet().TagContactDelete(tag.ID())
38 | }
39 |
--------------------------------------------------------------------------------
/wechaty/factory/url_link.go:
--------------------------------------------------------------------------------
1 | package factory
2 |
3 | import (
4 | "errors"
5 | "github.com/otiai10/opengraph"
6 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
7 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
8 | "github.com/wechaty/go-wechaty/wechaty/user"
9 | )
10 |
11 | var (
12 | ErrImageUrlOrDescNotFound = errors.New("imgUrl.or.desc.not.found")
13 | )
14 |
15 | type UrlLinkFactory struct{}
16 |
17 | func (u *UrlLinkFactory) Create(url string) (_interface.IUrlLink, error) {
18 | var og, err = opengraph.Fetch(url)
19 | if err != nil {
20 | return nil, err
21 | }
22 | var payload = &schemas.UrlLinkPayload{
23 | Url: url,
24 | Title: og.Title,
25 | Description: og.Description,
26 | }
27 |
28 | if len(og.Image) != 0 {
29 | payload.ThumbnailUrl = og.Image[0].URL
30 | }
31 |
32 | if payload.ThumbnailUrl == "" || payload.Description == "" {
33 | return nil, ErrImageUrlOrDescNotFound
34 | }
35 |
36 | return user.NewUrlLink(payload), nil
37 | }
38 |
--------------------------------------------------------------------------------
/wechaty/interface/accessory.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | import (
4 | wechatyPuppet "github.com/wechaty/go-wechaty/wechaty-puppet"
5 | )
6 |
7 | // IAccessory accessory interface
8 | type IAccessory interface {
9 | GetPuppet() wechatyPuppet.IPuppetAbstract
10 |
11 | GetWechaty() IWechaty
12 | }
13 |
--------------------------------------------------------------------------------
/wechaty/interface/contact.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | import (
4 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | )
7 |
8 | type IContactFactory interface {
9 | Load(id string) IContact
10 | LoadSelf(id string) IContactSelf
11 | // Find query params is string or *schemas.ContactQueryFilter
12 | // when the parameter is a string type, the name search is used by default
13 | Find(query interface{}) IContact
14 | // FindAll query params is string or *schemas.ContactQueryFilter
15 | // when the parameter is a string type, the name search is used by default
16 | FindAll(query interface{}) []IContact
17 | // Tags get tags for all contact
18 | Tags() []ITag
19 | }
20 |
21 | type IContact interface {
22 | // Ready is For FrameWork ONLY!
23 | Ready(forceSync bool) (err error)
24 | IsReady() bool
25 | // Sync force reload data for Contact, sync data from lowlevel API again.
26 | Sync() error
27 | String() string
28 | ID() string
29 | Name() string
30 | // Say something params {(string | Contact | FileBox | UrlLink | MiniProgram)}
31 | Say(something interface{}) (msg IMessage, err error)
32 | // Friend true for friend of the bot, false for not friend of the bot
33 | Friend() bool
34 | Type() schemas.ContactType
35 | // Star check if the contact is star contact
36 | Star() bool
37 | Gender() schemas.ContactGender
38 | Province() string
39 | City() string
40 | // Avatar get avatar picture file stream
41 | Avatar() *filebox.FileBox
42 | // Self Check if contact is self
43 | Self() bool
44 | // Weixin get the weixin number from a contact
45 | // Sometimes cannot get weixin number due to weixin security mechanism, not recommend.
46 | Weixin() string
47 | // Alias get alias
48 | Alias() string
49 | // SetAlias set alias
50 | SetAlias(newAlias string)
51 | }
52 |
--------------------------------------------------------------------------------
/wechaty/interface/contact_self.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | import "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
4 |
5 | type IContactSelfFactory interface {
6 | IContactFactory
7 | }
8 |
9 | type IContactSelf interface {
10 | IContact
11 | SetAvatar(box *filebox.FileBox) error
12 | // QRCode get bot qrcode
13 | QRCode() (string, error)
14 | SetName(name string) error
15 | // Signature change bot signature
16 | Signature(signature string) error
17 | }
18 |
--------------------------------------------------------------------------------
/wechaty/interface/friendship.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | import "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
4 |
5 | type IFriendshipFactory interface {
6 | Load(id string) IFriendship
7 | // Search search a Friend by phone or weixin.
8 | Search(query *schemas.FriendshipSearchCondition) (IContact, error)
9 | // Add send a Friend Request to a `contact` with message `hello`.
10 | // The best practice is to send friend request once per minute.
11 | // Remember not to do this too frequently, or your account may be blocked.
12 | Add(contact IContact, hello string) error
13 | // FromJSON create friendShip by friendshipJson
14 | FromJSON(payload string) (IFriendship, error)
15 | // FromPayload create friendShip by friendshipPayload
16 | FromPayload(payload *schemas.FriendshipPayload) (IFriendship, error)
17 | }
18 |
19 | type IFriendship interface {
20 | Ready() (err error)
21 | IsReady() bool
22 | Contact() IContact
23 | String() string
24 | // Accept accept friend request
25 | Accept() error
26 | Type() schemas.FriendshipType
27 | // Hello get verify message from
28 | Hello() string
29 | // ToJSON get friendShipPayload Json
30 | ToJSON() (string, error)
31 | }
32 |
--------------------------------------------------------------------------------
/wechaty/interface/image.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | import "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
4 |
5 | type IImageFactory interface {
6 | Create(id string) IImage
7 | }
8 |
9 | type IImage interface {
10 | Thumbnail() (*filebox.FileBox, error)
11 | HD() (*filebox.FileBox, error)
12 | Artwork() (*filebox.FileBox, error)
13 | }
14 |
--------------------------------------------------------------------------------
/wechaty/interface/message.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | import (
4 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | "time"
7 | )
8 |
9 | type IMessageFactory interface {
10 | Load(id string) IMessage
11 | // Find find message in cache
12 | Find(query interface{}) IMessage
13 | // FindAll Find message in cache
14 | FindAll(query *schemas.MessageQueryFilter) []IMessage
15 | }
16 |
17 | type IMessage interface {
18 | Ready() (err error)
19 | IsReady() bool
20 | String() string
21 | Room() IRoom
22 | // Type get the type from the message.
23 | Type() schemas.MessageType
24 | // Deprecated: please use Talker()
25 | From() IContact
26 | // Talker Get the talker of a message.
27 | Talker() IContact
28 | Text() string
29 | // Self check if a message is sent by self
30 | Self() bool
31 | Age() time.Duration
32 | // Date sent date
33 | Date() time.Time
34 | // Deprecated: please use Listener()
35 | To() IContact
36 | // Listener Get the destination of the message
37 | // listener() will return nil if a message is in a room
38 | // use Room() to get the room.
39 | Listener() IContact
40 | // ToRecalled Get the recalled message
41 | ToRecalled() (IMessage, error)
42 | // Say reply a Text or Media File message to the sender.
43 | Say(textOrContactOrFileOrUrlOrMini interface{}) (IMessage, error)
44 | // Recall recall a message
45 | Recall() (bool, error)
46 | // MentionList get message mentioned contactList.
47 | MentionList() []IContact
48 | MentionText() string
49 | MentionSelf() bool
50 | Forward(contactOrRoomId string) error
51 | // ToFileBox extract the Media File from the Message, and put it into the FileBox.
52 | ToFileBox() (*filebox.FileBox, error)
53 | // ToImage extract the Image File from the Message, so that we can use different image sizes.
54 | ToImage() (IImage, error)
55 | // ToContact Get Share Card of the Message
56 | // Extract the Contact Card from the Message, and encapsulate it into Contact class
57 | ToContact() (IContact, error)
58 | }
59 |
--------------------------------------------------------------------------------
/wechaty/interface/mini_program.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | type IMiniProgram interface {
4 | AppID() string
5 | Description() string
6 | PagePath() string
7 | ThumbUrl() string
8 | Title() string
9 | Username() string
10 | ThumbKey() string
11 | }
12 |
--------------------------------------------------------------------------------
/wechaty/interface/room.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | import (
4 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | )
7 |
8 | type IRoomFactory interface {
9 | // Create a new room.
10 | Create(contactList []IContact, topic string) (IRoom, error)
11 | FindAll(query *schemas.RoomQueryFilter) []IRoom
12 | // Find query params is string or *schemas.RoomQueryFilter
13 | // when the parameter is a string type, the room name search is used by default
14 | Find(query interface{}) IRoom
15 | Load(id string) IRoom
16 | }
17 |
18 | type IRoom interface {
19 | // Ready is For FrameWork ONLY!
20 | Ready(forceSync bool) (err error)
21 | IsReady() bool
22 | String() string
23 | ID() string
24 | // MemberAll all contacts in a room
25 | // params nil or string or *schemas.RoomMemberQueryFilter
26 | MemberAll(query interface{}) ([]IContact, error)
27 | // Member Find all contacts in a room, if get many, return the first one.
28 | // query params string or *schemas.RoomMemberQueryFilter
29 | Member(query interface{}) (IContact, error)
30 | // Alias return contact's roomAlias in the room
31 | Alias(contact IContact) (string, error)
32 | // Sync Force reload data for Room, Sync data from puppet API again.
33 | Sync() error
34 | // Say something params {(string | Contact | FileBox | UrlLink | MiniProgram )}
35 | // mentionList @ contact list
36 | Say(something interface{}, mentionList ...IContact) (msg IMessage, err error)
37 | // Add contact in a room
38 | Add(contact IContact) error
39 | // Del delete a contact from the room
40 | // it works only when the bot is the owner of the room
41 | Del(contact IContact) error
42 | // Quit the room itself
43 | Quit() error
44 | // Topic get topic from the room
45 | Topic() string
46 | // Topic set topic from the room
47 | SetTopic(topic string) error
48 | // Announce get announce from the room
49 | Announce() (string, error)
50 | // Announce set announce from the room
51 | // It only works when bot is the owner of the room.
52 | SetAnnounce(text string) error
53 | // QrCode Get QR Code Value of the Room from the room, which can be used as scan and join the room.
54 | QrCode() (string, error)
55 | // Has check if the room has member `contact`
56 | Has(contact IContact) (bool, error)
57 | // Owner get room's owner from the room.
58 | Owner() IContact
59 | // Avatar get avatar from the room.
60 | Avatar() (*filebox.FileBox, error)
61 | }
62 |
--------------------------------------------------------------------------------
/wechaty/interface/room_invitation.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
7 | )
8 |
9 | type IRoomInvitationFactory interface {
10 | Load(id string) IRoomInvitation
11 | FromJSON(s string) (IRoomInvitation, error)
12 | FromPayload(payload *schemas.RoomInvitationPayload) IRoomInvitation
13 | }
14 |
15 | type IRoomInvitation interface {
16 | String() string
17 | ToStringAsync() (string, error)
18 | Accept() error
19 | Inviter() (IContact, error)
20 | Room() (IRoom, error)
21 | Topic() (string, error)
22 | MemberCount() (int, error)
23 | MemberList() ([]IContact, error)
24 | Date() (time.Time, error)
25 | Age() (time.Duration, error)
26 | ToJson() (string, error)
27 | RawPayload() (schemas.RoomInvitationPayload, error)
28 | }
29 |
--------------------------------------------------------------------------------
/wechaty/interface/tag.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | type ITagFactory interface {
4 | Load(id string) ITag
5 | Get(tag string) ITag
6 | Delete(tag ITag) error
7 | }
8 |
9 | type ITag interface {
10 | ID() string
11 | Add(to IContact) error
12 | Remove(from IContact) error
13 | }
14 |
--------------------------------------------------------------------------------
/wechaty/interface/url_link.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | type IUrlLinkFactory interface {
4 | Create(url string) (IUrlLink, error)
5 | }
6 |
7 | type IUrlLink interface {
8 | String() string
9 | Url() string
10 | Title() string
11 | ThumbnailUrl() string
12 | Description() string
13 | }
14 |
--------------------------------------------------------------------------------
/wechaty/interface/wechaty.go:
--------------------------------------------------------------------------------
1 | package _interface
2 |
3 | // IWechaty interface
4 | type IWechaty interface {
5 | Room() IRoomFactory
6 | Contact() IContactFactory
7 | Message() IMessageFactory
8 | Tag() ITagFactory
9 | Friendship() IFriendshipFactory
10 | Image() IImageFactory
11 | UserSelf() IContactSelf
12 | }
13 |
--------------------------------------------------------------------------------
/wechaty/option.go:
--------------------------------------------------------------------------------
1 | package wechaty
2 |
3 | import (
4 | wp "github.com/wechaty/go-wechaty/wechaty-puppet"
5 | puppetservice "github.com/wechaty/go-wechaty/wechaty-puppet-service"
6 | mc "github.com/wechaty/go-wechaty/wechaty-puppet/memory-card"
7 | )
8 |
9 | // Option wechaty option
10 | type Option struct {
11 | // wechaty name
12 | name string
13 | // puppet instance
14 | puppet wp.IPuppetAbstract
15 | // puppet option
16 | puppetOption wp.Option
17 | // io token
18 | ioToken string
19 | // memory card
20 | memoryCard mc.IMemoryCard
21 |
22 | // puppet-serviceOptions
23 | puppetServiceOptions puppetservice.Options
24 | }
25 |
26 | // OptionFn func
27 | type OptionFn func(opts *Option)
28 |
29 | // WithName with name
30 | func WithName(name string) OptionFn {
31 | return func(opt *Option) {
32 | opt.name = name
33 | }
34 | }
35 |
36 | // WithPuppet with puppet impl
37 | func WithPuppet(puppet wp.IPuppetAbstract) OptionFn {
38 | return func(opt *Option) {
39 | opt.puppet = puppet
40 | }
41 | }
42 |
43 | // WithPuppetOption with puppet option
44 | func WithPuppetOption(puppetOption wp.Option) OptionFn {
45 | return func(opt *Option) {
46 | opt.puppetOption = puppetOption
47 | }
48 | }
49 |
50 | // WithIOToken with io token
51 | func WithIOToken(ioToken string) OptionFn {
52 | return func(opt *Option) {
53 | opt.ioToken = ioToken
54 | }
55 | }
56 |
57 | // WithMemoryCard with memory card
58 | func WithMemoryCard(memoryCard mc.IMemoryCard) OptionFn {
59 | return func(opt *Option) {
60 | opt.memoryCard = memoryCard
61 | }
62 | }
63 |
64 | // WithPuppetServiceOptions puppet service options
65 | func WithPuppetServiceOptions(puppetServiceOptions puppetservice.Options) OptionFn {
66 | return func(opts *Option) {
67 | opts.puppetServiceOptions = puppetServiceOptions
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/wechaty/plugin.go:
--------------------------------------------------------------------------------
1 | package wechaty
2 |
3 | import (
4 | "fmt"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | "reflect"
7 | "runtime/debug"
8 | "sync"
9 | )
10 |
11 | // PluginEvent stores the event name and the callback function.
12 | type PluginEvent struct {
13 | name schemas.PuppetEventName
14 | f interface{} // callback function
15 | }
16 |
17 | // Plugin ...
18 | type Plugin struct {
19 | Wechaty *Wechaty
20 | mu sync.RWMutex
21 | enable bool
22 | events []PluginEvent
23 | }
24 |
25 | // NewPlugin ...
26 | func NewPlugin() *Plugin {
27 | p := &Plugin{
28 | enable: true,
29 | }
30 | return p
31 | }
32 |
33 | // SetEnable enable or disable a plugin.
34 | func (p *Plugin) SetEnable(value bool) {
35 | p.mu.Lock()
36 | p.enable = value
37 | p.mu.Unlock()
38 | }
39 |
40 | // IsEnable returns whether the plugin is activated.
41 | func (p *Plugin) IsEnable() bool {
42 | p.mu.RLock()
43 | defer p.mu.RUnlock()
44 | return p.enable
45 | }
46 |
47 | func (p *Plugin) registerPluginEvent(wechaty *Wechaty) {
48 | for _, pluginEvent := range p.events {
49 | pluginEvent := pluginEvent
50 | f := func(data ...interface{}) {
51 | defer func() {
52 | if err := recover(); err != nil {
53 | log.Error("panic: ", err)
54 | log.Error(string(debug.Stack()))
55 | wechaty.events.Emit(schemas.PuppetEventNameError, NewContext(), fmt.Errorf("panic: event %s %v", pluginEvent.name, err))
56 | }
57 | }()
58 | values := make([]reflect.Value, 0, len(data))
59 | for _, v := range data {
60 | values = append(values, reflect.ValueOf(v))
61 | }
62 | // check whether the plugin can be used.
63 | if values[0].Interface().(*Context).IsActive(p) &&
64 | !values[0].Interface().(*Context).abort {
65 | _ = reflect.ValueOf(pluginEvent.f).Call(values)
66 | }
67 | }
68 | wechaty.registerEvent(pluginEvent.name, f)
69 | }
70 | }
71 |
72 | // OnScan ...
73 | func (p *Plugin) OnScan(f EventScan) *Plugin {
74 | p.events = append(p.events, PluginEvent{
75 | name: schemas.PuppetEventNameScan,
76 | f: f,
77 | })
78 | return p
79 | }
80 |
81 | // OnLogin ...
82 | func (p *Plugin) OnLogin(f EventLogin) *Plugin {
83 | p.events = append(p.events, PluginEvent{
84 | name: schemas.PuppetEventNameLogin,
85 | f: f,
86 | })
87 | return p
88 | }
89 |
90 | // OnMessage ...
91 | func (p *Plugin) OnMessage(f EventMessage) *Plugin {
92 | p.events = append(p.events, PluginEvent{
93 | name: schemas.PuppetEventNameMessage,
94 | f: f,
95 | })
96 | return p
97 | }
98 |
99 | // OnDong ...
100 | func (p *Plugin) OnDong(f EventDong) *Plugin {
101 | p.events = append(p.events, PluginEvent{
102 | name: schemas.PuppetEventNameDong,
103 | f: f,
104 | })
105 | return p
106 | }
107 |
108 | // OnError ...
109 | func (p *Plugin) OnError(f EventError) *Plugin {
110 | p.events = append(p.events, PluginEvent{
111 | name: schemas.PuppetEventNameError,
112 | f: f,
113 | })
114 | return p
115 | }
116 |
117 | // OnFriendship ...
118 | func (p *Plugin) OnFriendship(f EventFriendship) *Plugin {
119 | p.events = append(p.events, PluginEvent{
120 | name: schemas.PuppetEventNameFriendship,
121 | f: f,
122 | })
123 | return p
124 | }
125 |
126 | // OnHeartbeat ...
127 | func (p *Plugin) OnHeartbeat(f EventHeartbeat) *Plugin {
128 | p.events = append(p.events, PluginEvent{
129 | name: schemas.PuppetEventNameHeartbeat,
130 | f: f,
131 | })
132 | return p
133 | }
134 |
135 | // OnLogout ...
136 | func (p *Plugin) OnLogout(f EventLogout) *Plugin {
137 | p.events = append(p.events, PluginEvent{
138 | name: schemas.PuppetEventNameLogout,
139 | f: f,
140 | })
141 | return p
142 | }
143 |
144 | // OnReady ...
145 | func (p *Plugin) OnReady(f EventReady) *Plugin {
146 | p.events = append(p.events, PluginEvent{
147 | name: schemas.PuppetEventNameReady,
148 | f: f,
149 | })
150 | return p
151 | }
152 |
153 | // OnRoomInvite ...
154 | func (p *Plugin) OnRoomInvite(f EventRoomInvite) *Plugin {
155 | p.events = append(p.events, PluginEvent{
156 | name: schemas.PuppetEventNameRoomInvite,
157 | f: f,
158 | })
159 | return p
160 | }
161 |
162 | // OnRoomJoin ...
163 | func (p *Plugin) OnRoomJoin(f EventRoomJoin) *Plugin {
164 | p.events = append(p.events, PluginEvent{
165 | name: schemas.PuppetEventNameRoomJoin,
166 | f: f,
167 | })
168 | return p
169 | }
170 |
171 | // OnRoomLeave ...
172 | func (p *Plugin) OnRoomLeave(f EventRoomLeave) *Plugin {
173 | p.events = append(p.events, PluginEvent{
174 | name: schemas.PuppetEventNameRoomLeave,
175 | f: f,
176 | })
177 | return p
178 | }
179 |
180 | // OnRoomTopic ...
181 | func (p *Plugin) OnRoomTopic(f EventRoomTopic) *Plugin {
182 | p.events = append(p.events, PluginEvent{
183 | name: schemas.PuppetEventNameRoomTopic,
184 | f: f,
185 | })
186 | return p
187 | }
188 |
189 | // OnStart ...
190 | func (p *Plugin) OnStart(f EventStart) *Plugin {
191 | p.events = append(p.events, PluginEvent{
192 | name: schemas.PuppetEventNameStart,
193 | f: f,
194 | })
195 | return p
196 | }
197 |
198 | // OnStop ...
199 | func (p *Plugin) OnStop(f EventStop) *Plugin {
200 | p.events = append(p.events, PluginEvent{
201 | name: schemas.PuppetEventNameStop,
202 | f: f,
203 | })
204 | return p
205 | }
206 |
--------------------------------------------------------------------------------
/wechaty/user/config.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import logger "github.com/wechaty/go-wechaty/wechaty-puppet/log"
4 |
5 | var log = logger.L.WithField("module", "wechaty/user")
6 |
--------------------------------------------------------------------------------
/wechaty/user/contact.go:
--------------------------------------------------------------------------------
1 | /**
2 | * Go Wechaty - https://github.com/wechaty/go-wechaty
3 | *
4 | * Authors: Huan LI (李卓桓)
5 | * Bojie LI (李博杰)
6 | *
7 | * 2020-now @ Copyright Wechaty
8 | *
9 | * Licensed under the Apache License, Version 2.0 (the 'License');
10 | * you may not use this file except in compliance with the License.
11 | * You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an 'AS IS' BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | */
21 |
22 | package user
23 |
24 | import (
25 | "fmt"
26 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
27 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
28 | "github.com/wechaty/go-wechaty/wechaty/config"
29 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
30 | )
31 |
32 | type Contact struct {
33 | _interface.IAccessory
34 |
35 | Id string
36 | payload *schemas.ContactPayload
37 | }
38 |
39 | // NewContact ...
40 | func NewContact(id string, accessory _interface.IAccessory) *Contact {
41 | return &Contact{
42 | IAccessory: accessory,
43 | Id: id,
44 | }
45 | }
46 |
47 | // Ready is For FrameWork ONLY!
48 | func (c *Contact) Ready(forceSync bool) (err error) {
49 | if !forceSync && c.IsReady() {
50 | return nil
51 | }
52 |
53 | c.payload, err = c.GetPuppet().ContactPayload(c.Id)
54 | if err != nil {
55 | return err
56 | }
57 | return nil
58 | }
59 |
60 | func (c *Contact) IsReady() bool {
61 | return c.payload != nil
62 | }
63 |
64 | // Sync force reload data for Contact, sync data from lowlevel API again.
65 | func (c *Contact) Sync() error {
66 | err := c.GetPuppet().DirtyPayload(schemas.PayloadTypeContact, c.Id)
67 | if err != nil {
68 | return err
69 | }
70 | return c.Ready(true)
71 | }
72 |
73 | func (c *Contact) String() string {
74 | return fmt.Sprintf("Contact<%s>", c.identity())
75 | }
76 |
77 | func (c *Contact) identity() string {
78 | identity := "loading..."
79 | if c.payload.Alias != "" {
80 | identity = c.payload.Alias
81 | } else if c.payload.Name != "" {
82 | identity = c.payload.Name
83 | } else if c.Id != "" {
84 | identity = c.Id
85 | }
86 | return identity
87 | }
88 |
89 | func (c *Contact) ID() string {
90 | return c.Id
91 | }
92 |
93 | func (c *Contact) Name() string {
94 | if c.payload == nil {
95 | return ""
96 | }
97 | return c.payload.Name
98 | }
99 |
100 | // Say something params {(string | Contact | FileBox | UrlLink | MiniProgram)}
101 | func (c *Contact) Say(something interface{}) (msg _interface.IMessage, err error) {
102 | var msgID string
103 | switch v := something.(type) {
104 | case string:
105 | msgID, err = c.GetPuppet().MessageSendText(c.Id, v)
106 | case *Contact:
107 | msgID, err = c.GetPuppet().MessageSendContact(c.Id, v.Id)
108 | case *filebox.FileBox:
109 | msgID, err = c.GetPuppet().MessageSendFile(c.Id, v)
110 | case *UrlLink:
111 | msgID, err = c.GetPuppet().MessageSendURL(c.Id, v.payload)
112 | case *MiniProgram:
113 | msgID, err = c.GetPuppet().MessageSendMiniProgram(c.Id, v.payload)
114 | default:
115 | return nil, fmt.Errorf("unsupported arg: %v", something)
116 | }
117 | if err != nil {
118 | return nil, err
119 | }
120 | if msgID == "" {
121 | return nil, nil
122 | }
123 | msg = c.GetWechaty().Message().Load(msgID)
124 | return msg, msg.Ready()
125 | }
126 |
127 | // Friend true for friend of the bot, false for not friend of the bot
128 | func (c *Contact) Friend() bool {
129 | return c.payload.Friend
130 | }
131 |
132 | // Type contact type
133 | func (c *Contact) Type() schemas.ContactType {
134 | return c.payload.Type
135 | }
136 |
137 | // Star check if the contact is star contact
138 | func (c *Contact) Star() bool {
139 | return c.payload.Star
140 | }
141 |
142 | // Gender gender
143 | func (c *Contact) Gender() schemas.ContactGender {
144 | return c.payload.Gender
145 | }
146 |
147 | // Province Get the region 'province' from a contact
148 | func (c *Contact) Province() string {
149 | return c.payload.Province
150 | }
151 |
152 | // City Get the region 'city' from a contact
153 | func (c *Contact) City() string {
154 | return c.payload.City
155 | }
156 |
157 | // Avatar get avatar picture file stream
158 | func (c *Contact) Avatar() *filebox.FileBox {
159 | avatar, err := c.GetPuppet().ContactAvatar(c.Id)
160 | if err != nil {
161 | log.Errorf("Contact Avatar() exception: %s\n", err)
162 | return config.QRCodeForChatie()
163 | }
164 | return avatar
165 | }
166 |
167 | // Self Check if contact is self
168 | func (c *Contact) Self() bool {
169 | return c.GetPuppet().SelfID() == c.Id
170 | }
171 |
172 | // Weixin get the weixin number from a contact
173 | // Sometimes cannot get weixin number due to weixin security mechanism, not recommend.
174 | func (c *Contact) Weixin() string {
175 | return c.payload.WeiXin
176 | }
177 |
178 | // Alias get alias
179 | func (c *Contact) Alias() string {
180 | return c.payload.Alias
181 | }
182 |
183 | // SetAlias set alias
184 | func (c *Contact) SetAlias(newAlias string) {
185 | var err error
186 | defer func() {
187 | if err != nil {
188 | log.Errorf("Contact SetAlias(%s) rejected: %s\n", newAlias, err)
189 | }
190 | }()
191 | err = c.GetPuppet().SetContactAlias(c.Id, newAlias)
192 | if err != nil {
193 | return
194 | }
195 | err = c.GetPuppet().DirtyPayload(schemas.PayloadTypeContact, c.Id)
196 | if err != nil {
197 | log.Error("SetAlias DirtyPayload err:", err)
198 | }
199 | c.payload, err = c.GetPuppet().ContactPayload(c.Id)
200 | if err != nil {
201 | log.Error("SetAlias ContactPayload err:", err)
202 | return
203 | }
204 | if c.payload.Alias != newAlias {
205 | log.Errorf("Contact SetAlias(%s) sync with server fail: set(%s) is not equal to get(%s)\n", newAlias, newAlias, c.payload.Alias)
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/wechaty/user/contact_self.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
7 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
8 | )
9 |
10 | type ContactSelf struct {
11 | *Contact
12 | }
13 |
14 | // NewContactSelf ...
15 | func NewContactSelf(id string, accessory _interface.IAccessory) *ContactSelf {
16 | return &ContactSelf{&Contact{
17 | IAccessory: accessory,
18 | Id: id,
19 | }}
20 | }
21 |
22 | // SetAvatar SET the avatar for a bot
23 | func (c *ContactSelf) SetAvatar(box *filebox.FileBox) error {
24 | if c.Id != c.GetPuppet().SelfID() {
25 | return errors.New("set avatar only available for user self")
26 | }
27 | return c.GetPuppet().SetContactAvatar(c.Id, box)
28 | }
29 |
30 | // QRCode get bot qrcode
31 | func (c *ContactSelf) QRCode() (string, error) {
32 | puppetId := c.GetPuppet().SelfID()
33 | if puppetId == "" {
34 | return "", errors.New("can not get qrcode, user might be either not logged in or already logged out")
35 | }
36 | if c.Id != puppetId {
37 | return "", errors.New("only can get qrcode for the login userself")
38 | }
39 | code, err := c.GetPuppet().ContactSelfQRCode()
40 | if err != nil {
41 | return "", err
42 | }
43 | return code, nil
44 | }
45 |
46 | // SetName change bot name
47 | func (c *ContactSelf) SetName(name string) error {
48 | puppetId := c.GetPuppet().SelfID()
49 | if puppetId == "" {
50 | return errors.New("can not set name for user self, user might be either not logged in or already logged out")
51 | }
52 | if c.Id != puppetId {
53 | return errors.New("only can set name for user self")
54 | }
55 | err := c.GetPuppet().SetContactSelfName(name)
56 | if err != nil {
57 | return err
58 | }
59 | _ = c.Sync()
60 | return nil
61 | }
62 |
63 | // Signature change bot signature
64 | func (c *ContactSelf) Signature(signature string) error {
65 | puppetId := c.GetPuppet().SelfID()
66 | if puppetId == "" {
67 | return errors.New("can not set signature for user self, user might be either not logged in or already logged out")
68 | }
69 | if c.Id != puppetId {
70 | return errors.New("only can change signature for user self")
71 | }
72 | return c.GetPuppet().SetContactSelfSignature(signature)
73 | }
74 |
--------------------------------------------------------------------------------
/wechaty/user/friendship.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
8 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
9 | )
10 |
11 | type Friendship struct {
12 | _interface.IAccessory
13 | id string
14 | payload *schemas.FriendshipPayload
15 | }
16 |
17 | // NewFriendship ...
18 | func NewFriendship(id string, accessory _interface.IAccessory) *Friendship {
19 | return &Friendship{
20 | IAccessory: accessory,
21 | id: id,
22 | }
23 | }
24 |
25 | // Ready ...
26 | func (f *Friendship) Ready() (err error) {
27 | if f.IsReady() {
28 | return nil
29 | }
30 | f.payload, err = f.GetPuppet().FriendshipPayload(f.id)
31 | if err != nil {
32 | return err
33 | }
34 | return f.Contact().Ready(false)
35 | }
36 |
37 | // IsReady ...
38 | func (f *Friendship) IsReady() bool {
39 | return f.payload != nil
40 | }
41 |
42 | // Contact ...
43 | func (f *Friendship) Contact() _interface.IContact {
44 | return f.GetWechaty().Contact().Load(f.payload.ContactId)
45 | }
46 |
47 | func (f *Friendship) String() string {
48 | if f.payload == nil {
49 | return "Friendship not payload"
50 | }
51 | return fmt.Sprintf("Friendship#%s<%s>", f.payload.Type, f.payload.ContactId)
52 | }
53 |
54 | // Accept friend request
55 | func (f *Friendship) Accept() error {
56 | if f.payload.Type != schemas.FriendshipTypeReceive {
57 | return fmt.Errorf("accept() need type to be FriendshipType.Receive, but it got a %s", f.payload.Type)
58 | }
59 | err := f.GetPuppet().FriendshipAccept(f.id)
60 | if err != nil {
61 | return err
62 | }
63 | return f.Contact().Sync()
64 | }
65 |
66 | func (f *Friendship) Type() schemas.FriendshipType {
67 | return f.payload.Type
68 | }
69 |
70 | // Hello get verify message from
71 | func (f *Friendship) Hello() string {
72 | return f.payload.Hello
73 | }
74 |
75 | // toJSON get friendShipPayload Json
76 | func (f *Friendship) ToJSON() (string, error) {
77 | marshal, err := json.Marshal(f.payload)
78 | if err != nil {
79 | return "", err
80 | }
81 | return string(marshal), nil
82 | }
83 |
--------------------------------------------------------------------------------
/wechaty/user/image.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | "github.com/wechaty/go-wechaty/wechaty/interface"
7 | )
8 |
9 | type Images struct {
10 | _interface.IAccessory
11 | ImageId string
12 | }
13 |
14 | // NewImages create image struct
15 | func NewImages(id string, accessory _interface.IAccessory) *Images {
16 | if accessory.GetPuppet() == nil {
17 | panic("Image class can not be instantiated without a puppet!")
18 | }
19 | return &Images{accessory, id}
20 | }
21 |
22 | // Thumbnail message thumbnail images
23 | func (img *Images) Thumbnail() (*filebox.FileBox, error) {
24 | return img.IAccessory.GetPuppet().MessageImage(img.ImageId, schemas.ImageTypeThumbnail)
25 | }
26 |
27 | // HD message hd images
28 | func (img *Images) HD() (*filebox.FileBox, error) {
29 | return img.IAccessory.GetPuppet().MessageImage(img.ImageId, schemas.ImageTypeHD)
30 | }
31 |
32 | // Artwork message artwork images
33 | func (img *Images) Artwork() (*filebox.FileBox, error) {
34 | return img.IAccessory.GetPuppet().MessageImage(img.ImageId, schemas.ImageTypeArtwork)
35 | }
36 |
--------------------------------------------------------------------------------
/wechaty/user/location.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "fmt"
5 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
6 | )
7 |
8 | type Location struct {
9 | payload *schemas.LocationPayload
10 | }
11 |
12 | func NewLocation(payload *schemas.LocationPayload) *Location {
13 | return &Location{
14 | payload: payload,
15 | }
16 | }
17 |
18 | func (l *Location) Payload() schemas.LocationPayload {
19 | return *l.payload
20 | }
21 |
22 | func (l *Location) String() string {
23 | return fmt.Sprintf("Location<%s>", l.payload.Name)
24 | }
25 |
26 | func (l *Location) Address() string {
27 | return l.payload.Address
28 | }
29 |
30 | func (l *Location) Latitude() float64 {
31 | return l.payload.Latitude
32 | }
33 |
34 | func (l *Location) longitude() float64 {
35 | return l.payload.Longitude
36 | }
37 |
38 | func (l *Location) Name() string {
39 | return l.payload.Name
40 | }
41 |
42 | func (l *Location) Accuracy() float32 {
43 | return l.payload.Accuracy
44 | }
45 |
--------------------------------------------------------------------------------
/wechaty/user/mini_program.go:
--------------------------------------------------------------------------------
1 | /**
2 | * Go Wechaty - https://github.com/wechaty/go-wechaty
3 | *
4 | * Authors: Huan LI (李卓桓)
5 | * Chao Fei ()
6 | *
7 | * 2020-now @ Copyright Wechaty
8 | *
9 | * Licensed under the Apache License, Version 2.0 (the 'License');
10 | * you may not use this file except in compliance with the License.
11 | * You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an 'AS IS' BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | */
21 |
22 | package user
23 |
24 | import "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
25 |
26 | type MiniProgram struct {
27 | payload *schemas.MiniProgramPayload
28 | }
29 |
30 | func NewMiniProgram(payload *schemas.MiniProgramPayload) *MiniProgram {
31 | return &MiniProgram{payload: payload}
32 | }
33 |
34 | func (mp *MiniProgram) AppID() string {
35 | if mp.payloadIsNil() {
36 | return ""
37 | }
38 | return mp.payload.Appid
39 | }
40 |
41 | func (mp *MiniProgram) Description() string {
42 | if mp.payloadIsNil() {
43 | return ""
44 | }
45 | return mp.payload.Description
46 | }
47 |
48 | func (mp *MiniProgram) PagePath() string {
49 | if mp.payloadIsNil() {
50 | return ""
51 | }
52 | return mp.payload.PagePath
53 | }
54 |
55 | func (mp *MiniProgram) ThumbUrl() string {
56 | if mp.payloadIsNil() {
57 | return ""
58 | }
59 | return mp.payload.ThumbUrl
60 | }
61 |
62 | func (mp *MiniProgram) Title() string {
63 | if mp.payloadIsNil() {
64 | return ""
65 | }
66 | return mp.payload.Title
67 | }
68 |
69 | func (mp *MiniProgram) Username() string {
70 | if mp.payloadIsNil() {
71 | return ""
72 | }
73 | return mp.payload.Username
74 | }
75 |
76 | func (mp *MiniProgram) ThumbKey() string {
77 | if mp.payloadIsNil() {
78 | return ""
79 | }
80 | return mp.payload.ThumbKey
81 | }
82 |
83 | func (mp *MiniProgram) ShareId() string {
84 | if mp.payloadIsNil() {
85 | return ""
86 | }
87 | return mp.payload.ShareId
88 | }
89 |
90 | func (mp *MiniProgram) IconUrl() string {
91 | if mp.payloadIsNil() {
92 | return ""
93 | }
94 | return mp.payload.IconUrl
95 | }
96 |
97 | func (mp *MiniProgram) Payload() schemas.MiniProgramPayload {
98 | return *mp.payload
99 | }
100 |
101 | func (mp *MiniProgram) payloadIsNil() bool {
102 | return mp.payload == nil
103 | }
104 |
--------------------------------------------------------------------------------
/wechaty/user/room.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/wechaty/go-wechaty/wechaty-puppet/filebox"
8 | "github.com/wechaty/go-wechaty/wechaty-puppet/helper"
9 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
10 | "github.com/wechaty/go-wechaty/wechaty/config"
11 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
12 | )
13 |
14 | type Room struct {
15 | id string
16 | payLoad *schemas.RoomPayload
17 | _interface.IAccessory
18 | }
19 |
20 | // NewRoom ...
21 | func NewRoom(id string, accessory _interface.IAccessory) *Room {
22 | return &Room{
23 | id: id,
24 | IAccessory: accessory,
25 | }
26 | }
27 |
28 | // Ready is For FrameWork ONLY!
29 | func (r *Room) Ready(forceSync bool) (err error) {
30 | if !forceSync && r.IsReady() {
31 | return nil
32 | }
33 |
34 | r.payLoad, err = r.GetPuppet().RoomPayload(r.id)
35 | if err != nil {
36 | return err
37 | }
38 |
39 | memberIDs, err := r.GetPuppet().RoomMemberList(r.id)
40 | if err != nil {
41 | return err
42 | }
43 |
44 | async := helper.NewAsync(helper.DefaultWorkerNum)
45 | for _, id := range memberIDs {
46 | id := id
47 | async.AddTask(func() (interface{}, error) {
48 | return nil, r.GetWechaty().Contact().Load(id).Ready(false)
49 | })
50 | }
51 | _ = async.Result()
52 |
53 | return nil
54 | }
55 |
56 | func (r *Room) IsReady() bool {
57 | return r.payLoad != nil
58 | }
59 |
60 | func (r *Room) String() string {
61 | str := "loading"
62 | if r.payLoad.Topic != "" {
63 | str = r.payLoad.Topic
64 | }
65 | return fmt.Sprintf("Room<%s>", str)
66 | }
67 |
68 | func (r *Room) ID() string {
69 | return r.id
70 | }
71 |
72 | // MemberAll all contacts in a room
73 | // params nil or string or *schemas.RoomMemberQueryFilter
74 | func (r *Room) MemberAll(query interface{}) ([]_interface.IContact, error) {
75 | if query == nil {
76 | return r.memberList()
77 | }
78 | idList, err := r.GetPuppet().RoomMemberSearch(r.id, query)
79 | if err != nil {
80 | return nil, err
81 | }
82 | var contactList []_interface.IContact
83 | for _, id := range idList {
84 | contact := r.GetWechaty().Contact().Load(id)
85 | if err := contact.Ready(false); err != nil {
86 | return nil, err
87 | }
88 | contactList = append(contactList, contact)
89 | }
90 | return contactList, nil
91 | }
92 |
93 | // Member Find all contacts in a room, if get many, return the first one.
94 | // query params string or RoomMemberQueryFilter
95 | func (r *Room) Member(query interface{}) (_interface.IContact, error) {
96 | memberList, err := r.MemberAll(query)
97 | if err != nil {
98 | return nil, err
99 | }
100 | if len(memberList) == 0 {
101 | return nil, nil
102 | }
103 | return memberList[0], nil
104 | }
105 |
106 | // get all room member from the room
107 | func (r *Room) memberList() ([]_interface.IContact, error) {
108 | memberIDList, err := r.GetPuppet().RoomMemberList(r.id)
109 | if err != nil {
110 | return nil, err
111 | }
112 | if len(memberIDList) == 0 {
113 | return nil, nil
114 | }
115 | var contactList []_interface.IContact
116 | for _, id := range memberIDList {
117 | contactList = append(contactList, r.GetWechaty().Contact().Load(id))
118 | }
119 | return contactList, nil
120 | }
121 |
122 | // Alias return contact's roomAlias in the room
123 | func (r *Room) Alias(contact _interface.IContact) (string, error) {
124 | memberPayload, err := r.GetPuppet().RoomMemberPayload(r.id, contact.ID())
125 | if err != nil {
126 | return "", err
127 | }
128 | return memberPayload.RoomAlias, nil
129 | }
130 |
131 | // Sync Force reload data for Room, Sync data from puppet API again.
132 | func (r *Room) Sync() error {
133 | if err := r.GetPuppet().DirtyPayload(schemas.PayloadTypeRoom, r.id); err != nil {
134 | return err
135 | }
136 | if err := r.GetPuppet().DirtyPayload(schemas.PayloadTypeRoomMember, r.id); err != nil {
137 | return err
138 | }
139 | return r.Ready(true)
140 | }
141 |
142 | // Say something params {(string | Contact | FileBox | UrlLink | MiniProgram )}
143 | // mentionList @ contact list
144 | func (r *Room) Say(something interface{}, mentionList ..._interface.IContact) (msg _interface.IMessage, err error) {
145 | var msgID string
146 | switch v := something.(type) {
147 | case string:
148 | msgID, err = r.sayText(v, mentionList...)
149 | case *Contact:
150 | msgID, err = r.GetPuppet().MessageSendContact(r.id, v.Id)
151 | case *filebox.FileBox:
152 | msgID, err = r.GetPuppet().MessageSendFile(r.id, v)
153 | case *UrlLink:
154 | msgID, err = r.GetPuppet().MessageSendURL(r.id, v.payload)
155 | case *MiniProgram:
156 | msgID, err = r.GetPuppet().MessageSendMiniProgram(r.id, v.payload)
157 | default:
158 | return nil, fmt.Errorf("unsupported arg: %v", something)
159 | }
160 | if err != nil {
161 | return nil, err
162 | }
163 | if msgID == "" {
164 | return nil, nil
165 | }
166 | msg = r.GetWechaty().Message().Load(msgID)
167 | return msg, msg.Ready()
168 | }
169 |
170 | func (r *Room) sayText(text string, mentionList ..._interface.IContact) (string, error) {
171 | var mentionIDList []string
172 | if len(mentionList) > 0 {
173 | mentionAlias := make([]string, 0, len(mentionList))
174 | const atSeparator = config.FourPerEmSpace
175 | for _, contact := range mentionList {
176 | mentionIDList = append(mentionIDList, contact.ID())
177 | alias, _ := r.Alias(contact)
178 | if alias == "" {
179 | alias = contact.Name()
180 | }
181 | alias = strings.ReplaceAll(alias, " ", atSeparator)
182 | mentionAlias = append(mentionAlias, "@"+alias)
183 | }
184 | text = strings.Join(mentionAlias, atSeparator) + " " + text
185 | }
186 | return r.GetPuppet().MessageSendText(r.id, text, mentionIDList...)
187 | }
188 |
189 | // Add contact in a room
190 | func (r *Room) Add(contact _interface.IContact) error {
191 | return r.GetPuppet().RoomAdd(r.id, contact.ID())
192 | }
193 |
194 | // Del delete a contact from the room
195 | // it works only when the bot is the owner of the room
196 | func (r *Room) Del(contact _interface.IContact) error {
197 | return r.GetPuppet().RoomDel(r.id, contact.ID())
198 | }
199 |
200 | // Quit the room itself
201 | func (r *Room) Quit() error {
202 | return r.GetPuppet().RoomQuit(r.id)
203 | }
204 |
205 | // Topic get topic from the room
206 | func (r *Room) Topic() string {
207 | if r.payLoad.Topic != "" {
208 | return r.payLoad.Topic
209 | }
210 | memberList, err := r.memberList()
211 | if err != nil {
212 | log.Error("Room Topic err: ", err)
213 | return ""
214 | }
215 | i := 1
216 | defaultTopic := ""
217 | for _, member := range memberList {
218 | if i >= 3 {
219 | break
220 | }
221 | if member.ID() == r.GetPuppet().SelfID() {
222 | continue
223 | }
224 | defaultTopic += member.Name() + ","
225 | i++
226 | }
227 | return strings.TrimRight(defaultTopic, ",")
228 | }
229 |
230 | // SetTopic set topic from the room
231 | func (r *Room) SetTopic(topic string) error {
232 | return r.GetPuppet().SetRoomTopic(r.id, topic)
233 | }
234 |
235 | // Announce get announce from the room
236 | func (r *Room) Announce() (string, error) {
237 | return r.GetPuppet().RoomAnnounce(r.id)
238 | }
239 |
240 | // SetAnnounce set announce from the room
241 | // It only works when bot is the owner of the room.
242 | func (r *Room) SetAnnounce(text string) error {
243 | return r.GetPuppet().SetRoomAnnounce(r.id, text)
244 | }
245 |
246 | // QrCode Get QR Code Value of the Room from the room, which can be used as scan and join the room.
247 | func (r *Room) QrCode() (string, error) {
248 | return r.GetPuppet().RoomQRCode(r.id)
249 | }
250 |
251 | // Has check if the room has member `contact`
252 | func (r *Room) Has(contact _interface.IContact) (bool, error) {
253 | memberIDList, err := r.GetPuppet().RoomMemberList(r.id)
254 | if err != nil {
255 | return false, err
256 | }
257 | for _, id := range memberIDList {
258 | if id == contact.ID() {
259 | return true, nil
260 | }
261 | }
262 | return false, nil
263 | }
264 |
265 | // Owner get room's owner from the room.
266 | func (r *Room) Owner() _interface.IContact {
267 | if r.payLoad.OwnerId == "" {
268 | return nil
269 | }
270 | return r.GetWechaty().Contact().Load(r.payLoad.OwnerId)
271 | }
272 |
273 | // Avatar get avatar from the room.
274 | func (r *Room) Avatar() (*filebox.FileBox, error) {
275 | return r.GetPuppet().RoomAvatar(r.id)
276 | }
277 |
--------------------------------------------------------------------------------
/wechaty/user/room_invitation.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
9 | _interface "github.com/wechaty/go-wechaty/wechaty/interface"
10 | )
11 |
12 | type RoomInvitation struct {
13 | _interface.IAccessory
14 | id string
15 | }
16 |
17 | // NewRoomInvitation ...
18 | func NewRoomInvitation(id string, accessory _interface.IAccessory) *RoomInvitation {
19 | return &RoomInvitation{
20 | IAccessory: accessory,
21 | id: id,
22 | }
23 | }
24 |
25 | func (ri *RoomInvitation) String() string {
26 | id := "loading"
27 | if ri.id != "" {
28 | id = ri.id
29 | }
30 | return fmt.Sprintf("RoomInvitation#%s", id)
31 | }
32 |
33 | func (ri *RoomInvitation) ToStringAsync() (string, error) {
34 | payload, err := ri.GetPuppet().RoomInvitationPayload(ri.id)
35 | if err != nil {
36 | return "", err
37 | }
38 | return fmt.Sprintf("RoomInvitation#%s<%s,%s>", ri.id, payload.Topic, payload.InviterId), nil
39 | }
40 |
41 | // Accept Room Invitation
42 | func (ri *RoomInvitation) Accept() error {
43 | err := ri.GetPuppet().RoomInvitationAccept(ri.id)
44 | if err != nil {
45 | return err
46 | }
47 | inviter, err := ri.Inviter()
48 | if err != nil {
49 | return err
50 | }
51 | topic, err := ri.Topic()
52 | if err != nil {
53 | return err
54 | }
55 | log.Tracef("RoomInvitation accept() with room(%s) & inviter(%s) ready()", topic, inviter)
56 | return inviter.Ready(false)
57 | }
58 |
59 | // Inviter get the inviter from room invitation
60 | func (ri *RoomInvitation) Inviter() (_interface.IContact, error) {
61 | payload, err := ri.GetPuppet().RoomInvitationPayload(ri.id)
62 | if err != nil {
63 | return nil, err
64 | }
65 | return ri.GetWechaty().Contact().Load(payload.InviterId), nil
66 | }
67 |
68 | // Topic get the room topic from room invitation
69 | func (ri *RoomInvitation) Topic() (string, error) {
70 | payload, err := ri.GetPuppet().RoomInvitationPayload(ri.id)
71 | if err != nil {
72 | return "", err
73 | }
74 | return payload.Topic, nil
75 | }
76 |
77 | func (ri *RoomInvitation) MemberCount() (int, error) {
78 | payload, err := ri.GetPuppet().RoomInvitationPayload(ri.id)
79 | if err != nil {
80 | return 0, err
81 | }
82 | return payload.MemberCount, nil
83 | }
84 |
85 | // MemberList list of Room Members that you known(is friend)
86 | func (ri *RoomInvitation) MemberList() ([]_interface.IContact, error) {
87 | payload, err := ri.GetPuppet().RoomInvitationPayload(ri.id)
88 | if err != nil {
89 | return nil, err
90 | }
91 | contactList := make([]_interface.IContact, 0, len(payload.MemberIdList))
92 | for _, id := range payload.MemberIdList {
93 | c := ri.GetWechaty().Contact().Load(id)
94 | if err := c.Ready(false); err != nil {
95 | return nil, err
96 | }
97 | contactList = append(contactList, c)
98 | }
99 | return contactList, nil
100 | }
101 |
102 | // Room get the room from room invitation
103 | func (ri *RoomInvitation) Room() (_interface.IRoom, error) {
104 | payload, err := ri.GetPuppet().RoomInvitationPayload(ri.id)
105 | if err != nil {
106 | return nil, err
107 | }
108 | return ri.GetWechaty().Room().Load(payload.RoomId), nil
109 | }
110 |
111 | // Date get the invitation time
112 | func (ri *RoomInvitation) Date() (time.Time, error) {
113 | payload, err := ri.GetPuppet().RoomInvitationPayload(ri.id)
114 | if err != nil {
115 | return time.Time{}, err
116 | }
117 | return payload.Timestamp, nil
118 | }
119 |
120 | // Age returns the room invitation age in seconds
121 | func (ri *RoomInvitation) Age() (time.Duration, error) {
122 | date, err := ri.Date()
123 | if err != nil {
124 | return 0, err
125 | }
126 | return time.Since(date), nil
127 | }
128 |
129 | func (ri *RoomInvitation) ToJson() (string, error) {
130 | payload, err := ri.GetPuppet().RoomInvitationPayload(ri.id)
131 | if err != nil {
132 | return "", err
133 | }
134 | marshal, err := json.Marshal(payload)
135 | return string(marshal), err
136 | }
137 |
138 | func (ri *RoomInvitation) RawPayload() (schemas.RoomInvitationPayload, error) {
139 | payload, err := ri.GetPuppet().RoomInvitationPayload(ri.id)
140 | if err != nil {
141 | return schemas.RoomInvitationPayload{}, err
142 | }
143 |
144 | return *payload, nil
145 | }
--------------------------------------------------------------------------------
/wechaty/user/tag.go:
--------------------------------------------------------------------------------
1 | /**
2 | * Go Wechaty - https://github.com/wechaty/go-wechaty
3 | *
4 | * Authors: Huan LI (李卓桓)
5 | * Bojie LI (李博杰)
6 | *
7 | * 2020-now @ Copyright Wechaty
8 | *
9 | * Licensed under the Apache License, Version 2.0 (the 'License');
10 | * you may not use this file except in compliance with the License.
11 | * You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an 'AS IS' BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | */
21 |
22 | package user
23 |
24 | import _interface "github.com/wechaty/go-wechaty/wechaty/interface"
25 |
26 | type Tag struct {
27 | _interface.IAccessory
28 | id string
29 | }
30 |
31 | // NewTag ...
32 | func NewTag(id string, accessory _interface.IAccessory) *Tag {
33 | return &Tag{accessory, id}
34 | }
35 |
36 | func (t *Tag) ID() string {
37 | return t.id
38 | }
39 |
40 | // Add tag for contact
41 | func (t *Tag) Add(to _interface.IContact) error {
42 | return t.GetPuppet().TagContactAdd(t.id, to.ID())
43 | }
44 |
45 | // Remove this tag from Contact
46 | func (t *Tag) Remove(from _interface.IContact) error {
47 | return t.GetPuppet().TagContactRemove(t.id, from.ID())
48 | }
49 |
--------------------------------------------------------------------------------
/wechaty/user/url_link.go:
--------------------------------------------------------------------------------
1 | /**
2 | * Go Wechaty - https://github.com/wechaty/go-wechaty
3 | *
4 | * Authors: Huan LI (李卓桓)
5 | * Bojie LI (李博杰)
6 | *
7 | * 2020-now @ Copyright Wechaty
8 | *
9 | * Licensed under the Apache License, Version 2.0 (the 'License');
10 | * you may not use this file except in compliance with the License.
11 | * You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an 'AS IS' BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | */
21 |
22 | package user
23 |
24 | import (
25 | "fmt"
26 |
27 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
28 | )
29 |
30 | type UrlLink struct {
31 | payload *schemas.UrlLinkPayload
32 | }
33 |
34 | func NewUrlLink(payload *schemas.UrlLinkPayload) *UrlLink {
35 | return &UrlLink{payload: payload}
36 | }
37 |
38 | func (ul *UrlLink) String() string {
39 | return fmt.Sprintf("UrlLink<%s>", ul.Url())
40 | }
41 |
42 | func (ul *UrlLink) Url() string {
43 | if ul.payload == nil {
44 | return ""
45 | }
46 | return ul.payload.Url
47 | }
48 |
49 | func (ul *UrlLink) Title() string {
50 | if ul.payload == nil {
51 | return ""
52 | }
53 | return ul.payload.Title
54 | }
55 |
56 | func (ul *UrlLink) ThumbnailUrl() string {
57 | if ul.payload == nil {
58 | return ""
59 | }
60 | return ul.payload.ThumbnailUrl
61 | }
62 |
63 | func (ul *UrlLink) Description() string {
64 | if ul.payload == nil {
65 | return ""
66 | }
67 | return ul.payload.Description
68 | }
69 |
70 | // Payload UrlLink payload
71 | func (ul *UrlLink) Payload() schemas.UrlLinkPayload {
72 | return *ul.payload
73 | }
74 |
--------------------------------------------------------------------------------
/wechaty/wechaty_test.go:
--------------------------------------------------------------------------------
1 | package wechaty
2 |
3 | import (
4 | "github.com/wechaty/go-wechaty/wechaty-puppet/schemas"
5 | "testing"
6 | )
7 |
8 | func TestNewWechaty(t *testing.T) {
9 | tests := []struct {
10 | name string
11 | want *Wechaty
12 | }{
13 | {name: "new", want: NewWechaty()},
14 | }
15 | for _, tt := range tests {
16 | t.Run(tt.name, func(t *testing.T) {
17 | _ = NewWechaty()
18 | })
19 | }
20 | }
21 |
22 | func TestWechaty_Emit(t *testing.T) {
23 | wechaty := NewWechaty()
24 | got := ""
25 | expect := "test"
26 | wechaty.OnHeartbeat(func(context *Context, data string) {
27 | got = data
28 | })
29 | wechaty.emit(schemas.PuppetEventNameHeartbeat, NewContext(), expect)
30 | if got != expect {
31 | log.Fatalf("got %s expect %s", got, expect)
32 | }
33 | }
34 |
35 | func TestWechaty_On(t *testing.T) {
36 | wechaty := NewWechaty()
37 | got := ""
38 | expect := "ding"
39 | wechaty.OnDong(func(context *Context, data string) {
40 | got = data
41 | })
42 | wechaty.emit(schemas.PuppetEventNameDong, NewContext(), expect)
43 | if got != expect {
44 | log.Fatalf("got %s expect %s", got, expect)
45 | }
46 | }
47 |
48 | func TestWechatyPluginDisableOnce(t *testing.T) {
49 | testMessage := "abc"
50 | received := false
51 |
52 | plugin := NewPlugin()
53 | plugin.OnHeartbeat(func(context *Context, data string) {
54 | if data == testMessage {
55 | received = true
56 | }
57 | })
58 |
59 | wechaty := NewWechaty()
60 | wechaty.OnHeartbeat(func(context *Context, data string) {
61 | if data == testMessage {
62 | context.DisableOnce(plugin)
63 | }
64 | })
65 | wechaty.Use(plugin)
66 | wechaty.emit(schemas.PuppetEventNameHeartbeat, NewContext(), testMessage)
67 |
68 | if received == true {
69 | t.Fatalf("disable plugin method failed.(Context.DisableOnce())")
70 | }
71 | }
72 |
73 | func TestWechatyPluginSetEnable(t *testing.T) {
74 | testMessage := "abc"
75 | received := false
76 |
77 | plugin := NewPlugin()
78 | plugin.OnHeartbeat(func(context *Context, data string) {
79 | if data == testMessage {
80 | received = true
81 | }
82 | })
83 |
84 | plugin.SetEnable(false)
85 |
86 | wechaty := NewWechaty()
87 | wechaty.Use(plugin)
88 | wechaty.emit(schemas.PuppetEventNameHeartbeat, NewContext(), testMessage)
89 |
90 | if received == true {
91 | t.Fatalf("disable plugin method failed.(Plugin.Disable())")
92 | }
93 | }
94 |
95 | func TestPluginPassingData(t *testing.T) {
96 | testData := "hello"
97 |
98 | p1 := NewPlugin()
99 | p1.OnHeartbeat(func(context *Context, data string) {
100 | context.SetData("helloStr", testData)
101 | })
102 |
103 | p2 := NewPlugin()
104 | p2.OnHeartbeat(func(context *Context, data string) {
105 | if testData != context.GetData("helloStr").(string) {
106 | t.Fatal("SetData() / GetData() not working.")
107 | }
108 | })
109 |
110 | wechaty := NewWechaty()
111 | wechaty.Use(p1).Use(p2)
112 | wechaty.emit(schemas.PuppetEventNameHeartbeat, NewContext(), "Data")
113 | }
114 |
115 | func TestPluginAbort(t *testing.T) {
116 | plugin := NewPlugin()
117 | plugin.OnHeartbeat(func(context *Context, data string) {
118 | t.Fatal("Context.Abort() not working.")
119 | })
120 |
121 | wechaty := NewWechaty()
122 | wechaty.OnHeartbeat(func(context *Context, data string) {
123 | context.Abort()
124 | })
125 | wechaty.Use(plugin)
126 | wechaty.emit(schemas.PuppetEventNameHeartbeat, NewContext(), "Data")
127 | }
128 |
--------------------------------------------------------------------------------