├── .editorconfig ├── .github ├── CODEOWNERS └── workflows │ └── maven.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── MAINTAINERS ├── Makefile ├── NOTICE ├── README.md ├── VERSION ├── docs └── images │ └── java-wechaty.png ├── examples ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── github │ └── wechaty │ └── example │ ├── Main.java │ └── MainWithPlugin.java ├── pom.xml ├── wechaty-puppet-hostie ├── pom.xml └── src │ └── main │ └── kotlin │ └── io │ └── github │ └── wechaty │ ├── grpc │ └── GrpcPuppet.kt │ └── utils │ └── FutureUtils.kt ├── wechaty-puppet-mock ├── pom.xml └── src │ └── main │ ├── kotlin │ └── io │ │ └── github │ │ └── wechaty │ │ ├── MockData.kt │ │ ├── MockPuppet.kt │ │ ├── config.kt │ │ └── utils │ │ └── MockitoHelper.kt │ └── resources │ ├── image │ └── mock.png │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── wechaty-puppet ├── pom.xml └── src │ ├── main │ └── kotlin │ │ ├── Puppet.kt │ │ └── io │ │ └── github │ │ └── wechaty │ │ ├── StateEnum.kt │ │ ├── ThrowUnsupportedError.kt │ │ ├── eventEmitter │ │ └── EventEmitter.kt │ │ ├── filebox │ │ ├── FileBox.kt │ │ └── FileBoxType.kt │ │ ├── listener │ │ └── Listener.kt │ │ ├── memorycard │ │ ├── MemoryCard.kt │ │ ├── StorageFile.kt │ │ ├── StorageOptions.kt │ │ └── StrorageBackend.kt │ │ ├── schemas │ │ ├── Contact.kt │ │ ├── Event.kt │ │ ├── EventEnum.kt │ │ ├── Friendship.kt │ │ ├── ImageType.kt │ │ ├── Message.kt │ │ ├── MiniProgram.kt │ │ ├── Puppet.kt │ │ ├── PuppetOptions.kt │ │ ├── Room.kt │ │ ├── RoomInvitationPayload.kt │ │ └── UrlLinkPayload.kt │ │ ├── status │ │ └── StateSwitch.kt │ │ ├── utils │ │ ├── FutureUtils.kt │ │ ├── JsonCodec.kt │ │ └── JsonUtils.kt │ │ └── watchdag │ │ └── WatchDog.kt │ └── test │ └── kotlin │ └── io │ └── github │ └── wechaty │ └── filebox │ └── FileBoxTest.kt └── wechaty ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── opengraph │ │ ├── MetaElement.java │ │ ├── OpenGraph.java │ │ └── OpenGraphNamespace.java ├── kotlin │ └── io │ │ └── github │ │ └── wechaty │ │ ├── Accessory.kt │ │ ├── RoomListener.kt │ │ ├── Wechaty.kt │ │ ├── WechatyListener.kt │ │ ├── WechatyOptions.kt │ │ ├── opengraph │ │ └── OpenGraph.kt │ │ ├── type │ │ └── Sayable.kt │ │ ├── user │ │ ├── Contact.kt │ │ ├── ContactSelf.kt │ │ ├── Favorite.kt │ │ ├── Friendship.kt │ │ ├── Image.kt │ │ ├── Message.kt │ │ ├── MiniProgram.kt │ │ ├── Room.kt │ │ ├── RoomInvitation.kt │ │ ├── Tag.kt │ │ ├── UrlLink.kt │ │ └── manager │ │ │ ├── ContactManager.kt │ │ │ ├── FriendshipManager.kt │ │ │ ├── ImageManager.kt │ │ │ ├── MessageManager.kt │ │ │ ├── PuppetManager.kt │ │ │ ├── RoomInvitationManager.kt │ │ │ ├── RoomManager.kt │ │ │ └── TagManager.kt │ │ └── utils │ │ └── QrcodeUtils.kt └── resources │ └── log4j2.xml └── test └── kotlin └── io └── github └── wechaty └── user └── RoomTest.kt /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 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 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/about-codeowners/ 3 | # 4 | 5 | * @wechaty/java 6 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | - name: Build with Maven 24 | run: mvn -B package --file pom.xml 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.nar 19 | *.ear 20 | *.zip 21 | *.tar.gz 22 | *.rar 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | ### macOS template 28 | # General 29 | .DS_Store 30 | .AppleDouble 31 | .LSOverride 32 | 33 | 34 | # Icon must end with two \r 35 | Icon 36 | 37 | # Thumbnails 38 | ._* 39 | 40 | # Files that might appear in the root of a volume 41 | .DocumentRevisions-V100 42 | .fseventsd 43 | .Spotlight-V100 44 | .TemporaryItems 45 | .Trashes 46 | .VolumeIcon.icns 47 | .com.apple.timemachine.donotpresent 48 | 49 | # Directories potentially created on remote AFP share 50 | .AppleDB 51 | .AppleDesktop 52 | Network Trash Folder 53 | Temporary Items 54 | .apdisk 55 | 56 | ### Kotlin template 57 | # Compiled class file 58 | *.class 59 | 60 | # Log file 61 | *.log 62 | 63 | # BlueJ files 64 | *.ctxt 65 | 66 | # Mobile Tools for Java (J2ME) 67 | .mtj.tmp/ 68 | 69 | # Package Files # 70 | *.jar 71 | *.war 72 | *.nar 73 | *.ear 74 | *.zip 75 | *.tar.gz 76 | *.rar 77 | 78 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 79 | hs_err_pid* 80 | 81 | ### JetBrains template 82 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 83 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 84 | 85 | # User-specific stuff 86 | .idea/**/workspace.xml 87 | .idea/**/tasks.xml 88 | .idea/**/usage.statistics.xml 89 | .idea/**/dictionaries 90 | .idea/**/shelf 91 | 92 | # Generated files 93 | .idea/**/contentModel.xml 94 | 95 | # Sensitive or high-churn files 96 | .idea/**/dataSources/ 97 | .idea/**/dataSources.ids 98 | .idea/**/dataSources.local.xml 99 | .idea/**/sqlDataSources.xml 100 | .idea/**/dynamic.xml 101 | .idea/**/uiDesigner.xml 102 | .idea/**/dbnavigator.xml 103 | 104 | # Gradle 105 | .idea/**/gradle.xml 106 | .idea/**/libraries 107 | 108 | # Gradle and Maven with auto-import 109 | # When using Gradle or Maven with auto-import, you should exclude module files, 110 | # since they will be recreated, and may cause churn. Uncomment if using 111 | # auto-import. 112 | # .idea/artifacts 113 | # .idea/compiler.xml 114 | # .idea/modules.xml 115 | # .idea/*.iml 116 | # .idea/modules 117 | # *.iml 118 | # *.ipr 119 | 120 | # CMake 121 | cmake-build-*/ 122 | 123 | # Mongo Explorer plugin 124 | .idea/**/mongoSettings.xml 125 | 126 | # File-based project format 127 | *.iws 128 | 129 | # IntelliJ 130 | out/ 131 | 132 | # mpeltonen/sbt-idea plugin 133 | .idea_modules/ 134 | 135 | # JIRA plugin 136 | atlassian-ide-plugin.xml 137 | 138 | # Cursive Clojure plugin 139 | .idea/replstate.xml 140 | 141 | # Crashlytics plugin (for Android Studio and IntelliJ) 142 | com_crashlytics_export_strings.xml 143 | crashlytics.properties 144 | crashlytics-build.properties 145 | fabric.properties 146 | 147 | # Editor-based Rest Client 148 | .idea/httpRequests 149 | 150 | # Android studio 3.1+ serialized cache file 151 | .idea/caches/build_file_checksums.ser 152 | 153 | build/* 154 | 155 | .gradle/5.2.1/vcsMetadata-1/ 156 | .idea/modules/ 157 | .idea/vcs.xml 158 | target/ 159 | 160 | .idea/* 161 | 162 | *.iml 163 | 164 | 165 | token.txt 166 | Main2.java 167 | Main3.java 168 | aliyun/* 169 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | 4 | "editor.fontFamily": "'Fira Code iScript', Consolas, 'Courier New', monospace", 5 | "editor.fontLigatures": true, 6 | 7 | "editor.tokenColorCustomizations": { 8 | "textMateRules": [ 9 | { 10 | "scope": [ 11 | //following will be in italics (=Pacifico) 12 | "comment", 13 | // "entity.name.type.class", //class names 14 | "keyword", //import, export, return… 15 | "support.class.builtin.js", //String, Number, Boolean…, this, super 16 | "storage.modifier", //static keyword 17 | "storage.type.class.js", //class keyword 18 | "storage.type.function.js", // function keyword 19 | "storage.type.js", // Variable declarations 20 | "keyword.control.import.js", // Imports 21 | "keyword.control.from.js", // From-Keyword 22 | "entity.name.type.js", // new … Expression 23 | "keyword.control.flow.js", // await 24 | "keyword.control.conditional.js", // if 25 | "keyword.control.loop.js", // for 26 | "keyword.operator.new.js", // new 27 | ], 28 | "settings": { 29 | "fontStyle": "italic", 30 | }, 31 | }, 32 | { 33 | "scope": [ 34 | //following will be excluded from italics (My theme (Monokai dark) has some defaults I don't want to be in italics) 35 | "invalid", 36 | "keyword.operator", 37 | "constant.numeric.css", 38 | "keyword.other.unit.px.css", 39 | "constant.numeric.decimal.js", 40 | "constant.numeric.json", 41 | "entity.name.type.class.js" 42 | ], 43 | "settings": { 44 | "fontStyle": "", 45 | }, 46 | } 47 | ] 48 | }, 49 | 50 | "files.exclude": { 51 | "dist/": true, 52 | "doc/": true, 53 | "node_modules/": true, 54 | "package/": true, 55 | }, 56 | "alignment": { 57 | "operatorPadding": "right", 58 | "indentBase": "firstline", 59 | "surroundSpace": { 60 | "colon": [1, 1], // The first number specify how much space to add to the left, can be negative. The second number is how much space to the right, can be negative. 61 | "assignment": [1, 1], // The same as above. 62 | "arrow": [1, 1], // The same as above. 63 | "comment": 2, // Special how much space to add between the trailing comment and the code. 64 | // If this value is negative, it means don't align the trailing comment. 65 | } 66 | }, 67 | "editor.formatOnSave": false, 68 | "python.pythonPath": "python3", 69 | "eslint.validate": [ 70 | "javascript", 71 | "typescript", 72 | ], 73 | "cSpell.words": [ 74 | "Wechaty", 75 | "appid", 76 | "huan", 77 | "ioscat", 78 | "ipad", 79 | "lijiarui", 80 | "logonoff", 81 | "padchat", 82 | "padplus", 83 | "pagepath", 84 | "qrcode", 85 | "removee", 86 | "thumbnailurl", 87 | "wechat", 88 | "weixin", 89 | "zixia" 90 | ], 91 | } 92 | -------------------------------------------------------------------------------- /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 2020 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 | Zhengxin DIAO (刁政欣) 3 | Xiaoya Ren 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Java Wechaty 2 | # 3 | # GitHb: https://github.com/wechaty/java-wechaty 4 | # Author: Huan LI github.com/huan 5 | # 6 | 7 | .PHONY: all 8 | all: install bot 9 | 10 | .PHONY: install 11 | install: 12 | echo "maven install ?" 13 | 14 | .PHONY: test 15 | test: 16 | echo "maven test ?" 17 | 18 | .PHONY: bot 19 | bot: 20 | java examples/ding-dong-bot.java 21 | 22 | .PHONY: version 23 | version: 24 | @newVersion=$$(awk -F. '{print $$1"."$$2"."$$3+1}' < VERSION) \ 25 | && echo $${newVersion} > VERSION \ 26 | && git add VERSION \ 27 | && git commit -m "$${newVersion}" > /dev/null \ 28 | && git tag "v$${newVersion}" \ 29 | && echo "Bumped version to $${newVersion}" 30 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Java 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 | # java-wechaty 2 | 3 | ![Java CI with Maven](https://github.com/wechaty/java-wechaty/workflows/Java%20CI%20with%20Maven/badge.svg) 4 | [![Java Version](https://img.shields.io/maven-central/v/io.github.wechaty/wechaty?label=Maven)](https://mvnrepository.com/artifact/io.github.wechaty/wechaty) 5 | 6 | ![Java Wechaty](docs/images/java-wechaty.png) 7 | 8 | [![Java Wechaty Getting Started](https://img.shields.io/badge/Java%20Wechaty-Getting%20Started-orange)](https://github.com/wechaty/java-wechaty-getting-started) 9 | [![Wechaty in Kotlin](https://img.shields.io/badge/Wechaty-Kotlin-orange)](https://github.com/wechaty/java-wechaty) 10 | 11 | ## Connecting Chatbots 12 | 13 | [![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-brightgreen.svg)](https://github.com/Wechaty/wechaty) 14 | [![Kotlin](https://img.shields.io/badge/%3C%2F%3E-Kotlin-orange.svg)](https://kotlinlang.org) 15 | 16 | Wechaty is a RPA SDK for Wechat **Individual** Account that can help you create a chatbot in 6 lines of Java. 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 _java wechaty_, join our **Wechaty Java Developers' Home**. 40 | 41 | ![Wechaty Friday.BOT QR Code](https://wechaty.js.org/img/friday-qrcode.svg) 42 | 43 | Scan now, because other Wechaty Java developers want to talk with you too! (secret code: _java wechaty_) 44 | 45 | ## The World's Shortest Java ChatBot: 6 lines of Code 46 | 47 | ```java 48 | class Bot{ 49 | public static void main(String args[]){ 50 | Wechaty bot = Wechaty.instance() 51 | .onScan((qrcode, statusScanStatus, data) -> System.out.println(QrcodeUtils.getQr(qrcode))) 52 | .onLogin(user -> System.out.println("User logined :" + user)) 53 | .onMessage(message -> System.out.println("Message:" + message)) 54 | .start(true); 55 | } 56 | } 57 | ``` 58 | 59 | if use plugins 60 | ```java 61 | class Bot{ 62 | public static void main(String args[]){ 63 | Wechaty bot = Wechaty.instance() 64 | .use( 65 | WechatyPlugins.ScanPlugin(), 66 | WechatyPlugins.DingDongPlugin(null)) 67 | .start(true); 68 | } 69 | } 70 | ``` 71 | 72 | ## Development 73 | 74 | To be writen: 75 | 76 | ```sh 77 | make install 78 | make bot 79 | ``` 80 | 81 | ## Java Wechaty Developing Plan 82 | 83 | We already have Wechaty in TypeScript, It will be not too hard to translate the TypeScript(TS) to Java because [wechaty](https://github.com/wechaty/wechaty) has only 3,000 lines of the TS code, they are well designed and de-coupled by the [wechaty-puppet](https://github.com/wechaty/wechaty-puppet/) abstraction. So after we have translated those 3,000 lines of TypeScript code, we will almost be done. 84 | 85 | As we have already a ecosystem of Wechaty in TypeScript, so we will not have to implement everything in Java, especially, in the Feb 2020, we have finished the [@chatie/grpc](https://github.com/chatie/grpc) service abstracting module with the [wechaty-puppet-hostie](https://github.com/wechaty/wechaty-puppet-hostie) implmentation. 86 | 87 | The following diagram shows out that we can reuse almost everything in TypeScript, and what we need to do is only the block located at the top right of the diagram: `Wechaty (Java)`. 88 | 89 | ```ascii 90 | +--------------------------+ +--------------------------+ 91 | | | | | 92 | | Wechaty (TypeScript) | | Wechaty (Java) | 93 | | | | | 94 | +--------------------------+ +--------------------------+ 95 | 96 | +-------------------------------------------------------+ 97 | | Wechaty Puppet Hostie | 98 | | | 99 | | (wechaty-puppet-hostie) | 100 | +-------------------------------------------------------+ 101 | 102 | +--------------------- @chatie/grpc ----------------------+ 103 | 104 | +-------------------------------------------------------+ 105 | | Wechaty Puppet Abstract | 106 | | | 107 | | (wechaty-puppet) | 108 | +-------------------------------------------------------+ 109 | 110 | +--------------------------+ +--------------------------+ 111 | | Pad Protocol | | Web Protocol | 112 | | | | | 113 | | wechaty-puppet-padplus | |(wechaty-puppet-puppeteer)| 114 | +--------------------------+ +--------------------------+ 115 | +--------------------------+ +--------------------------+ 116 | | Windows Protocol | | Mac Protocol | 117 | | | | | 118 | | (wechaty-puppet-windows) | | (wechaty-puppet-macpro) | 119 | +--------------------------+ +--------------------------+ 120 | ``` 121 | 122 | ## Example: How to Translate TypeScript to Java 123 | 124 | There's a 100 lines class named `Image` in charge of downloading the WeChat image to different sizes. 125 | 126 | It is a great example for demonstrating how do we translate the TypeScript to Java in Wechaty Way: 127 | 128 | ### Image Class Source Code 129 | 130 | - TypeScript: 131 | - Java: 132 | 133 | If you are interested in the translation and want to look at how it works, it will be a good start from reading and comparing those two `Image` class files in TypeScript and Java at the same time. 134 | 135 | ## To-do List 136 | 137 | - TS: TypeScript 138 | - SLOC: Source Lines Of Code 139 | 140 | ### Wechaty Internal Modules 141 | 142 | 1. [ ] Class Wechaty 143 | - TS SLOC(1160): 144 | - [ ] Code 145 | - [ ] Unit Tests 146 | - [ ] Documentation 147 | 1. [ ] Class Contact 148 | - TS SLOC(804): 149 | - [x] Code 150 | - [ ] Unit Tests 151 | - [ ] Documentation 152 | 1. [ ] Class ContactSelf 153 | - TS SLOC(199): 154 | - [x] Code 155 | - [ ] Unit Tests 156 | - [ ] Documentation 157 | 1. [ ] Class Message 158 | - TS SLOC(1054): 159 | - [x] Code 160 | - [ ] Unit Tests 161 | - [ ] Documentation 162 | 1. [ ] Class Room 163 | - TS SLOC(1194): 164 | - [x] Code 165 | - [ ] Unit Tests 166 | - [ ] Documentation 167 | 1. [ ] Class Image 168 | - TS SLOC(60): 169 | - [x] Code 170 | - [ ] Unit Tests 171 | - [ ] Documentation 172 | 1. [ ] Class Accessory 173 | - TS SLOC(179): 174 | - [x] Code 175 | - [ ] Unit Tests 176 | - [ ] Documentation 177 | 1. [ ] Class Config 178 | - TS SLOC(187): 179 | - [ ] Code 180 | - [ ] Unit Tests 181 | - [ ] Documentation 182 | 1. [ ] Class Favorite 183 | - TS SLOC(52): 184 | - [ ] Code 185 | - [ ] Unit Tests 186 | - [ ] Documentation 187 | 1. [ ] Class Friendship 188 | - TS SLOC(417): 189 | - [x] Code 190 | - [ ] Unit Tests 191 | - [ ] Documentation 192 | 1. [ ] Class MiniProgram 193 | - TS SLOC(70): 194 | - [ ] Code 195 | - [ ] Unit Tests 196 | - [ ] Documentation 197 | 1. [ ] Class RoomInvitation 198 | - TS SLOC(317): 199 | - [ ] Code 200 | - [ ] Unit Tests 201 | - [ ] Documentation 202 | 1. [ ] Class Tag 203 | - TS SLOC(190): 204 | - [x] Code 205 | - [ ] Unit Tests 206 | - [ ] Documentation 207 | 1. [ ] Class UrlLink 208 | - TS SLOC(107): 209 | - [ ] Code 210 | - [ ] Unit Tests 211 | - [ ] Documentation 212 | 213 | ### Wechaty External Modules 214 | 215 | 1. [ ] Class FileBox 216 | - TS SLOC(638): 217 | - [ ] Code 218 | - [ ] Unit Tests 219 | - [ ] Documentation 220 | 1. [ ] Class MemoryCard 221 | - TS SLOC(376): 222 | - [ ] Code 223 | - [ ] Unit Tests 224 | - [ ] Documentation 225 | 1. [ ] Class WechatyPuppet 226 | - TS SLOC(1115): 227 | - [x] Code 228 | - [ ] Unit Tests 229 | - [ ] Documentation 230 | 1. [ ] Class WechatyPuppetHostie 231 | - TS SLOC(909): 232 | - [x] Code 233 | - [ ] Unit Tests 234 | - [ ] Documentation 235 | 236 | ## Usage 237 | 238 | 1. Add a token to class `io.github.wechaty.example.Main` in folder `examples/src/main/java` 239 | 2. Build: 240 | 241 | ```shell 242 | cd examples/ 243 | mvn install 244 | ``` 245 | 246 | 3. Run 247 | 248 | ```shell 249 | java -jar target/wechaty-example-1.0.0-SNAPSHOT-jar-with-dependencies.jar 250 | # or run in background 251 | nohup java -jar target/wechaty-example-1.0.0-SNAPSHOT-jar-with-dependencies.jar &>> nohup.out & tailf nohup.out 252 | ``` 253 | 254 | 4. enjoy 255 | 256 | ## Requirements 257 | 258 | 1. JDK/JRE 259 | 260 | ## Install 261 | 262 | ```shell 263 | mvn install wechaty 264 | ``` 265 | 266 | ## Links 267 | 268 | 1. [Publish Java Module to Maven Central Repo - OSSRH Guide](https://central.sonatype.org/pages/ossrh-guide.html) 269 | 1. [Kotlin vs Java: Most Important Differences That You Must Know](https://hackr.io/blog/kotlin-vs-java) 270 | 271 | ## History 272 | 273 | ### master 274 | 275 | ### v0.4 (Jun 19, 2020) 276 | 277 | Java(Kotlin) Wechaty **BETA** Released! 278 | 279 | Read more from our Multi-language Wechaty Beta Release event from our blog: 280 | 281 | - [Multi Language Wechaty Beta Release Announcement!](https://wechaty.js.org/2020/06/19/multi-language-wechaty-beta-release/) 282 | 283 | ### v0.1.4 (June 13 2020) 284 | 1. use `PuppetManager` to manage multi puppet implementations. 285 | 2. add mock puppet. 286 | 3. remove puppet implementations from wechaty pom. Which implementation to use depends on which implementation jar in your pom. 287 | 4. fix some bugs. 288 | 289 | ### v0.1.3 (June 6 2020) 290 | 1. support plugins! 291 | 292 | ### v0.1.2 (June 6 2020) 293 | 1. change method `on(Event:String,Listener:listener)` to `onEvent(Listener:listener)`. See examples 294 | 2. update version to 0.1.2 295 | 3. update wechaty grpc to 0.16.1 296 | 4. move examples from wechaty to independent module. Make example easy to use. 297 | 298 | ### v0.1.1 (May 31 2020) 299 | 300 | 1. update version to 0.1.1-SNAPSHOT 301 | 1. finish all function of room 302 | 1. remove log4j2 package from wechaty 303 | 1. change all log level to debug 304 | 1. remove log4j2.xml from wechaty 305 | 306 | ### v0.1 (May 18 2020) 307 | 308 | We decided to use Kotlin to develop the Java Wechaty! 309 | 310 | 1. Project re-inited by overwriting `wechaty/java-wechaty` by `diaozxin/kotlin-wechaty`. 311 | 1. Second contributor joined: [@redmaple1](https://github.com/redmaple1) Xiaoya Ren 312 | 313 | ### v0.0.1 (Mar 12, 2020) 314 | 315 | 1. Project created. 316 | 1. First contributor joined: [@diaozxin007](https://github.com/diaozxin007) Zhengxin DIAO (刁政欣) 317 | 318 | ## Related Projects 319 | 320 | - [Wechaty](https://github.com/wechaty/wechaty) - Conversatioanl AI Chatot SDK for Wechaty Individual Accounts (TypeScript) 321 | - [Python Wechaty](https://github.com/wechaty/python-wechaty) - Python WeChaty Conversational AI Chatbot SDK for Wechat Individual Accounts (Python) 322 | - [Go Wechaty](https://github.com/wechaty/go-wechaty) - Go WeChaty Conversational AI Chatbot SDK for Wechat Individual Accounts (Go) 323 | - [Java Wechaty](https://github.com/wechaty/java-wechaty) - Java WeChaty Conversational AI Chatbot SDK for Wechat Individual Accounts (Java) 324 | - [Scala Wechaty](https://github.com/wechaty/scala-wechaty) - Scala WeChaty Conversational AI Chatbot SDK for WechatyIndividual Accounts (Scala) 325 | 326 | ## Stargazers over time 327 | 328 | [![Stargazers over time](https://starchart.cc/wechaty/java-wechaty.svg)](https://starchart.cc/wechaty/java-wechaty) 329 | 330 | ## Badge 331 | 332 | [![Wechaty in Kotlin](https://img.shields.io/badge/Wechaty-Kotlin-orange)](https://github.com/wechaty/java-wechaty) 333 | 334 | ```md 335 | [![Wechaty in Kotlin](https://img.shields.io/badge/Wechaty-Kotlin-orange)](https://github.com/wechaty/java-wechaty) 336 | ``` 337 | 338 | ## Contributors 339 | 340 | [![contributors](https://sourcerer.io/fame/huan/wechaty/java-wechaty/images/0)](https://sourcerer.io/fame/huan/wechaty/java-wechaty/links/0) 341 | [![contributors](https://sourcerer.io/fame/huan/wechaty/java-wechaty/images/1)](https://sourcerer.io/fame/huan/wechaty/java-wechaty/links/1) 342 | [![contributors](https://sourcerer.io/fame/huan/wechaty/java-wechaty/images/2)](https://sourcerer.io/fame/huan/wechaty/java-wechaty/links/2) 343 | [![contributors](https://sourcerer.io/fame/huan/wechaty/java-wechaty/images/3)](https://sourcerer.io/fame/huan/wechaty/java-wechaty/links/3) 344 | [![contributors](https://sourcerer.io/fame/huan/wechaty/java-wechaty/images/4)](https://sourcerer.io/fame/huan/wechaty/java-wechaty/links/4) 345 | [![contributors](https://sourcerer.io/fame/huan/wechaty/java-wechaty/images/5)](https://sourcerer.io/fame/huan/wechaty/java-wechaty/links/5) 346 | [![contributors](https://sourcerer.io/fame/huan/wechaty/java-wechaty/images/6)](https://sourcerer.io/fame/huan/wechaty/java-wechaty/links/6) 347 | [![contributors](https://sourcerer.io/fame/huan/wechaty/java-wechaty/images/7)](https://sourcerer.io/fame/huan/wechaty/java-wechaty/links/7) 348 | 349 | ## Committers 350 | 351 | - [@redmaple1](https://github.com/redmaple1) Xiaoya Ren 352 | - [@huan](https://github.com/huan) - Huan LI (李卓桓) 353 | 354 | ## Creator 355 | 356 | - [@diaozxin007](https://github.com/diaozxin007) diaozxin@gmail.com 357 | - Website: [犀利豆的博客](https://xilidou.com/) 358 | 359 | ## Copyright & License 360 | 361 | - Code & Docs © 2020 Wechaty Contributors 362 | - Code released under the Apache-2.0 License 363 | - Docs released under Creative Commons 364 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.4.0 2 | -------------------------------------------------------------------------------- /docs/images/java-wechaty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechaty/java-wechaty/65ca2e43d4978e1fac92d32c243ccf3b6df4a886/docs/images/java-wechaty.png -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | io.github.wechaty 7 | wechaty-parent 8 | 0.1.5-SNAPSHOT 9 | 10 | 11 | 4.0.0 12 | wechaty-example 13 | 14 | wechaty/example 15 | 16 | jar 17 | 1.0.0-SNAPSHOT 18 | 19 | 20 | 1.3.72 21 | 1.8 22 | UTF-8 23 | UTF-8 24 | true 25 | 0.10.1 26 | 0.1.5-SNAPSHOT 27 | 28 | 29 | 30 | 31 | 32 | io.github.wechaty 33 | wechaty 34 | ${wechaty.version} 35 | 36 | 37 | org.apache.logging.log4j 38 | log4j-api 39 | 2.13.1 40 | 41 | 42 | 43 | org.apache.logging.log4j 44 | log4j-core 45 | 2.13.2 46 | 47 | 48 | org.apache.logging.log4j 49 | log4j-slf4j-impl 50 | 2.13.1 51 | 52 | 53 | 54 | com.lmax 55 | disruptor 56 | 3.4.2 57 | 58 | 59 | 60 | org.slf4j 61 | slf4j-api 62 | 1.7.30 63 | 64 | 65 | 66 | io.github.wechaty 67 | java-wechaty-plugin-contrib 68 | 1.0.0-SNAPSHOT 69 | 70 | 71 | org.apache.commons 72 | commons-lang3 73 | 74 | 75 | 76 | com.aliyun 77 | aliyun-java-sdk-core 78 | 4.5.2 79 | 80 | 81 | 82 | 83 | com.aliyun 84 | aliyun-java-sdk-imagerecog 85 | 1.0.7 86 | 87 | 88 | 89 | 90 | com.aliyun.oss 91 | aliyun-sdk-oss 92 | 3.4.2 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | oss-sonatype 101 | oss-sonatype 102 | https://oss.sonatype.org/content/repositories/snapshots/ 103 | 104 | true 105 | 106 | 107 | 108 | alimaven 109 | aliyun maven 110 | https://maven.aliyun.com/nexus/content/groups/public/ 111 | 112 | true 113 | 114 | 115 | false 116 | 117 | 118 | 119 | 120 | 121 | 122 | aliyun-plugin 123 | https://maven.aliyun.com/repository/public 124 | 125 | true 126 | 127 | 128 | false 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-compiler-plugin 138 | 3.6.1 139 | 140 | 1.8 141 | 1.8 142 | 143 | 144 | 145 | org.apache.maven.plugins 146 | maven-assembly-plugin 147 | 3.3.0 148 | 149 | 150 | make-assembly 151 | package 152 | 153 | single 154 | 155 | 156 | 157 | 158 | io.github.wechaty.example.Main 159 | 160 | 161 | 162 | jar-with-dependencies 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /examples/src/main/java/io/github/wechaty/example/Main.java: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.example; 2 | 3 | 4 | import io.github.wechaty.Wechaty; 5 | import io.github.wechaty.user.Room; 6 | import io.github.wechaty.utils.QrcodeUtils; 7 | import okhttp3.OkHttpClient; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import java.io.IOException; 11 | import java.util.concurrent.ExecutionException; 12 | 13 | 14 | public class Main { 15 | 16 | public static void main(String[] args){ 17 | 18 | Wechaty bot = Wechaty.instance("your_token") 19 | .onScan((qrcode, statusScanStatus, data) -> System.out.println(QrcodeUtils.getQr(qrcode))) 20 | .onLogin(user -> System.out.println(user)) 21 | .onMessage(message -> { 22 | Room room = message.room(); 23 | String text = message.text(); 24 | if (StringUtils.equals(text, "#ding")) { 25 | if (room != null) { 26 | room.say("dong"); 27 | } 28 | } 29 | }).start(true); 30 | 31 | // } 32 | 33 | // Room room = bot.room(); 34 | // 35 | // RoomQueryFilter roomQueryFilter = new RoomQueryFilter(); 36 | // 37 | // roomQueryFilter.setTopic("ChatOps - Donut"); 38 | // 39 | // Future> all = room.findAll(roomQueryFilter); 40 | // 41 | // List rooms = all.get(); 42 | // 43 | // Room room1 = rooms.get(0); 44 | // 45 | // FileBox fileBox = FileBox.fromFile("dong.jpg", "dong.jpg"); 46 | // 47 | // room1.say(fileBox).get(); 48 | 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /examples/src/main/java/io/github/wechaty/example/MainWithPlugin.java: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.example; 2 | 3 | import io.github.wechaty.Wechaty; 4 | import io.github.wechaty.plugins.WechatyPlugins; 5 | 6 | public class MainWithPlugin { 7 | 8 | public static void main(String[] args) { 9 | 10 | Wechaty bot = Wechaty.instance("your_token") 11 | .use(WechatyPlugins.ScanPlugin(), WechatyPlugins.DingDongPlugin(null)) 12 | .start(true); 13 | 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | io.github.wechaty 6 | wechaty-parent 7 | pom 8 | 0.1.5-SNAPSHOT 9 | kotlin-wechaty 10 | 11 | 12 | Wechaty is a Wechat Bot SDK for Personal Account that lets you create software to extend the functionality of the 13 | Wechat, 14 | writen in Node.js with TypeScript, Support all platforms including Linux, Windows, Darwin(OSX/Mac) and Docker. 15 | 16 | https://github.com/Chatie/grpc 17 | 18 | 19 | 20 | 21 | 1.3.72 22 | 1.8 23 | UTF-8 24 | UTF-8 25 | true 26 | 0.10.1 27 | 28 | 29 | 30 | wechaty 31 | wechaty-puppet 32 | wechaty-puppet-hostie 33 | wechaty-puppet-mock 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | io.github.wechaty 42 | 0.16.1 43 | grpc 44 | 45 | 46 | 47 | org.jetbrains.kotlin 48 | kotlin-stdlib 49 | ${kotlin.version} 50 | 51 | 52 | 53 | 54 | org.apache.commons 55 | commons-lang3 56 | 3.10 57 | 58 | 59 | 60 | 61 | 62 | com.google.guava 63 | guava 64 | 29.0-jre 65 | 66 | 67 | 68 | 69 | 70 | com.github.ben-manes.caffeine 71 | caffeine 72 | 2.8.1 73 | 74 | 75 | 76 | org.slf4j 77 | slf4j-api 78 | 1.7.30 79 | 80 | 81 | org.apache.logging.log4j 82 | log4j-core 83 | 2.13.2 84 | test 85 | 86 | 87 | org.apache.logging.log4j 88 | log4j-slf4j-impl 89 | 2.13.1 90 | test 91 | 92 | 93 | com.lmax 94 | disruptor 95 | 3.4.2 96 | test 97 | 98 | 99 | 100 | org.apache.commons 101 | commons-collections4 102 | 4.4 103 | 104 | 105 | 106 | com.fasterxml.jackson.module 107 | jackson-module-kotlin 108 | 2.11.0 109 | 110 | 111 | 112 | 113 | com.fasterxml.jackson.core 114 | jackson-core 115 | 2.11.0 116 | 117 | 118 | 119 | 120 | com.fasterxml.jackson.core 121 | jackson-databind 122 | 2.11.0 123 | 124 | 125 | 126 | 127 | com.google.zxing 128 | core 129 | 3.3.0 130 | 131 | 132 | 133 | 134 | commons-io 135 | commons-io 136 | 2.6 137 | 138 | 139 | 140 | 141 | com.squareup.okhttp3 142 | okhttp 143 | 4.6.0 144 | 145 | 146 | 147 | org.hamcrest 148 | hamcrest-core 149 | 1.3 150 | 151 | 152 | net.sourceforge.htmlcleaner 153 | htmlcleaner 154 | 2.16 155 | 156 | 157 | 158 | org.mockito 159 | mockito-core 160 | 3.3.3 161 | 162 | 163 | junit 164 | junit 165 | 4.12 166 | 167 | 168 | com.github.javafaker 169 | javafaker 170 | 1.0.2 171 | 172 | 173 | org.reflections 174 | reflections 175 | 0.9.10 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | org.codehaus.mojo 185 | build-helper-maven-plugin 186 | 3.0.0 187 | 188 | 189 | 190 | generate-sources 191 | 192 | add-source 193 | 194 | 195 | 196 | src/main/kotlin 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | Zhengxin Diao 229 | diaozxin@gmail.com 230 | 231 | 232 | Xiaoya ren 233 | 234 | 235 | 236 | 237 | 238 | Apache License, Version 2.0 239 | http://www.apache.org/licenses/LICENSE-2.0 240 | 241 | 242 | 243 | 244 | 245 | Zhengxin Diao 246 | diaozxin@gmail.com 247 | 248 | 249 | Xiaoya ren 250 | 251 | 252 | 253 | 254 | 255 | sonatype 256 | https://oss.sonatype.org/content/repositories/snapshots 257 | 258 | 259 | sonatype 260 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 261 | 262 | 263 | 264 | 265 | https://github.com/Chatie/grpc 266 | scm:git:ssh://github.com/Chatie/grpc.git 267 | scm:git:ssh://git@github.com/Chatie/grpc.git 268 | HEAD 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /wechaty-puppet-hostie/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | io.github.wechaty 6 | wechaty-parent 7 | 0.1.5-SNAPSHOT 8 | 9 | 4.0.0 10 | wechaty-puppet-hostie 11 | jar 12 | 13 | 14 | 15 | 16 | org.jetbrains.kotlin 17 | kotlin-stdlib 18 | 19 | 20 | io.github.wechaty 21 | wechaty-puppet 22 | ${project.version} 23 | 24 | 25 | io.github.wechaty 26 | grpc 27 | 28 | 29 | com.squareup.okhttp3 30 | okhttp 31 | 32 | 33 | 34 | 35 | com.google.guava 36 | guava 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.slf4j 53 | slf4j-api 54 | 55 | 56 | 57 | com.fasterxml.jackson.module 58 | jackson-module-kotlin 59 | 60 | 61 | 62 | org.apache.commons 63 | commons-lang3 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.jetbrains.kotlin 73 | kotlin-maven-plugin 74 | ${kotlin.version} 75 | 76 | 77 | compile 78 | 79 | compile 80 | 81 | 82 | 83 | ${project.basedir}/src/main/kotlin 84 | ${project.basedir}/src/main/java 85 | 86 | 87 | 88 | 89 | test-compile 90 | 91 | test-compile 92 | 93 | 94 | 95 | ${project.basedir}/src/test/kotlin 96 | ${project.basedir}/src/test/java 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-compiler-plugin 105 | 3.6.1 106 | 107 | 1.8 108 | 1.8 109 | 110 | 111 | 112 | 113 | default-compile 114 | none 115 | 116 | 117 | 118 | default-testCompile 119 | none 120 | 121 | 122 | java-compile 123 | compile 124 | 125 | compile 126 | 127 | 128 | 129 | java-test-compile 130 | test-compile 131 | 132 | testCompile 133 | 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-source-plugin 140 | 3.2.1 141 | 142 | 143 | package 144 | 145 | jar-no-fork 146 | 147 | 148 | 149 | 150 | 151 | 152 | org.jetbrains.dokka 153 | dokka-maven-plugin 154 | ${dokka.version} 155 | 156 | 157 | pre-site 158 | 159 | javadocJar 160 | 161 | 162 | 163 | 164 | 165 | src/main/kotlin 166 | 167 | 168 | 169 | 170 | org.apache.maven.plugins 171 | maven-javadoc-plugin 172 | 3.2.0 173 | 174 | 175 | package 176 | 177 | jar 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | jcenter 188 | JCenter 189 | https://jcenter.bintray.com/ 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /wechaty-puppet-hostie/src/main/kotlin/io/github/wechaty/utils/FutureUtils.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.utils; 2 | 3 | import java.util.concurrent.CompletableFuture 4 | import java.util.concurrent.ExecutionException 5 | import java.util.concurrent.Future 6 | import java.util.stream.Collectors 7 | 8 | /** 9 | * @author Zhengxin 10 | */ 11 | object FutureUtils { 12 | 13 | fun toCompletable(future: Future): CompletableFuture { 14 | return CompletableFuture.supplyAsync { 15 | try { 16 | return@supplyAsync future.get() 17 | } catch (e: InterruptedException) { 18 | throw RuntimeException(e) 19 | } catch (e: ExecutionException) { 20 | throw RuntimeException(e) 21 | } 22 | } 23 | } 24 | 25 | fun sequence(futures: List>): CompletableFuture> { 26 | val allDoneFuture = 27 | CompletableFuture.allOf(*futures.toTypedArray>()) 28 | return allDoneFuture.thenApply { v: Void? -> 29 | futures.stream().map { obj: CompletableFuture -> obj.join() }.collect(Collectors.toList()) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /wechaty-puppet-mock/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | wechaty-parent 7 | io.github.wechaty 8 | 0.1.5-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | wechaty-puppet-mock 13 | 14 | 15 | 16 | org.jetbrains.kotlin 17 | kotlin-stdlib 18 | 19 | 20 | io.github.wechaty 21 | wechaty-puppet 22 | ${project.version} 23 | 24 | 25 | org.mockito 26 | mockito-core 27 | 28 | 29 | junit 30 | junit 31 | 32 | 33 | com.github.javafaker 34 | javafaker 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.jetbrains.kotlin 43 | kotlin-maven-plugin 44 | ${kotlin.version} 45 | 46 | 47 | compile 48 | 49 | compile 50 | 51 | 52 | 53 | ${project.basedir}/src/main/kotlin 54 | src/main/kotlin 55 | 56 | 57 | 58 | 59 | test-compile 60 | 61 | test-compile 62 | 63 | 64 | 65 | ${project.basedir}/src/test/kotlin 66 | ${project.basedir}/src/test/java 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 3.6.1 76 | 77 | 1.8 78 | 1.8 79 | 80 | 81 | 82 | 83 | default-compile 84 | none 85 | 86 | 87 | 88 | default-testCompile 89 | none 90 | 91 | 92 | java-compile 93 | compile 94 | 95 | compile 96 | 97 | 98 | 99 | java-test-compile 100 | test-compile 101 | 102 | testCompile 103 | 104 | 105 | 106 | 107 | 108 | org.apache.maven.plugins 109 | maven-source-plugin 110 | 3.2.1 111 | 112 | 113 | package 114 | 115 | jar-no-fork 116 | 117 | 118 | 119 | 120 | 121 | 122 | org.jetbrains.dokka 123 | dokka-maven-plugin 124 | ${dokka.version} 125 | 126 | 127 | pre-site 128 | 129 | javadocJar 130 | 131 | 132 | 133 | 134 | 135 | src/main/kotlin 136 | 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-javadoc-plugin 142 | 3.2.0 143 | 144 | 145 | package 146 | 147 | jar 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | jcenter 158 | JCenter 159 | https://jcenter.bintray.com/ 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /wechaty-puppet-mock/src/main/kotlin/io/github/wechaty/MockData.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty 2 | 3 | import com.github.javafaker.Faker 4 | import io.github.wechaty.filebox.FileBox 5 | import io.github.wechaty.schemas.* 6 | import java.util.* 7 | 8 | /** 9 | * Data for mock 10 | * @author renxiaoya 11 | * @date 2020-06-01 12 | **/ 13 | class MockData { 14 | 15 | companion object { 16 | private val faker = Faker() 17 | 18 | fun getFakeContactPayload(): ContactPayload { 19 | val contactPayload = ContactPayload(UUID.randomUUID().toString()) 20 | contactPayload.address = faker.address().streetAddress() 21 | contactPayload.avatar = faker.avatar().toString() 22 | contactPayload.city = faker.address().city() 23 | contactPayload.friend = true 24 | contactPayload.gender = ContactGender.Male 25 | contactPayload.name = faker.name().firstName() 26 | contactPayload.province = faker.address().state() 27 | contactPayload.signature = faker.lorem().sentence() 28 | contactPayload.star = false 29 | contactPayload.type = ContactType.Personal 30 | return contactPayload 31 | } 32 | 33 | fun getFakeImageFileBox(): FileBox { 34 | return FileBox.fromUrl(faker.avatar().image(), null) 35 | } 36 | 37 | fun getMessagePayload(fromId: String, toId: String): MessagePayload { 38 | val messagePayload = getFakeMessagePayload() 39 | messagePayload.fromId = fromId 40 | messagePayload.toId = toId 41 | return messagePayload 42 | } 43 | 44 | fun getFakeMessagePayload(): MessagePayload { 45 | val messagePayload = MessagePayload(UUID.randomUUID().toString()) 46 | messagePayload.fromId = UUID.randomUUID().toString() 47 | messagePayload.mentionIdList = listOf() 48 | messagePayload.text = faker.lorem().sentence() 49 | messagePayload.timestamp = Date().time 50 | messagePayload.toId = UUID.randomUUID().toString() 51 | messagePayload.type = MessageType.Text 52 | messagePayload.roomId = "${UUID.randomUUID().toString()}@chatroom" 53 | messagePayload.filename = faker.file().fileName() 54 | return messagePayload 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /wechaty-puppet-mock/src/main/kotlin/io/github/wechaty/config.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty 2 | 3 | import io.github.wechaty.filebox.FileBox 4 | 5 | /** 6 | * config for mock 7 | * @author renxiaoya 8 | * @date 2020-06-02 9 | **/ 10 | const val CHATIE_OFFICIAL_ACCOUNT_QRCODE = "http://weixin.qq.com/r/qymXj7DEO_1ErfTs93y5" 11 | 12 | fun qrCodeForChatie(): FileBox { 13 | return FileBox.fromQRCode(CHATIE_OFFICIAL_ACCOUNT_QRCODE) 14 | } 15 | -------------------------------------------------------------------------------- /wechaty-puppet-mock/src/main/kotlin/io/github/wechaty/utils/MockitoHelper.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.utils 2 | 3 | import org.mockito.Mockito 4 | 5 | /** 6 | * A helper for Mockito 7 | * 8 | * @author renxiaoya 9 | * @date 2020-05-29 10 | **/ 11 | object MockitoHelper { 12 | fun anyObject(): T { 13 | Mockito.any() 14 | return uninitialized() 15 | } 16 | 17 | @Suppress("UNCHECKED_CAST") 18 | fun uninitialized(): T = null as T 19 | } 20 | -------------------------------------------------------------------------------- /wechaty-puppet-mock/src/main/resources/image/mock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechaty/java-wechaty/65ca2e43d4978e1fac92d32c243ccf3b6df4a886/wechaty-puppet-mock/src/main/resources/image/mock.png -------------------------------------------------------------------------------- /wechaty-puppet-mock/src/main/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline 2 | -------------------------------------------------------------------------------- /wechaty-puppet/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | io.github.wechaty 6 | wechaty-parent 7 | 0.1.5-SNAPSHOT 8 | 9 | 4.0.0 10 | wechaty-puppet 11 | jar 12 | 13 | 14 | true 15 | 0.10.1 16 | 17 | 18 | 19 | 20 | 21 | org.jetbrains.kotlin 22 | kotlin-stdlib 23 | 24 | 25 | 26 | 27 | com.github.ben-manes.caffeine 28 | caffeine 29 | 30 | 31 | 32 | 33 | org.apache.commons 34 | commons-collections4 35 | 36 | 37 | 38 | 39 | org.apache.commons 40 | commons-lang3 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.slf4j 61 | slf4j-api 62 | 63 | 64 | 65 | 66 | com.google.guava 67 | guava 68 | 69 | 70 | 71 | 72 | com.fasterxml.jackson.core 73 | jackson-core 74 | 75 | 76 | 77 | 78 | com.fasterxml.jackson.core 79 | jackson-databind 80 | 81 | 82 | 83 | com.fasterxml.jackson.module 84 | jackson-module-kotlin 85 | 86 | 87 | 88 | com.google.zxing 89 | core 90 | 91 | 92 | 93 | 94 | commons-io 95 | commons-io 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | com.squareup.okhttp3 105 | okhttp 106 | 107 | 108 | org.hamcrest 109 | hamcrest-core 110 | 111 | 112 | org.mockito 113 | mockito-core 114 | test 115 | 116 | 117 | junit 118 | junit 119 | test 120 | 121 | 122 | 123 | 124 | 125 | https://github.com/wechaty/java-wechaty 126 | scm:git:ssh://github.com/wechaty/java-wechaty 127 | scm:git:ssh://git@github.com/wechaty/java-wechaty.git 128 | HEAD 129 | 130 | 131 | 132 | 133 | 134 | 135 | org.jetbrains.kotlin 136 | kotlin-maven-plugin 137 | ${kotlin.version} 138 | 139 | 140 | compile 141 | compile 142 | 143 | 144 | ${project.basedir}/src/main/kotlin 145 | ${project.basedir}/src/main/java 146 | 147 | 148 | 149 | 150 | test-compile 151 | test-compile 152 | 153 | 154 | ${project.basedir}/src/test/kotlin 155 | ${project.basedir}/src/test/java 156 | 157 | 158 | 159 | 160 | 161 | 162 | org.apache.maven.plugins 163 | maven-compiler-plugin 164 | 3.6.1 165 | 166 | 1.8 167 | 1.8 168 | 169 | 170 | 171 | 172 | 173 | default-compile 174 | none 175 | 176 | 177 | 178 | default-testCompile 179 | none 180 | 181 | 182 | java-compile 183 | compile 184 | compile 185 | 186 | 187 | java-test-compile 188 | test-compile 189 | testCompile 190 | 191 | 192 | 193 | 194 | org.apache.maven.plugins 195 | maven-source-plugin 196 | 3.2.1 197 | 198 | 199 | package 200 | 201 | jar-no-fork 202 | 203 | 204 | 205 | 206 | 207 | 208 | org.jetbrains.dokka 209 | dokka-maven-plugin 210 | ${dokka.version} 211 | 212 | 213 | 214 | javadocJar 215 | 216 | 217 | 218 | 219 | 220 | src/main/kotlin 221 | 222 | 223 | 224 | 225 | org.apache.maven.plugins 226 | maven-javadoc-plugin 227 | 3.2.0 228 | 229 | 230 | package 231 | 232 | jar 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | jcenter 243 | JCenter 244 | https://jcenter.bintray.com/ 245 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/StateEnum.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty 2 | 3 | enum class StateEnum { 4 | 5 | PENDING,ON,OFF; 6 | 7 | } -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/ThrowUnsupportedError.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty 2 | 3 | fun throwUnsupportedError(){ 4 | throw Exception( 5 | listOf( 6 | "Wechaty Puppet Unsupported API Error.", 7 | " ", 8 | "Learn More At https://github.com/wechaty/wechaty-puppet/wiki/Compatibility" 9 | ).toString() 10 | ) 11 | } -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/eventEmitter/EventEmitter.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.eventEmitter 2 | 3 | import com.google.common.collect.ArrayListMultimap 4 | import com.google.common.collect.Lists 5 | import com.google.common.collect.Multimaps 6 | import io.github.wechaty.schemas.EventHeartbeatPayload 7 | import org.apache.commons.collections4.CollectionUtils 8 | import org.slf4j.LoggerFactory 9 | import java.util.concurrent.Executor 10 | import java.util.concurrent.Executors 11 | import java.util.concurrent.locks.ReentrantLock 12 | 13 | open class EventEmitter : EventEmitterInterface { 14 | 15 | private var maxListeners: Int = 0; 16 | 17 | private val executor: Executor 18 | 19 | private val lock = ReentrantLock() 20 | 21 | constructor(executor: Executor) { 22 | this.executor = executor 23 | } 24 | 25 | constructor() { 26 | val i = Runtime.getRuntime().availableProcessors() * 2 27 | this.executor = Executors.newFixedThreadPool(i) 28 | } 29 | 30 | 31 | private val map = Multimaps.synchronizedListMultimap(ArrayListMultimap.create()) 32 | 33 | override fun addListener(event:Event, vararg listeners: Listener) { 34 | listeners.forEach { 35 | map.put(event, it) 36 | } 37 | } 38 | 39 | override fun emit(event:Event, vararg any: Any) { 40 | val tolist: List? 41 | val list = map.get(event) 42 | if (CollectionUtils.isEmpty(list)) { 43 | log.debug("this eventName:${event} has no listener") 44 | return 45 | } 46 | tolist = list.toList() 47 | tolist.forEach { 48 | executor.execute { 49 | it.handler(*any) 50 | } 51 | } 52 | } 53 | 54 | override fun eventNames(): List { 55 | val keySet = map.keySet() 56 | return Lists.newArrayList(keySet) 57 | } 58 | 59 | override fun getMaxListeners(): Int { 60 | return maxListeners 61 | } 62 | 63 | override fun listenerCount(event:Event): Int { 64 | return map.get(event).size 65 | } 66 | 67 | override fun listeners(event:Event): List { 68 | return map.get(event) 69 | } 70 | 71 | override fun on(event:Event, listener: Listener) { 72 | map.put(event, listener) 73 | } 74 | 75 | /** 76 | * can not work well on multithreading 77 | */ 78 | override fun once(event:Event, listener: Listener) { 79 | val wrapListener = object : Listener { 80 | override fun handler(vararg any: Any) { 81 | removeListener(event, this) 82 | listener.handler(*any) 83 | } 84 | } 85 | 86 | map.put(event, wrapListener) 87 | 88 | } 89 | 90 | override fun removeAllListeners(event:Event): Boolean { 91 | map.removeAll(event) 92 | return true 93 | 94 | 95 | } 96 | 97 | override fun removeListener(event:Event, listener: Listener): Boolean { 98 | return map.remove(event, listener) 99 | } 100 | 101 | override fun clean() { 102 | map.clear() 103 | } 104 | 105 | override fun setMaxListeners(max: Int) { 106 | maxListeners = max 107 | } 108 | 109 | override fun len(): Int { 110 | return map.size() 111 | } 112 | 113 | companion object { 114 | private val log = LoggerFactory.getLogger(EventEmitter::class.java) 115 | } 116 | 117 | 118 | } 119 | 120 | interface EventEmitterInterface { 121 | 122 | // AddListener is an alias for .On(eventName, listener). 123 | fun addListener(event:Event, vararg listeners: Listener) 124 | 125 | // Emit fires a particular event, 126 | // Synchronously calls each of the listeners registered for the event named 127 | // eventName, in the order they were registered, 128 | // passing the supplied arguments to each. 129 | fun emit(event:Event, vararg any: Any) 130 | 131 | // EventNames returns an array listing the events for which the emitter has registered listeners. 132 | // The values in the array will be strings. 133 | fun eventNames(): List 134 | 135 | // GetMaxListeners returns the max listeners for this emitter 136 | // see SetMaxListeners 137 | fun getMaxListeners(): Int 138 | // ListenerCount returns the length of all registered listeners to a particular event 139 | 140 | fun listenerCount(event:Event): Int 141 | 142 | // Listeners returns a copy of the array of listeners for the event named eventName. 143 | fun listeners(event:Event): List 144 | 145 | // On registers a particular listener for an event, func receiver parameter(s) is/are optional 146 | fun on(event:Event, listener: Listener) 147 | 148 | // Once adds a one time listener function for the event named eventName. 149 | // The next time eventName is triggered, this listener is removed and then invoked. 150 | fun once(event:Event, listener: Listener) 151 | 152 | // RemoveAllListeners removes all listeners, or those of the specified eventName. 153 | // Note that it will remove the event itself. 154 | // Returns an indicator if event and listeners were found before the remove. 155 | fun removeAllListeners(event:Event): Boolean 156 | 157 | // RemoveListener removes given listener from the event named eventName. 158 | // Returns an indicator whether listener was removed 159 | fun removeListener(event:Event, listener: Listener): Boolean 160 | 161 | // Clear removes all events and all listeners, restores Events to an empty value 162 | fun clean() 163 | 164 | // SetMaxListeners obviously this function allows the MaxListeners 165 | // to be decrease or increase. Set to zero for unlimited 166 | fun setMaxListeners(max: Int) 167 | 168 | // Len returns the length of all registered events 169 | fun len(): Int 170 | 171 | } 172 | 173 | 174 | interface Listener { 175 | 176 | fun handler(vararg any: Any) 177 | } 178 | 179 | interface Event 180 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/filebox/FileBox.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.filebox 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.github.wechaty.utils.JsonUtils 5 | import okhttp3.OkHttpClient 6 | import okhttp3.Request 7 | import org.apache.commons.collections4.CollectionUtils 8 | import org.apache.commons.io.FileUtils 9 | import org.apache.commons.io.FilenameUtils 10 | import org.apache.commons.lang3.StringUtils 11 | import java.io.File 12 | import java.net.URL 13 | import java.nio.file.Files 14 | import java.nio.file.Path 15 | import java.util.concurrent.CompletableFuture 16 | import java.util.concurrent.Future 17 | 18 | 19 | class FileBox(options: FileBoxOptions) { 20 | 21 | @JsonProperty 22 | var mimeType: String? = null 23 | 24 | @JsonProperty 25 | var base64: String? = null 26 | 27 | @JsonProperty 28 | var remoteUrl: String? = null 29 | 30 | @JsonProperty 31 | var qrCode: String? = null 32 | 33 | @JsonProperty 34 | var buffer: ByteArray? = null 35 | var localPath: String? = null 36 | 37 | @JsonProperty 38 | var headers: OutgoingHttpHeaders? = null 39 | 40 | @JsonProperty 41 | var name: String? = null 42 | 43 | @JsonProperty 44 | var metadata: Metadata? = null 45 | 46 | @JsonProperty 47 | var boxType: FileBoxType 48 | 49 | private val client: OkHttpClient = OkHttpClient() 50 | 51 | init { 52 | when (options) { 53 | is FileBoxOptionsBuffer -> { 54 | this.name = options.name 55 | this.boxType = options.type 56 | this.buffer = options.buffer 57 | } 58 | 59 | is FileBoxOptionsFile -> { 60 | this.name = options.name 61 | this.boxType = options.type 62 | this.localPath = options.path 63 | } 64 | 65 | is FileBoxOptionsUrl -> { 66 | this.name = options.name 67 | this.boxType = options.type 68 | this.remoteUrl = options.url 69 | this.headers = options.headers 70 | } 71 | 72 | is FileBoxOptionsStream -> { 73 | this.name = options.name 74 | this.boxType = options.type 75 | } 76 | 77 | is FileBoxOptionsQRCode -> { 78 | this.name = options.name 79 | this.boxType = options.type 80 | this.qrCode = options.qrCode 81 | } 82 | 83 | is FileBoxOptionsBase64 -> { 84 | this.name = options.name 85 | this.boxType = options.type 86 | this.base64 = options.base64 87 | } 88 | } 89 | } 90 | 91 | fun type(): FileBoxType { 92 | return this.boxType 93 | } 94 | 95 | fun ready(): Future { 96 | if (this.boxType == FileBoxType.Url) { 97 | 98 | } 99 | 100 | return CompletableFuture.completedFuture(null); 101 | } 102 | 103 | fun syncRemoteName(): Future { 104 | 105 | val httpHeadHeader = httpHeadHeader(this.remoteUrl!!) 106 | 107 | val path: Path = File(localPath!!).toPath() 108 | val mimeType = Files.probeContentType(path) 109 | 110 | val get = httpHeadHeader["content-type"] 111 | 112 | when { 113 | CollectionUtils.isNotEmpty(get) -> { 114 | this.mimeType = get?.get(0) 115 | } 116 | StringUtils.isNotEmpty(mimeType) -> { 117 | this.mimeType = mimeType 118 | } 119 | else -> { 120 | this.mimeType = null 121 | } 122 | } 123 | 124 | return CompletableFuture.completedFuture(null); 125 | 126 | } 127 | 128 | private fun httpHeadHeader(url: String): Map> { 129 | 130 | val request: Request = Request.Builder() 131 | .url(url) 132 | .build() 133 | 134 | client.newCall(request).execute().use { response -> 135 | val headers = response.headers 136 | return headers.toMultimap() 137 | } 138 | } 139 | 140 | fun toJsonString(): String { 141 | buffer = getBufferByte(this) 142 | 143 | return JsonUtils.write(this) 144 | 145 | } 146 | 147 | private fun getBufferByte(fileBox: FileBox): ByteArray? { 148 | when (fileBox.type()) { 149 | FileBoxType.File -> { 150 | 151 | val file = File(ClassLoader.getSystemResource("dong.jpg").path) 152 | 153 | return FileUtils.readFileToByteArray(file) 154 | 155 | } 156 | FileBoxType.Url -> { 157 | return null; 158 | } 159 | FileBoxType.Base64 -> { 160 | return null; 161 | } 162 | else -> { 163 | TODO() 164 | } 165 | 166 | } 167 | } 168 | 169 | companion object { 170 | 171 | @JvmStatic 172 | fun fromFile(path: String, name: String): FileBox { 173 | var localname = name 174 | 175 | if (StringUtils.isEmpty(name)) { 176 | localname = FilenameUtils.getBaseName(path) 177 | } 178 | 179 | val fileBoxOptionsFile = FileBoxOptionsFile(path = path, name = localname) 180 | return FileBox(fileBoxOptionsFile) 181 | 182 | } 183 | 184 | @JvmStatic 185 | fun fromJson(obj: String): FileBox { 186 | 187 | // return JsonUtils.readValue(obj) 188 | 189 | val jsonNode = JsonUtils.mapper.readTree(obj) 190 | 191 | var fileBox: FileBox 192 | 193 | val type = jsonNode.findValue("boxType").asInt() 194 | 195 | when (type) { 196 | 197 | FileBoxType.Base64.code -> { 198 | fileBox = fromBase64( 199 | jsonNode.findValue("base64").asText(), 200 | jsonNode.findValue("name").asText() 201 | ) 202 | } 203 | 204 | FileBoxType.Url.code -> { 205 | fileBox = fromUrl( 206 | jsonNode.findValue("remoteUrl").asText(), 207 | jsonNode.findValue("name").asText() 208 | ) 209 | } 210 | 211 | FileBoxType.QRcode.code -> { 212 | fileBox = fromQRCode( 213 | jsonNode.findValue("qrCode").asText() 214 | ) 215 | } 216 | else -> { 217 | throw Exception("unknown filebox json object{type} $jsonNode") 218 | } 219 | } 220 | 221 | // fileBox.metadata = jsonNode.get("metadata") 222 | return fileBox; 223 | } 224 | 225 | @JvmStatic 226 | fun fromBase64(base64: String, name: String): FileBox { 227 | val options = FileBoxOptionsBase64(base64 = base64, name = name) 228 | return FileBox(options) 229 | } 230 | 231 | @JvmStatic 232 | fun fromDataUrl(dataUrl: String, name: String): FileBox { 233 | val base64 = dataUrlToBase64(dataUrl); 234 | return fromBase64(base64, name) 235 | } 236 | 237 | @JvmStatic 238 | fun fromQRCode(qrCode: String): FileBox { 239 | val optionsQRCode = FileBoxOptionsQRCode(name = "qrcode.png", qrCode = qrCode) 240 | return FileBox(optionsQRCode) 241 | } 242 | 243 | @JvmStatic 244 | fun fromUrl(url: String, name: String?, headers: OutgoingHttpHeaders? = null): FileBox { 245 | 246 | var localName: String? = name 247 | 248 | if (StringUtils.isEmpty(url)) { 249 | val parsedUrl = URL(url) 250 | localName = parsedUrl.path 251 | } 252 | 253 | val optionsUrl = FileBoxOptionsUrl(name = localName, url = url) 254 | optionsUrl.headers = headers 255 | return FileBox(optionsUrl) 256 | 257 | } 258 | 259 | 260 | /** 261 | * ????? 262 | */ 263 | fun dataUrlToBase64(dataUrl: String): String { 264 | val split = StringUtils.split(dataUrl, ",") 265 | return split[split.size - 1] 266 | } 267 | 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/filebox/FileBoxType.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.filebox 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue 4 | 5 | 6 | enum class FileBoxType(val code: Int) { 7 | 8 | Unknown(0), 9 | 10 | Base64(1), 11 | Url(2), 12 | QRcode(3), 13 | 14 | Buffer(4), 15 | File(5), 16 | Stream(6); 17 | 18 | @JsonValue 19 | fun code(): Int { 20 | return code 21 | } 22 | 23 | } 24 | 25 | 26 | data class FileBoxOptionsFile(val type: FileBoxType = FileBoxType.File, var path: String, var name: String) : FileBoxOptions() { 27 | override fun toString(): String { 28 | return "FileBoxOptionsFile(type=$type, path='$path')" 29 | } 30 | } 31 | 32 | data class FileBoxOptionsUrl(val type: FileBoxType = FileBoxType.Url, var url: String, var name: String?) : FileBoxOptions() { 33 | var headers: OutgoingHttpHeaders? = mutableMapOf() 34 | override fun toString(): String { 35 | return "FileBoxOptionsUrl(type=$type, path='$url', headrs=$headers)" 36 | } 37 | } 38 | 39 | data class FileBoxOptionsBuffer(var type: FileBoxType = FileBoxType.Buffer, var buffer: ByteArray, var name: String) : FileBoxOptions() { 40 | override fun toString(): String { 41 | return "FileBoxOptionsBuffer(type=$type, buffer=$buffer)" 42 | } 43 | 44 | } 45 | 46 | data class FileBoxOptionsBase64(var type: FileBoxType = FileBoxType.Base64, var base64: String, var name: String) : FileBoxOptions() { 47 | 48 | 49 | } 50 | 51 | data class FileBoxOptionsStream(val name: String, val type: FileBoxType = FileBoxType.Stream, val stream: Readable) : FileBoxOptions() 52 | 53 | 54 | data class FileBoxOptionsQRCode(val type: FileBoxType = FileBoxType.QRcode, val qrCode: String, var name: String) : FileBoxOptions() { 55 | 56 | } 57 | 58 | data class FileBoxOptionsJsonObjectCommon( 59 | var name: String, 60 | var metadata: Metadata, 61 | var type: Int 62 | ) 63 | 64 | data class FileBoxOptionsJsonObjectBase64( 65 | val type: FileBoxType = FileBoxType.Base64, 66 | val base64: String, 67 | val name: String, 68 | val metadata: Metadata 69 | ) : FileBoxJsonObject() 70 | 71 | 72 | data class FileBoxJsonObjectUrl( 73 | val type: FileBoxType = FileBoxType.Url, 74 | val remoteUrl: String, 75 | var name: String, 76 | val metadata: Metadata 77 | ) : FileBoxJsonObject() { 78 | var headers: OutgoingHttpHeaders? = mutableMapOf() 79 | } 80 | 81 | data class FileBoxJsonObjectQRCode( 82 | val type: FileBoxType = FileBoxType.QRcode, 83 | val qrCode: String, 84 | var name: String, 85 | val metadata: Metadata 86 | ) : FileBoxJsonObject() 87 | 88 | sealed class FileBoxJsonObject 89 | 90 | sealed class FileBoxOptions 91 | 92 | typealias Metadata = Map 93 | 94 | typealias OutgoingHttpHeaders = Map> 95 | 96 | 97 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/listener/Listener.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.listener 2 | 3 | import io.github.wechaty.schemas.* 4 | 5 | @FunctionalInterface 6 | interface PuppetDongListener{ 7 | fun handler(payload: EventDongPayload) 8 | 9 | // override fun handler0(vararg any: Any) { 10 | // handler(any[0] as EventDongPayload) 11 | // } 12 | } 13 | 14 | @FunctionalInterface 15 | interface PuppetErrorListener{ 16 | fun handler(payload: EventErrorPayload) 17 | // override fun handler0(vararg any: Any) { 18 | // handler(any[0] as EventErrorPayload) 19 | // } 20 | 21 | } 22 | 23 | @FunctionalInterface 24 | interface PuppetFriendshipListener{ 25 | fun handler(payload: EventFriendshipPayload) 26 | // override fun handler0(vararg any: Any) { 27 | // handler(any[0] as EventFriendshipPayload) 28 | // } 29 | } 30 | 31 | @FunctionalInterface 32 | interface PuppetLoginListener{ 33 | fun handler(payload: EventLoginPayload) 34 | // override fun handler0(vararg any: Any) { 35 | // handler(any[0] as EventLoginPayload) 36 | // } 37 | } 38 | 39 | @FunctionalInterface 40 | interface PuppetLogoutListener{ 41 | fun handler(payload: EventLogoutPayload) 42 | // override fun handler0(vararg any: Any) { 43 | // handler(any[0] as EventLogoutPayload) 44 | // } 45 | } 46 | 47 | @FunctionalInterface 48 | interface PuppetMessageListener{ 49 | fun handler(payload: EventMessagePayload) 50 | // override fun handler0(vararg any: Any) { 51 | // handler(any[0] as EventMessagePayload) 52 | // } 53 | } 54 | 55 | @FunctionalInterface 56 | interface PuppetResetListener{ 57 | fun handler(payload: EventResetPayload) 58 | // override fun handler0(vararg any: Any) { 59 | // handler(any[0] as EventResetPayload) 60 | // } 61 | } 62 | 63 | @FunctionalInterface 64 | interface PuppetRoomJoinListener{ 65 | fun handler(payload: EventRoomJoinPayload) 66 | // override fun handler0(vararg any: Any) { 67 | // handler(any[0] as EventRoomJoinPayload) 68 | // } 69 | } 70 | 71 | @FunctionalInterface 72 | interface PuppetRoomLeaveListener{ 73 | fun handler(payload: EventRoomLeavePayload) 74 | // override fun handler0(vararg any: Any) { 75 | // handler(any[0] as EventRoomLeavePayload) 76 | // } 77 | } 78 | 79 | @FunctionalInterface 80 | interface PuppetRoomTopicListener{ 81 | fun handler(payload: EventRoomTopicPayload) 82 | // override fun handler0(vararg any: Any) { 83 | // handler(any[0] as EventRoomTopicPayload) 84 | // } 85 | } 86 | 87 | @FunctionalInterface 88 | interface PuppetRoomInviteListener{ 89 | fun handler(payload: EventRoomInvitePayload) 90 | // override fun handler0(vararg any: Any) { 91 | // handler(any[0] as EventRoomInvitePayload) 92 | // } 93 | } 94 | 95 | @FunctionalInterface 96 | interface PuppetScanListener{ 97 | fun handler(payload: EventScanPayload) 98 | // override fun handler0(vararg any: Any) { 99 | // handler(any[0] as EventScanPayload) 100 | // } 101 | 102 | } 103 | 104 | 105 | @FunctionalInterface 106 | interface PuppetReadyListener{ 107 | fun handler(payload: EventReadyPayload) 108 | // override fun handler0(vararg any: Any) { 109 | // handler(any[0] as EventReadyPayload) 110 | // } 111 | } 112 | 113 | @FunctionalInterface 114 | interface PuppetHeartbeatListener{ 115 | fun handler(payload: EventHeartbeatPayload) 116 | // override fun handler0(vararg any: Any) { 117 | // handler(any[0] as EventHeartbeatPayload) 118 | // } 119 | } 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/MemoryCard.kt: -------------------------------------------------------------------------------- 1 | //package io.github.wechaty.memorycard 2 | // 3 | //import org.slf4j.LoggerFactory 4 | //import java.util.concurrent.Future 5 | // 6 | //class MemoryCard{ 7 | // 8 | // private var name:String? = null 9 | // protected var parent:MemoryCard? = null 10 | // protected var payload:MemoryCardPayload ? = null 11 | // protected var storage:StorageBackend? = null 12 | // protected val multiplexNameList = mutableListOf() 13 | // 14 | // private var options:MemoryCardOptions 15 | // 16 | // constructor(name: String?=null,options:MemoryCardOptions? = null){ 17 | // 18 | // val _optiones:MemoryCardOptions = options ?: MemoryCardOptions() 19 | // 20 | // if(name != null){ 21 | // if(options != null) { 22 | // _optiones.name = name 23 | // } 24 | // } 25 | // this.options = _optiones 26 | // this.name = _optiones.name 27 | // 28 | // (_optiones.multiplex != null).let { 29 | // this.parent = _optiones.multiplex!!.parent 30 | // this.payload = this.parent!!.payload 31 | // this.multiplexNameList.addAll(parent!!.multiplexNameList) 32 | // this.multiplexNameList.add(_optiones.multiplex!!.name) 33 | // this.storage = null 34 | // } 35 | // 36 | // (_optiones.multiplex == null).let { 37 | // this.payload = null 38 | // this.multiplexNameList.clear() 39 | // } 40 | // } 41 | // 42 | // private fun getStore():StorageBackend?{ 43 | // log.debug("getStorage() for storage type: %s'",this.options) 44 | // 45 | // return StorageBackend.getStorage( 46 | // this.options.name!!, 47 | // this.options.storageOptions 48 | // ) 49 | // } 50 | // 51 | // fun load(){ 52 | // this.payload = this.storage!!.load() 53 | // } 54 | // 55 | // fun save(){ 56 | // this.storage!!.save(this.payload!!) 57 | // } 58 | // 59 | // companion object{ 60 | // private val log = LoggerFactory.getLogger(MemoryCard::class.java) 61 | // 62 | // fun multiplex(memory:MemoryCard,name:String):MemoryCard{ 63 | // return MemoryCard() 64 | // } 65 | // } 66 | // 67 | // 68 | //} 69 | // 70 | //class MemoryCardPayload{ 71 | // val map:Map = HashMap() 72 | //} 73 | // 74 | //data class Multiplex( 75 | // val parent:MemoryCard, 76 | // val name:String 77 | //) 78 | // 79 | //class MemoryCardOptions{ 80 | // 81 | // var name:String? = null 82 | // var storageOptions:StorageBackendOptions? = null 83 | // var multiplex:Multiplex? = null 84 | // 85 | //} 86 | // 87 | // 88 | // 89 | // 90 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StorageFile.kt: -------------------------------------------------------------------------------- 1 | //package io.github.wechaty.memorycard 2 | // 3 | //import io.github.wechaty.utils.JsonUtils 4 | //import org.apache.commons.io.FileUtils 5 | //import org.apache.commons.io.FilenameUtils 6 | //import org.apache.commons.lang3.StringUtils 7 | //import java.io.File 8 | // 9 | //class StorageFile(val name: String, var options: StorageBackendOptions) : StorageBackend(name,options) { 10 | // 11 | // private var absFileName:String 12 | // 13 | // init { 14 | // options.type = "file" 15 | // options = options as StorageFileOptions 16 | // val file = File(name) 17 | // if(file.isAbsolute){ 18 | // this.absFileName = name 19 | // }else{ 20 | // this.absFileName = FilenameUtils.concat(System.getProperty("user.dir"),name) 21 | // } 22 | // 23 | // if(!StringUtils.endsWith(this.absFileName,".memory-card.json")){ 24 | // this.absFileName += ".memory-card.json" 25 | // } 26 | // 27 | // } 28 | // 29 | // override fun save(payload: MemoryCardPayload) { 30 | // val text = JsonUtils.write(payload) 31 | // val file = File(absFileName) 32 | // FileUtils.write(file,text,"UTF-8") 33 | // 34 | // } 35 | // 36 | // override fun load(): MemoryCardPayload { 37 | // val file = File(absFileName) 38 | // if(!file.exists()){ 39 | // return MemoryCardPayload() 40 | // } 41 | // val text = FileUtils.readFileToString(file, "UTF-8") 42 | // return JsonUtils.readValue(text); 43 | // 44 | // } 45 | // 46 | // override fun destory() { 47 | // TODO("Not yet implemented") 48 | // } 49 | // 50 | //} 51 | // 52 | // 53 | // 54 | //fun main(){ 55 | // 56 | // StorageFile("test",StorageFileOptions()) 57 | // 58 | //} 59 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StorageOptions.kt: -------------------------------------------------------------------------------- 1 | //package io.github.wechaty.memorycard 2 | // 3 | //sealed class StorageBackendOptions{ 4 | // var type:String?=null 5 | //} 6 | // 7 | //data class StorageS3Options( 8 | // val accessKeyId:String, 9 | // val secretAccessKey:String, 10 | // val region:String, 11 | // val bucket:String 12 | //):StorageBackendOptions() 13 | // 14 | // 15 | //data class StorageObsOptions( 16 | // val accessKeyId:String, 17 | // val secretAccessKey:String, 18 | // val server:String, 19 | // val bucket:String 20 | //):StorageBackendOptions() 21 | // 22 | //class StorageNopOptions: StorageBackendOptions() { 23 | // var placeholder: String? = null 24 | //} 25 | // 26 | //typealias StorageFileOptions = StorageNopOptions 27 | // 28 | //val BACKEND_DICT = mapOf( 29 | // "file" to StorageFile::class 30 | //) 31 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/memorycard/StrorageBackend.kt: -------------------------------------------------------------------------------- 1 | //package io.github.wechaty.memorycard 2 | // 3 | //import org.slf4j.LoggerFactory 4 | //import kotlin.reflect.full.createInstance 5 | //import kotlin.reflect.full.primaryConstructor 6 | // 7 | //abstract class StorageBackend(name: String, option: StorageBackendOptions) { 8 | // 9 | // init { 10 | // log.debug("constructor({}, { type: {} })",name,option) 11 | // } 12 | // 13 | // abstract fun save(payload: MemoryCardPayload) 14 | // abstract fun load():MemoryCardPayload 15 | // abstract fun destory() 16 | // 17 | // companion object{ 18 | // private val log = LoggerFactory.getLogger(StorageBackend::class.java) 19 | // 20 | // fun getStorage(name: String,options: StorageBackendOptions?):StorageBackend{ 21 | // 22 | // var _options = options 23 | // 24 | // if(options == null) { 25 | // _options = StorageFileOptions() 26 | // _options.type = "file" 27 | // } 28 | // 29 | // if(_options?.type == null || _options.type !in BACKEND_DICT.keys){ 30 | // throw Exception("backed unknown : ${_options?.type}") 31 | // } 32 | // 33 | // 34 | // } 35 | // 36 | // } 37 | // 38 | //} 39 | // 40 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Contact.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | 4 | enum class ContactGender(var code: Int) { 5 | Unknown(0), Male(1), Female(2); 6 | 7 | companion object { 8 | fun getByCode(code: Int): ContactGender { 9 | val values = values() 10 | for (value in values) { 11 | if (value.code == code) { 12 | return value 13 | } 14 | } 15 | return Unknown 16 | } 17 | } 18 | } 19 | 20 | enum class ContactType(var code: Int) { 21 | Unknown(0), Personal(1), Official(2); 22 | 23 | companion object { 24 | fun getByCode(code: Int): ContactType { 25 | val values = values() 26 | for (value in values) { 27 | if (value.code == code) { 28 | return value 29 | } 30 | } 31 | return Unknown 32 | } 33 | } 34 | 35 | 36 | } 37 | 38 | class ContactQueryFilter { 39 | var alias: String? = null 40 | var id: String? = null 41 | var name: String? = null 42 | var weixin: String? = null 43 | 44 | var aliasReg:Regex? = null 45 | var nameReg:Regex? = null 46 | } 47 | 48 | class ContactPayload(val id:String) { 49 | var gender: ContactGender? = null 50 | var type: ContactType? = null 51 | var name: String? = null 52 | var avatar: String? = null 53 | var address: String? = null 54 | var alias: String? = null 55 | var city: String? = null 56 | var friend: Boolean? = null 57 | var province: String? = null 58 | var signature: String? = null 59 | var star: Boolean? = null 60 | var weixin: String? = null 61 | override fun toString(): String { 62 | return "ContactPayload(id=$id, gender=$gender, type=$type, name=$name, avatar=$avatar, address=$address, alias=$alias, city=$city, friend=$friend, province=$province, signature=$signature, star=$star, weixin=$weixin)" 63 | } 64 | } 65 | 66 | typealias ContactPayloadFilterFunction = (payload:ContactPayload) -> Boolean 67 | typealias ContactPayloadFilterFactory = (query: ContactQueryFilter) -> ContactPayloadFilterFunction 68 | 69 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Event.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | enum class ScanStatus(var code: Int) { 4 | Unknown(-1), 5 | Cancel(0), 6 | Waiting(1), 7 | Scanned(2), 8 | Confirmed(3), 9 | Timeout(4); 10 | 11 | companion object { 12 | fun getByCode(code: Int): ScanStatus { 13 | val values = ScanStatus.values() 14 | for (value in values) { 15 | if (value.code == code) { 16 | return value 17 | } 18 | } 19 | return ScanStatus.Unknown 20 | } 21 | } 22 | 23 | } 24 | 25 | data class EventLoginPayload(var contactId:String) { 26 | override fun toString(): String { 27 | return "EventLoginPayload(contactId='$contactId')" 28 | } 29 | } 30 | 31 | data class EventLogoutPayload( 32 | var contactId:String, 33 | var data: String 34 | ) { 35 | override fun toString(): String { 36 | return "EventLogoutPayload(contactId='$contactId', data='$data')" 37 | } 38 | } 39 | 40 | 41 | data class EventMessagePayload(var messageId:String) { 42 | override fun toString(): String { 43 | return "EventMessagePayload(messageId='$messageId')" 44 | } 45 | } 46 | 47 | data class EventRoomInvitePayload( 48 | var roomInvitationId: String 49 | ) { 50 | override fun toString(): String { 51 | return "EventRoomInvitePayload(roomInvitationId='$roomInvitationId')" 52 | } 53 | } 54 | 55 | data class EventRoomJoinPayload( 56 | var inviteeIdList : List, 57 | var inviterId : String, 58 | var roomId : String, 59 | var timestamp : Long 60 | ) { 61 | override fun toString(): String { 62 | return "EventRoomJoinPayload(inviteeIdList=$inviteeIdList, inviterId='$inviterId', roomId='$roomId', timestamp=$timestamp)" 63 | } 64 | } 65 | 66 | data class EventRoomLeavePayload( 67 | var removeeIdList: List, 68 | var removerId:String, 69 | var roomId:String, 70 | var timestamp:Long 71 | ) { 72 | override fun toString(): String { 73 | return "EventRoomLeavePayload(removeeIdList=$removeeIdList, removerId='$removerId', roomId='$roomId', timestamp=$timestamp)" 74 | } 75 | } 76 | 77 | data class EventRoomTopicPayload( 78 | var changerId:String, 79 | var newTopic:String, 80 | var oldTopic:String, 81 | var roomId:String, 82 | var timestamp:Long 83 | ) { 84 | override fun toString(): String { 85 | return "EventRoomTopicePayload(changerId='$changerId', newTopic='$newTopic', oldTopic='$oldTopic', roomId='$roomId', timestamp=$timestamp)" 86 | } 87 | } 88 | 89 | data class EventScanPayload(var status: ScanStatus){ 90 | var qrcode:String? = null 91 | var data:String? = null 92 | override fun toString(): String { 93 | return "EventScanPayload(status=$status, qrcode=$qrcode, data=$data)" 94 | } 95 | } 96 | 97 | data class EventFriendshipPayload( 98 | var friendshipId: String 99 | ) { 100 | override fun toString(): String { 101 | return "EventFriendshipPayload(friendshipId='$friendshipId')" 102 | } 103 | } 104 | 105 | data class EventDongPayload( 106 | var data:String 107 | ) { 108 | override fun toString(): String { 109 | return "EventDongPayload(data='$data')" 110 | } 111 | } 112 | 113 | data class EventErrorPayload( 114 | var data:String 115 | ) { 116 | override fun toString(): String { 117 | return "EventErrorPayload(data='$data')" 118 | } 119 | } 120 | 121 | data class EventReadyPayload( 122 | var data:String 123 | ) { 124 | override fun toString(): String { 125 | return "EventReadyPayload(data='$data')" 126 | } 127 | } 128 | 129 | data class EventResetPayload( 130 | var data :String 131 | ) { 132 | override fun toString(): String { 133 | return "EventResetPayload(data='$data')" 134 | } 135 | } 136 | 137 | data class EventHeartbeatPayload( 138 | var data:String, 139 | var timeout:Long 140 | ) { 141 | override fun toString(): String { 142 | return "EventHeartbeatPayload(data='$data')" 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/EventEnum.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.io.github.wechaty.schemas 2 | 3 | import io.github.wechaty.eventEmitter.Event 4 | 5 | enum class EventEnum :Event{ 6 | 7 | START, 8 | 9 | FRIENDSHIP, 10 | 11 | LOGIN, 12 | 13 | LOGOUT, 14 | 15 | MESSAGE, 16 | 17 | ROOM_INVITE, 18 | 19 | INVITE, 20 | 21 | ROOM_JOIN, 22 | 23 | JOIN, 24 | 25 | ROOM_LEAVE, 26 | 27 | LEAVE, 28 | 29 | ROOM_TOPIC, 30 | 31 | TOPIC, 32 | 33 | SCAN, 34 | 35 | DONG, 36 | 37 | ERROR, 38 | 39 | READY, 40 | 41 | RESET, 42 | 43 | HEART_BEAT, 44 | 45 | ON, 46 | OFF, 47 | 48 | WATCH_DOG; 49 | 50 | } 51 | 52 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Friendship.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | enum class FriendshipType(var code: Int) { 4 | Unknown(0), Confirm(1), Receive(2), Verify(3); 5 | 6 | companion object { 7 | fun getByCode(code: Int): FriendshipType { 8 | val values = values() 9 | for (value in values) { 10 | if (value.code == code) { 11 | return value 12 | } 13 | } 14 | return Unknown 15 | } 16 | } 17 | 18 | } 19 | 20 | enum class FriendshipSceneType(var code: Int) { 21 | Unknown(0), 22 | QQ(1), 23 | Email(2), 24 | Weixin(3), 25 | QQtbd(12), 26 | Room(14), 27 | Phone(15), 28 | Card(17), 29 | Location(18), 30 | Bottle(25), 31 | Shaking(29), 32 | QRCode(30); 33 | 34 | companion object { 35 | fun getByCode(code: Int): FriendshipSceneType { 36 | val values = values() 37 | for (value in values) { 38 | if (value.code == code) { 39 | return value 40 | } 41 | } 42 | return Unknown 43 | } 44 | } 45 | 46 | } 47 | 48 | 49 | class FriendshipPayload { 50 | var id: String? = null 51 | var contactId: String? = null 52 | var hello: String? = null 53 | var timestamp: Long? = null 54 | var scene:FriendshipSceneType? = null 55 | var type:FriendshipType? = null 56 | var stranger:String? = null 57 | var ticket:String? = null 58 | } 59 | 60 | //class FriendshipPayloadConfirm : FriendshipPayloadBase(), FriendshipPayload { 61 | // var type = FriendshipType.Confirm 62 | //} 63 | // 64 | //class FriendshipPayloadReceive : FriendshipPayloadBase(), FriendshipPayload { 65 | // var scene: FriendshipSceneType? = null 66 | // var stranger: String? = null 67 | // var ticket: String? = null 68 | // var type = FriendshipType.Receive 69 | //} 70 | // 71 | // 72 | //class FriendshipPayloadVerify : FriendshipPayloadBase(), FriendshipPayload { 73 | // var type = FriendshipType.Verify 74 | //} 75 | 76 | class FriendshipSearchCondition { 77 | var phone: String? = null 78 | var weixin: String? = null 79 | } 80 | 81 | 82 | class FriendshipQueryFilter { 83 | var list: List? = null 84 | } -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/ImageType.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | enum class ImageType(val code:Int) { 4 | 5 | Unknown(0), 6 | Thumbnail(1), 7 | HD(2), 8 | Artwork(3); 9 | 10 | 11 | } -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Message.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | enum class MessageType(// Video(4), Video(43) 4 | var code: Int 5 | ) { 6 | Unknown(0), 7 | Attachment(1), // Attach(6), 8 | Audio(2), // Audio(1), Voice(34) 9 | Contact(3), // ShareCard(42) 10 | ChatHistory(4), // ChatHistory(19) 11 | Emoticon(5), // Sticker: Emoticon(15), Emoticon(47) 12 | Image(6), // Img(2), Image(3) 13 | Text(7), // Text(1) 14 | Location(8), // Location(48) 15 | MiniProgram(9), // MiniProgram(33) 16 | Transfer(10), // Transfers(2000) 17 | RedEnvelope(11), // RedEnvelopes(2001) 18 | Recalled(12), // Recalled(10002) 19 | Url(13), // Url(5) 20 | Video(14); 21 | 22 | companion object { 23 | fun getByCode(code: Int): MessageType { 24 | val values = values() 25 | for (value in values) { 26 | if (value.code == code) { 27 | return value 28 | } 29 | } 30 | return MessageType.Unknown 31 | } 32 | } 33 | 34 | } 35 | 36 | data class MessagePayload(val id:String) { 37 | var mentionIdList: List? = null 38 | var filename : String?= null; 39 | var text: String? = null 40 | var timestamp: Long? = null 41 | var type: MessageType? = null 42 | var fromId: String? = null 43 | var roomId: String? = null 44 | var toId: String? = null 45 | 46 | override fun toString(): String { 47 | return "MessagePayload(id='$id', mentionIdList=$mentionIdList, filename=$filename, text=$text, timestamp=$timestamp, type=$type, fromId=$fromId, roomId=$roomId, toId=$toId)" 48 | } 49 | 50 | 51 | } 52 | 53 | class MessageQueryFilter { 54 | var fromId: String? = null 55 | var id: String? = null 56 | var roomId: String? = null 57 | var text: String? = null 58 | var toId: String? = null 59 | var type: MessageType? = null 60 | var textReg:Regex? = null 61 | override fun toString(): String { 62 | return "MessageQueryFilter(fromId=$fromId, id=$id, roomId=$roomId, text=$text, toId=$toId, type=$type, textReg=$textReg)" 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/MiniProgram.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | 4 | class MiniProgramPayload { 5 | var appId: String? = null 6 | var description: String? = null 7 | var pagePath: String? = null 8 | var thumbUrl: String? = null 9 | var title: String? = null 10 | var username: String? = null 11 | var thumbKey: String? = null 12 | } 13 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Puppet.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | import io.github.wechaty.io.github.wechaty.schemas.EventEnum 4 | 5 | data class PuppetQRCodeScanEvent(val qrcoode: String, val status: Int) { 6 | var data: String? = null 7 | } 8 | 9 | data class PuppetRoomInviteEvent(val inviterId: String, val roomId: String) 10 | 11 | data class PuppetRoomJoinEvent( 12 | val inviteeNameList: List, 13 | val inviterName: String, 14 | val roomId: String, 15 | val timestamp: Long 16 | ) 17 | 18 | data class PuppetRoomLeaveEvent( 19 | val leaverNameList: List, 20 | val removerName: String, 21 | val roomId: String, 22 | val timestamp: Long // Unix Timestamp, in seconds 23 | ) 24 | 25 | data class PuppetRoomTopicEvent( 26 | val changerName: String, 27 | val roomId: String, 28 | val topic: String, 29 | val timestamp: Long 30 | ) 31 | 32 | val CHAT_EVENT_DICT = mapOf( 33 | EventEnum.FRIENDSHIP to "receive a friend request", 34 | EventEnum.LOGIN to "puppet had logined", 35 | EventEnum.LOGOUT to "puppet had logouted", 36 | EventEnum.MESSAGE to "received a new message", 37 | EventEnum.ROOM_INVITE to "received a room invitation", 38 | EventEnum.ROOM_JOIN to "be added to a room", 39 | EventEnum.ROOM_LEAVE to "leave or be removed from a room", 40 | EventEnum.ROOM_TOPIC to "room topic had been changed", 41 | EventEnum.SCAN to "a QR Code scan is required" 42 | ) 43 | 44 | val PUPPET_EVENT_DICT = mapOf( 45 | EventEnum.DONG to "emit this event if you received a ding() call", 46 | EventEnum.ERROR to "emit an Error instance when there's any Error need to report to Wechaty", 47 | EventEnum.READY to "emit this event after the puppet is ready(you define it)", 48 | EventEnum.RESET to "reset the puppet by emit this event", 49 | EventEnum.WATCH_DOG to "feed the watchdog by emit this event" 50 | ) + CHAT_EVENT_DICT 51 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/PuppetOptions.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | class PuppetOptions { 4 | var endPoint: String? = null 5 | var timeout: Long? = null 6 | var token: String? = null 7 | var puppetOptionKey: String? = null 8 | var name: String = "DEFAULT" 9 | } 10 | 11 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/Room.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | class RoomMemberQueryFilter() { 4 | var name: String? = null 5 | var roomAlias: String? = null 6 | var contactAlias: String? = null 7 | } 8 | 9 | 10 | class RoomQueryFilter { 11 | var id: String? = null 12 | var topic: String? = null 13 | 14 | var topicRegex:Regex? =null 15 | } 16 | 17 | 18 | class RoomPayload (val id:String){ 19 | var topic: String? = null 20 | var avatar: String? = null 21 | var memberIdList: List = listOf() 22 | var ownerId: String? = null 23 | var adminIdList: List? = null 24 | } 25 | 26 | class RoomMemberPayload { 27 | var id: String? = null 28 | var roomAlias: String? = null 29 | var inviterId: String? = null 30 | var avatar: String? = null 31 | var name: String? = null 32 | } 33 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/RoomInvitationPayload.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas 2 | 3 | /** 4 | * @author Zhengxin 5 | */ 6 | class RoomInvitationPayload { 7 | var id: String? = null 8 | var inviterId: String? = null 9 | var topic: String? = null 10 | var avatar: String? = null 11 | var invitation: String? = null 12 | var memberCount: Int? = null 13 | var memberIdList: List? = null 14 | var timestamp: Long? = null 15 | var receiverId: String? = null 16 | } -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/schemas/UrlLinkPayload.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.schemas; 2 | 3 | data class UrlLinkPayload(val title: String,val url: String) { 4 | 5 | var description: String? = null 6 | 7 | var thumbnailUrl: String? = null 8 | 9 | override fun toString(): String { 10 | return "UrlLinkPayload(title='$title', url='$url', description=$description, thumbnailUrl=$thumbnailUrl)" 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/status/StateSwitch.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.io.github.wechaty.status 2 | 3 | import io.github.wechaty.StateEnum 4 | import io.github.wechaty.eventEmitter.EventEmitter 5 | import io.github.wechaty.io.github.wechaty.schemas.EventEnum 6 | import org.slf4j.LoggerFactory 7 | import java.util.concurrent.CompletableFuture 8 | import java.util.concurrent.Future 9 | import java.util.concurrent.atomic.AtomicInteger 10 | 11 | val COUNTER=AtomicInteger() 12 | 13 | class StateSwitch:EventEmitter(){ 14 | 15 | 16 | @Volatile 17 | private var onoff:Boolean = false 18 | 19 | @Volatile 20 | private var pending:Boolean = false 21 | 22 | 23 | private lateinit var onPromise : Future 24 | private lateinit var offPromise : Future 25 | 26 | private lateinit var onResolver : Function 27 | private lateinit var offResolver : Function 28 | 29 | 30 | private val name :String = "#${COUNTER.addAndGet(1)}" 31 | 32 | init { 33 | offPromise = CompletableFuture.completedFuture(null); 34 | onPromise = CompletableFuture.runAsync{ 35 | TODO() 36 | } 37 | } 38 | 39 | fun on(state: StateEnum):StateEnum{ 40 | val on = on() 41 | log.debug("statusSwitch $name on ${state.name} <- $on") 42 | 43 | onoff = true 44 | pending = (state == StateEnum.PENDING) 45 | 46 | emit(EventEnum.ON,state.name) 47 | TODO() 48 | } 49 | 50 | fun on():StateEnum{ 51 | TODO() 52 | } 53 | 54 | companion object{ 55 | private val log = LoggerFactory.getLogger(StateSwitch::class.java) 56 | } 57 | 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/utils/FutureUtils.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.utils; 2 | 3 | import java.util.* 4 | import java.util.concurrent.CompletableFuture 5 | import java.util.concurrent.ExecutionException 6 | import java.util.concurrent.Future 7 | import java.util.stream.Collectors 8 | 9 | /** 10 | * @author Zhengxin 11 | */ 12 | object FutureUtils { 13 | 14 | fun toCompletable(future: Future): CompletableFuture { 15 | return CompletableFuture.supplyAsync { 16 | try { 17 | return@supplyAsync future.get() 18 | } catch (e: InterruptedException) { 19 | throw RuntimeException(e) 20 | } catch (e: ExecutionException) { 21 | throw RuntimeException(e) 22 | } 23 | } 24 | } 25 | 26 | fun sequence(futures: List>): CompletableFuture> { 27 | val allDoneFuture = 28 | CompletableFuture.allOf(*futures.toTypedArray>()) 29 | return allDoneFuture.thenApply { v: Void -> 30 | futures.stream().map { obj: CompletableFuture -> obj.join() }.filter { Objects.nonNull(it) }.collect(Collectors.toList()) 31 | } 32 | } 33 | 34 | fun sequenceVoid(futures: List>): CompletableFuture { 35 | val allDoneFuture = 36 | CompletableFuture.allOf(*futures.toTypedArray>()) 37 | return CompletableFuture.allOf(allDoneFuture) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/utils/JsonCodec.kt: -------------------------------------------------------------------------------- 1 | //package io.github.wechaty.io.github.wechaty.utils 2 | // 3 | //import io.vertx.core.buffer.Buffer 4 | //import io.vertx.core.eventbus.MessageCodec 5 | //import java.io.* 6 | // 7 | // 8 | //class GenericCodec(private val cls: Class) : MessageCodec { 9 | // override fun encodeToWire(buffer: Buffer, s: T) { 10 | // val bos = ByteArrayOutputStream() 11 | // var out: ObjectOutput? = null 12 | // try { 13 | // out = ObjectOutputStream(bos) 14 | // out.writeObject(s) 15 | // out.flush() 16 | // val yourBytes = bos.toByteArray() 17 | // buffer.appendInt(yourBytes.size) 18 | // buffer.appendBytes(yourBytes) 19 | // out.close() 20 | // } catch (e: IOException) { 21 | // } finally { 22 | // try { 23 | // bos.close() 24 | // } catch (ex: IOException) { 25 | // } 26 | // } 27 | // } 28 | // 29 | // override fun decodeFromWire(pos: Int, buffer: Buffer): T? { 30 | // // My custom message starting from this *position* of buffer 31 | // var _pos = pos 32 | // 33 | // // Length of JSON 34 | // val length: Int = buffer.getInt(_pos) 35 | // 36 | // // Jump 4 because getInt() == 4 bytes 37 | // val yourBytes: ByteArray = buffer.getBytes(4.let { _pos += it; _pos }, length.let { _pos += it; _pos }) 38 | // val bis = ByteArrayInputStream(yourBytes) 39 | // try { 40 | // val ois = ObjectInputStream(bis) 41 | // val msg = ois.readObject() as T 42 | // ois.close() 43 | // return msg 44 | // } catch (e: IOException) { 45 | // println("Listen failed " + e.message) 46 | // } catch (e: ClassNotFoundException) { 47 | // println("Listen failed " + e.message) 48 | // } finally { 49 | // try { 50 | // bis.close() 51 | // } catch (e: IOException) { 52 | // } 53 | // } 54 | // return null 55 | // } 56 | // 57 | // override fun transform(customMessage: T): T { 58 | // // If a message is sent *locally* across the event bus. 59 | // // This example sends message just as is 60 | // return customMessage 61 | // } 62 | // 63 | // override fun name(): String { 64 | // // Each codec must have a unique name. 65 | // // This is used to identify a codec when sending a message and for unregistering 66 | // // codecs. 67 | // return cls.simpleName + "Codec" 68 | // } 69 | // 70 | // override fun systemCodecID(): Byte { 71 | // // Always -1 72 | // return -1 73 | // } 74 | // 75 | //} -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/utils/JsonUtils.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.utils 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude 4 | import com.fasterxml.jackson.databind.ObjectMapper 5 | import com.fasterxml.jackson.module.kotlin.KotlinModule 6 | import com.fasterxml.jackson.module.kotlin.readValue 7 | 8 | object JsonUtils { 9 | 10 | val mapper: ObjectMapper = ObjectMapper() 11 | .setSerializationInclusion(JsonInclude.Include.NON_NULL) 12 | .registerModule(KotlinModule()) 13 | 14 | inline fun readValue(json :String): T { 15 | return mapper.readValue(json) 16 | } 17 | 18 | fun write(input:Any):String{ 19 | return mapper.writeValueAsString(input) 20 | } 21 | 22 | 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /wechaty-puppet/src/main/kotlin/io/github/wechaty/watchdag/WatchDog.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.io.github.wechaty.watchdag 2 | 3 | import io.github.wechaty.eventEmitter.Event 4 | import io.github.wechaty.eventEmitter.EventEmitter 5 | import io.github.wechaty.eventEmitter.Listener 6 | import io.github.wechaty.io.github.wechaty.schemas.EventEnum 7 | import org.slf4j.LoggerFactory 8 | import java.util.* 9 | import java.util.concurrent.Executors 10 | import java.util.concurrent.ScheduledFuture 11 | import java.util.concurrent.TimeUnit 12 | 13 | 14 | class WatchDog(var defaultTimeOut:Long = 60*1000,val name:String = "Bark"):EventEmitter(){ 15 | 16 | @Volatile 17 | private var lastFeed :Long = 0 18 | private var lastFood: WatchdogFood? = null 19 | private var timeOut = defaultTimeOut 20 | private val executorService = Executors.newSingleThreadScheduledExecutor() 21 | private var schedule:ScheduledFuture<*>? = null 22 | 23 | @Volatile 24 | private var timeId: Long = 0; 25 | 26 | 27 | 28 | init{ 29 | startTimer(defaultTimeOut) 30 | } 31 | 32 | fun on(event:Event,listener:WatchdogListener){ 33 | super.on(event,object:Listener{ 34 | override fun handler(vararg any: Any) { 35 | val watchdogFood = WatchdogFood(timeOut) 36 | log.debug("sent reset message") 37 | feed(watchdogFood) 38 | listener.handler(watchdogFood) 39 | 40 | } 41 | 42 | }) 43 | } 44 | 45 | private fun startTimer(timeout:Long){ 46 | var localTimeout = timeout; 47 | if(localTimeout == 0L){ 48 | localTimeout = defaultTimeOut; 49 | } 50 | schedule = executorService.schedule({ 51 | val watchdogFood = WatchdogFood(timeout) 52 | log.debug("sent reset message") 53 | emit(EventEnum.RESET, watchdogFood) 54 | }, localTimeout, TimeUnit.MILLISECONDS) 55 | 56 | } 57 | 58 | fun left():Long{ 59 | 60 | if(lastFeed == 0L){ 61 | return 0 62 | } 63 | 64 | return lastFeed.plus(timeOut).minus( System.currentTimeMillis() ) 65 | } 66 | 67 | fun feed(food: WatchdogFood):Long{ 68 | log.debug("feed dog $food") 69 | if(food.timeout == 0L){ 70 | food.timeout = defaultTimeOut 71 | } 72 | 73 | schedule?.cancel(true) 74 | 75 | this.lastFeed = System.currentTimeMillis(); 76 | this.lastFood = food 77 | this.timeOut = food.timeout 78 | 79 | log.debug("lastFeed is ${this.lastFeed}") 80 | 81 | startTimer(food.timeout) 82 | 83 | 84 | return left() 85 | } 86 | 87 | fun stopTimer(){ 88 | schedule?.cancel(true) 89 | } 90 | 91 | fun sleep(){ 92 | stopTimer() 93 | } 94 | 95 | companion object{ 96 | 97 | private val log = LoggerFactory.getLogger(WatchdogFood::class.java) 98 | 99 | } 100 | 101 | 102 | 103 | 104 | } 105 | 106 | interface WatchdogListener { 107 | fun handler(event: WatchdogFood) 108 | } 109 | 110 | 111 | data class WatchdogFood(var timeout: Long) { 112 | 113 | var data:Any? = null 114 | override fun toString(): String { 115 | return "WatchdogFood(timeout=$timeout, data=$data)" 116 | } 117 | } 118 | 119 | fun main(){ 120 | val watchDog = WatchDog() 121 | 122 | val watchdogFood = WatchdogFood(2000) 123 | 124 | watchDog.feed(watchdogFood) 125 | 126 | println(watchDog.left()) 127 | 128 | // watchDog.on("reset"){ 129 | // println("rest") 130 | // } 131 | 132 | 133 | Thread.sleep(1000000) 134 | } 135 | 136 | -------------------------------------------------------------------------------- /wechaty-puppet/src/test/kotlin/io/github/wechaty/filebox/FileBoxTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.filebox 2 | 3 | import junit.framework.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | const val EXPECTED_FILEBOX_URL = "http://testurl" 7 | const val EXPECTED_FILEBOX_NAME = "fileboxname" 8 | 9 | class FileBoxTest { 10 | 11 | @Test 12 | fun testFileBoxFromURLShallHaveCorrectNameAndURL() { 13 | 14 | var filebox : FileBox = FileBox.fromJson("{\"remoteUrl\":\"" + EXPECTED_FILEBOX_URL + "\"," + 15 | "\"name\":\"" + EXPECTED_FILEBOX_NAME + "\"," + 16 | "\"boxType\":2}") 17 | 18 | assertEquals(EXPECTED_FILEBOX_URL, filebox.remoteUrl) 19 | assertEquals(EXPECTED_FILEBOX_NAME, filebox.name) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /wechaty/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | io.github.wechaty 6 | wechaty-parent 7 | 0.1.5-SNAPSHOT 8 | 9 | 4.0.0 10 | wechaty 11 | jar 12 | wechaty 13 | 14 | 15 | true 16 | 17 | 18 | 19 | 20 | 21 | io.github.wechaty 22 | wechaty-puppet 23 | ${project.version} 24 | 25 | 26 | 27 | io.github.wechaty 28 | wechaty-puppet-hostie 29 | ${project.version} 30 | 31 | 32 | 33 | io.github.wechaty 34 | wechaty-puppet-mock 35 | ${project.version} 36 | test 37 | 38 | 39 | 40 | org.jetbrains.kotlin 41 | kotlin-stdlib 42 | 43 | 44 | 45 | 46 | com.github.ben-manes.caffeine 47 | caffeine 48 | 49 | 50 | 51 | 52 | org.apache.commons 53 | commons-collections4 54 | 55 | 56 | 57 | 58 | com.google.guava 59 | guava 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.apache.logging.log4j 68 | log4j-core 69 | test 70 | 71 | 72 | org.apache.logging.log4j 73 | log4j-slf4j-impl 74 | test 75 | 76 | 77 | 78 | 79 | com.lmax 80 | disruptor 81 | test 82 | 83 | 84 | 85 | org.slf4j 86 | slf4j-api 87 | 88 | 89 | 90 | 91 | com.squareup.okhttp3 92 | okhttp 93 | 94 | 95 | 96 | 97 | 98 | org.hamcrest 99 | hamcrest-core 100 | 101 | 102 | net.sourceforge.htmlcleaner 103 | htmlcleaner 104 | 105 | 106 | 107 | org.mockito 108 | mockito-core 109 | test 110 | 111 | 112 | junit 113 | junit 114 | test 115 | 116 | 117 | org.reflections 118 | reflections 119 | 120 | 121 | 122 | 123 | 124 | 125 | org.jetbrains.kotlin 126 | kotlin-maven-plugin 127 | ${kotlin.version} 128 | 129 | 130 | compile 131 | compile 132 | 133 | 134 | ${project.basedir}/src/main/kotlin 135 | ${project.basedir}/src/main/java 136 | 137 | 138 | 139 | 140 | test-compile 141 | test-compile 142 | 143 | 144 | ${project.basedir}/src/test/kotlin 145 | ${project.basedir}/src/test/java 146 | 147 | 148 | 149 | 150 | 151 | 152 | org.apache.maven.plugins 153 | maven-compiler-plugin 154 | 3.6.1 155 | 156 | 1.8 157 | 1.8 158 | 159 | 160 | 161 | 162 | default-compile 163 | none 164 | 165 | 166 | 167 | default-testCompile 168 | none 169 | 170 | 171 | java-compile 172 | compile 173 | compile 174 | 175 | 176 | java-test-compile 177 | test-compile 178 | testCompile 179 | 180 | 181 | 182 | 183 | org.apache.maven.plugins 184 | maven-source-plugin 185 | 3.2.1 186 | 187 | 188 | package 189 | 190 | jar-no-fork 191 | 192 | 193 | 194 | 195 | 196 | 197 | org.apache.maven.plugins 198 | maven-javadoc-plugin 199 | 3.2.0 200 | 201 | 202 | package 203 | 204 | jar 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /wechaty/src/main/java/org/opengraph/MetaElement.java: -------------------------------------------------------------------------------- 1 | package org.opengraph; 2 | 3 | import java.net.URL; 4 | 5 | /** 6 | * Represents OpenGraph enabled meta data for a specific document 7 | * @author Callum Jones 8 | */ 9 | public class MetaElement 10 | { 11 | private OpenGraphNamespace namespace; //either "og" an NS specific 12 | private String property; 13 | private String content; 14 | 15 | /** 16 | * Construct the representation of an element 17 | * @param namespace The namespace the element belongs to 18 | * @param property The property key 19 | * @param content The content or value of this element 20 | */ 21 | public MetaElement(OpenGraphNamespace namespace, String property, String content) 22 | { 23 | this.namespace = namespace; 24 | this.property = property; 25 | this.content = content; 26 | } 27 | 28 | /** 29 | * Fetch the content string of the element 30 | */ 31 | public String getContent() 32 | { 33 | return content; 34 | } 35 | 36 | /** 37 | * Fetch the OpenGraph namespace 38 | */ 39 | public OpenGraphNamespace getNamespace() 40 | { 41 | return namespace; 42 | } 43 | 44 | /** 45 | * Fetch the property of the element 46 | */ 47 | public String getProperty() 48 | { 49 | return property; 50 | } 51 | 52 | /** 53 | * Fetch the OpenGraph data from the object 54 | * @return If the content is a URL, then an attempted will be made to build OpenGraph data from the object 55 | */ 56 | public OpenGraph getExtendedData() 57 | { 58 | //The Java language should know the best form of a URL 59 | try 60 | { 61 | URL url = new URL(getContent()); 62 | 63 | //success 64 | return new OpenGraph(url.toString(), true); 65 | } 66 | catch (Exception e) 67 | { 68 | return null; //not a valid URL 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /wechaty/src/main/java/org/opengraph/OpenGraphNamespace.java: -------------------------------------------------------------------------------- 1 | package org.opengraph; 2 | 3 | /** 4 | * Represents an OpenGraph namespace 5 | * @author Callum Jones 6 | */ 7 | public class OpenGraphNamespace 8 | { 9 | private String prefix; 10 | private String schemaURI; 11 | 12 | /** 13 | * Construct a namespace 14 | * @param prefix The OpenGraph assigned namespace prefix such as og or og_appname 15 | * @param schemaURI The URL for the OpenGraph schema 16 | */ 17 | public OpenGraphNamespace(String prefix, String schemaURI) 18 | { 19 | this.prefix = prefix; 20 | this.schemaURI = schemaURI; 21 | } 22 | 23 | /* 24 | * Fetch the prefix used for the namespace 25 | */ 26 | public String getPrefix() 27 | { 28 | return prefix; 29 | } 30 | 31 | /* 32 | * Fetch the address for the schema reference 33 | */ 34 | public String getSchemaURI() 35 | { 36 | return schemaURI; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/Accessory.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty 2 | 3 | import io.github.wechaty.eventEmitter.EventEmitter 4 | 5 | 6 | open class Accessory(open val wechaty: Wechaty) : EventEmitter() 7 | 8 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/RoomListener.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty 2 | 3 | import io.github.wechaty.user.Contact 4 | import io.github.wechaty.user.Message 5 | import io.github.wechaty.user.RoomInvitation 6 | import java.util.* 7 | 8 | @FunctionalInterface 9 | interface InviteListener { 10 | fun handler(inviter: Contact, invitation: RoomInvitation) 11 | } 12 | 13 | @FunctionalInterface 14 | interface LeaveListener { 15 | fun handler(leaverList: List, remover: Contact, date: Date) 16 | } 17 | 18 | @FunctionalInterface 19 | interface RoomInnerMessageListener { 20 | fun handler(message: Message, date: Date) 21 | } 22 | 23 | @FunctionalInterface 24 | interface JoinListener { 25 | fun handler(inviteeList: List, inviter: Contact, date: Date) 26 | } 27 | 28 | @FunctionalInterface 29 | interface TopicListener { 30 | fun handler(topic: String, oldTopic: String, changer: Contact, date: Date) 31 | } 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/WechatyListener.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty 2 | 3 | import io.github.wechaty.schemas.ScanStatus 4 | import io.github.wechaty.user.Contact 5 | import io.github.wechaty.user.ContactSelf 6 | import io.github.wechaty.user.Message 7 | import io.github.wechaty.user.Room 8 | import java.util.* 9 | 10 | @FunctionalInterface 11 | interface DongListener { 12 | fun handler(data: String?) 13 | } 14 | 15 | @FunctionalInterface 16 | interface ErrorListener { 17 | fun handler(error: String) 18 | } 19 | 20 | @FunctionalInterface 21 | interface FriendshipListener { 22 | fun handler(friendshipId: String) 23 | } 24 | 25 | @FunctionalInterface 26 | interface HeartbeatListener{ 27 | fun handler(data:Any) 28 | 29 | } 30 | 31 | @FunctionalInterface 32 | interface ScanListener { 33 | fun handler(qrcode: String?, statusScanStatus: ScanStatus, data: String?) 34 | 35 | } 36 | 37 | @FunctionalInterface 38 | interface LoginListener { 39 | fun handler(self: ContactSelf) 40 | } 41 | 42 | @FunctionalInterface 43 | interface LogoutListener { 44 | fun handler(contactId: String, reason: String?) 45 | } 46 | 47 | @FunctionalInterface 48 | interface ResetListerner { 49 | fun handler(reason: String) 50 | } 51 | 52 | @FunctionalInterface 53 | interface RoomJoinListener { 54 | fun handler(room: Room, inviteeList: List, inviter: Contact, date: Date) 55 | } 56 | 57 | @FunctionalInterface 58 | interface RoomLeaveListener { 59 | fun handler(room: Room, leaverList: List, remover: Contact, date: Date) 60 | } 61 | 62 | @FunctionalInterface 63 | interface RoomTopicListener { 64 | fun handler(room: Room, newTopic: String, oldTopic: String, changer: Contact, date: Date) 65 | } 66 | 67 | @FunctionalInterface 68 | interface RoomInviteListener { 69 | fun handler(roomInvitationId: String) 70 | } 71 | 72 | @FunctionalInterface 73 | interface ReadyListener { 74 | fun handler() 75 | } 76 | 77 | @FunctionalInterface 78 | interface MessageListener { 79 | fun handler(message: Message) 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/WechatyOptions.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty 2 | 3 | //import io.github.wechaty.memorycard.MemoryCard 4 | import io.github.wechaty.schemas.PuppetOptions 5 | 6 | class WechatyOptions { 7 | 8 | // var memory:MemoryCard? = null 9 | 10 | var name:String = "Wechaty" 11 | 12 | // var puppet:String = "wechaty-puppet-hostie" 13 | var puppet:String = "io.github.wechaty.grpc.GrpcPuppet" 14 | 15 | var puppetOptions:PuppetOptions? = null 16 | 17 | var ioToken:String? = null 18 | 19 | } 20 | typealias WechatyPlugin = (Wechaty) -> Unit 21 | 22 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/opengraph/OpenGraph.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.opengraph 2 | 3 | import org.opengraph.OpenGraph 4 | import java.util.concurrent.CompletableFuture 5 | import java.util.concurrent.Future 6 | 7 | fun openGraph(url:String):Future{ 8 | return CompletableFuture.supplyAsync { 9 | return@supplyAsync OpenGraph(url,true) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/type/Sayable.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.type 2 | 3 | import io.github.wechaty.user.Contact 4 | import java.util.concurrent.Future 5 | 6 | interface Sayable { 7 | fun say(something: Any, contact: Contact): Future 8 | } 9 | 10 | interface Acceptable{ 11 | fun accept():Future 12 | } 13 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/Contact.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Puppet 5 | import io.github.wechaty.Wechaty 6 | import io.github.wechaty.filebox.FileBox 7 | import io.github.wechaty.schemas.ContactGender 8 | import io.github.wechaty.schemas.ContactPayload 9 | import io.github.wechaty.schemas.ContactQueryFilter 10 | import io.github.wechaty.schemas.ContactType 11 | import io.github.wechaty.type.Sayable 12 | import io.github.wechaty.utils.FutureUtils 13 | import org.apache.commons.lang3.StringUtils 14 | import org.slf4j.LoggerFactory 15 | import java.util.concurrent.CompletableFuture 16 | import java.util.concurrent.Future 17 | import kotlin.math.E 18 | 19 | open class Contact(wechaty: Wechaty,val id:String) : Sayable, Accessory(wechaty) { 20 | 21 | protected val puppet: Puppet = wechaty.getPuppet() 22 | protected var payload: ContactPayload? = null 23 | 24 | override fun say(something: Any, contact: Contact): Future { 25 | when (something) { 26 | 27 | is String -> { 28 | val messageSendText = puppet.messageSendText(id, something) 29 | } 30 | 31 | } 32 | return CompletableFuture.completedFuture(null); 33 | } 34 | 35 | fun say(something: Any): Message? { 36 | 37 | val msgId: String? 38 | 39 | when (something) { 40 | 41 | is String -> { 42 | msgId = puppet.messageSendText(id, something).get() 43 | } 44 | is Contact -> { 45 | msgId = puppet.messageSendContact(id, something.id).get() 46 | } 47 | is FileBox -> { 48 | msgId = puppet.messageSendFile(id, something).get() 49 | } 50 | is UrlLink -> { 51 | msgId = puppet.messageSendUrl(id, something.payload).get() 52 | } 53 | is MiniProgram -> { 54 | msgId = puppet.messageSendMiniProgram(id, something.payload).get() 55 | } 56 | else -> { 57 | throw Exception("unsupported arg:$something") 58 | } 59 | } 60 | 61 | if (msgId != null) { 62 | 63 | val message = wechaty.messageManager.load(msgId) 64 | message.ready() 65 | return message 66 | } 67 | 68 | return null; 69 | 70 | } 71 | 72 | fun sync() { 73 | return ready(true) 74 | } 75 | 76 | fun ready(forceSyn: Boolean = false) { 77 | if (!forceSyn && isReady()) { 78 | return 79 | } 80 | try { 81 | if (forceSyn) { 82 | puppet.contactPayloadDirty(id) 83 | } 84 | this.payload = puppet.contactPayload(id).get() 85 | } catch (e: Exception) { 86 | log.error("ready() contactPayload {} error ", id, e) 87 | throw e 88 | } 89 | 90 | 91 | } 92 | 93 | fun isReady(): Boolean { 94 | return (payload != null && StringUtils.isNotEmpty(payload!!.name)) 95 | } 96 | 97 | fun name():String{ 98 | return payload?.name ?: "" 99 | } 100 | 101 | fun setAlias(newAlias:String){ 102 | if(payload == null){ 103 | throw Exception("no payload") 104 | } 105 | try { 106 | puppet.contactAlias(id, newAlias).get() 107 | puppet.contactPayloadDirty(id) 108 | payload = puppet.contactPayload(id).get() 109 | }catch (e:Exception){ 110 | log.error("alias({}) rejected: {}", newAlias, e.message) 111 | throw e 112 | } 113 | 114 | } 115 | 116 | fun getAlias():String?{ 117 | return payload?.alias ?:null 118 | } 119 | 120 | fun stranger():Boolean?{ 121 | return if(friend() == null){ 122 | null 123 | }else{ 124 | !friend()!! 125 | } 126 | } 127 | 128 | fun friend():Boolean?{ 129 | return payload?.friend 130 | } 131 | 132 | fun type():ContactType{ 133 | return payload?.type ?: throw Exception("no payload") 134 | } 135 | 136 | fun gender():ContactGender{ 137 | return payload?.gender ?: ContactGender.Unknown 138 | } 139 | 140 | fun province():String?{ 141 | return payload?.province 142 | } 143 | 144 | fun city():String?{ 145 | return payload?.city 146 | } 147 | 148 | open fun avatar(): FileBox { 149 | try { 150 | return wechaty.getPuppet().getContactAvatar(this.id).get() 151 | } catch (e: Exception) { 152 | log.error("error",e) 153 | TODO() 154 | } 155 | } 156 | 157 | fun tags():List{ 158 | val tagIdList = wechaty.getPuppet().tagContactList(this.id).get() 159 | return try { 160 | tagIdList.map { 161 | wechaty.tagManager.load(it) 162 | } 163 | } catch (e: Exception) { 164 | log.error("error",e) 165 | listOf() 166 | } 167 | } 168 | 169 | fun self():Boolean{ 170 | val userId = puppet.selfId() 171 | if(StringUtils.isEmpty(userId)){ 172 | return false 173 | } 174 | return StringUtils.equals(id,userId) 175 | } 176 | 177 | 178 | companion object { 179 | private val log = LoggerFactory.getLogger(Contact::class.java) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/ContactSelf.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.Wechaty 4 | import io.github.wechaty.filebox.FileBox 5 | import java.util.concurrent.CompletableFuture 6 | import java.util.concurrent.Future 7 | 8 | class ContactSelf(wechaty: Wechaty, id: String) : Contact(wechaty, id) { 9 | 10 | fun avatar(fileBox: FileBox) { 11 | puppet.setContactAvatar(super.id, fileBox) 12 | } 13 | 14 | fun setName(name:String){ 15 | puppet.contactSelfName(name).get() 16 | sync() 17 | } 18 | 19 | fun signature(signature:String){ 20 | 21 | var puppetId:String? = puppet.selfId() 22 | 23 | let{ 24 | puppetId != null 25 | }.run { 26 | puppet.contactSelfSignature(signature).get() 27 | sync() 28 | } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/Favorite.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | 6 | class Favorite(wechaty: Wechaty):Accessory(wechaty){ 7 | 8 | 9 | 10 | } -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/Friendship.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | import io.github.wechaty.schemas.FriendshipPayload 6 | import io.github.wechaty.schemas.FriendshipSearchCondition 7 | import io.github.wechaty.schemas.FriendshipType 8 | import io.github.wechaty.utils.JsonUtils 9 | import org.apache.commons.lang3.StringUtils 10 | import org.slf4j.LoggerFactory 11 | 12 | class Friendship (wechaty: Wechaty,val id:String):Accessory(wechaty){ 13 | 14 | 15 | private var payload:FriendshipPayload? = null 16 | 17 | fun isReady():Boolean{ 18 | return payload != null 19 | } 20 | 21 | fun ready(){ 22 | if(isReady()){ 23 | return 24 | } 25 | this.payload = wechaty.getPuppet().friendshipPayload(id!!).get() 26 | contact().ready() 27 | 28 | } 29 | 30 | fun contact():Contact{ 31 | if(payload == null){ 32 | throw Exception("no payload") 33 | } 34 | return wechaty.contactManager.load(payload!!.contactId!!) 35 | } 36 | 37 | fun accept(){ 38 | if(payload == null){ 39 | throw Exception("no payload") 40 | } 41 | 42 | if(payload!!.type != FriendshipType.Receive){ 43 | throw Exception("accept() need type to be FriendshipType.Receive, but it got a ${payload!!.type}") 44 | } 45 | wechaty.getPuppet().friendshipAccept(this.id).get() 46 | val contact = contact() 47 | contact.ready() 48 | contact.sync() 49 | } 50 | 51 | fun hello():String{ 52 | if(payload==null){ 53 | throw Exception("ne payload") 54 | } 55 | return this.payload?.hello ?: ""; 56 | } 57 | 58 | fun type():FriendshipType{ 59 | return this.payload?.type ?:FriendshipType.Unknown 60 | } 61 | 62 | fun toJson():String{ 63 | if(payload==null){ 64 | throw Exception("ne payload") 65 | } 66 | return JsonUtils.write(payload!!); 67 | } 68 | 69 | companion object{ 70 | private val log = LoggerFactory.getLogger(Friendship::class.java) 71 | } 72 | 73 | } 74 | 75 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/Image.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | import io.github.wechaty.filebox.FileBox 6 | import io.github.wechaty.schemas.ImageType 7 | 8 | class Image(wechaty: Wechaty,val id:String):Accessory(wechaty){ 9 | 10 | fun thumbnail(): FileBox { 11 | return wechaty.getPuppet().messageImage(id, ImageType.Thumbnail).get() 12 | } 13 | 14 | fun hd():FileBox{ 15 | return wechaty.getPuppet().messageImage(id, ImageType.HD).get() 16 | } 17 | 18 | fun artwork():FileBox{ 19 | return wechaty.getPuppet().messageImage(id, ImageType.Artwork).get() 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/Message.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | import io.github.wechaty.filebox.FileBox 6 | import io.github.wechaty.schemas.MessagePayload 7 | import io.github.wechaty.schemas.MessageType 8 | import io.github.wechaty.schemas.RoomMemberQueryFilter 9 | import io.github.wechaty.type.Sayable 10 | import org.apache.commons.collections4.CollectionUtils 11 | import org.apache.commons.lang3.StringUtils 12 | import org.slf4j.LoggerFactory 13 | import java.util.concurrent.CompletableFuture 14 | import java.util.concurrent.Future 15 | import java.util.regex.Pattern 16 | 17 | open class Message(wechaty: Wechaty,val id: String) : Sayable, Accessory(wechaty){ 18 | 19 | private val AT_SEPRATOR_REGEX = "[\\u2005\\u0020]" 20 | private val puppet = wechaty.getPuppet() 21 | private var payload : MessagePayload? = null 22 | 23 | override fun say(something: Any, contact: Contact): Future { 24 | 25 | val from = from() 26 | 27 | val room = room() 28 | 29 | val conversationId: String 30 | 31 | conversationId = when { 32 | room != null -> { 33 | room.id 34 | } 35 | from != null -> { 36 | from.id 37 | } 38 | else -> { 39 | throw Exception("neither room nor fromId?") 40 | } 41 | } 42 | 43 | var msgId:String? 44 | 45 | return CompletableFuture.supplyAsync { 46 | when(something){ 47 | 48 | is String ->{ 49 | msgId = puppet.messageSendText(conversationId, something).get() 50 | } 51 | is FileBox ->{ 52 | msgId = puppet.messageSendFile(conversationId,something).get() 53 | } 54 | 55 | is UrlLink ->{ 56 | msgId = puppet.messageSendUrl(conversationId,something.payload).get() 57 | } 58 | 59 | is MiniProgram->{ 60 | msgId = puppet.messageSendMiniProgram(conversationId,something.payload).get() 61 | } 62 | 63 | else ->{ 64 | throw Exception("unknown message") 65 | } 66 | 67 | } 68 | if(msgId != null){ 69 | val msg = wechaty.messageManager.load(msgId!!) 70 | return@supplyAsync msg 71 | } 72 | return@supplyAsync null 73 | } 74 | } 75 | 76 | fun from():Contact?{ 77 | if(payload == null){ 78 | throw Exception("no payload") 79 | } 80 | val fromId = payload!!.fromId ?: return null 81 | 82 | return wechaty.contactManager.load(fromId) 83 | } 84 | 85 | fun to():Contact?{ 86 | if(payload == null){ 87 | throw Exception("no payload") 88 | } 89 | val toId = payload!!.toId ?: return null 90 | return wechaty.contactManager.load(toId) 91 | } 92 | 93 | fun room():Room?{ 94 | if(payload == null){ 95 | throw Exception("no payload") 96 | } 97 | 98 | val roomId = payload!!.roomId 99 | 100 | if (StringUtils.isEmpty(roomId)) { 101 | return null 102 | } 103 | return wechaty.roomManager.load(roomId!!) 104 | } 105 | 106 | 107 | fun recall():Future{ 108 | return CompletableFuture.supplyAsync { 109 | return@supplyAsync puppet.messageRecall(this.id).get() 110 | } 111 | } 112 | 113 | fun type(): MessageType{ 114 | if(this.payload == null){ 115 | throw Exception("no payload") 116 | } 117 | return this.payload?.type ?: MessageType.Unknown 118 | } 119 | 120 | fun self():Boolean{ 121 | val selfId = puppet.selfId() 122 | val from = from() 123 | 124 | return StringUtils.equals(selfId,from?.id) 125 | } 126 | 127 | fun mentionList():List{ 128 | val room = this.room() 129 | 130 | if(room == null && type() != MessageType.Text){ 131 | return listOf() 132 | } 133 | 134 | if(CollectionUtils.isNotEmpty(payload?.mentionIdList)){ 135 | 136 | 137 | val list = payload!!.mentionIdList!!.map { 138 | val contact = wechaty.contactManager.load(it) 139 | contact.ready() 140 | contact 141 | } 142 | 143 | return list 144 | } 145 | 146 | val atList = this.text().split(AT_SEPRATOR_REGEX) 147 | if(atList.isEmpty()){ 148 | return listOf() 149 | } 150 | 151 | val rawMentionList = atList.filter { 152 | "@" in it 153 | }.map { 154 | multipleAt(it) 155 | } 156 | 157 | val mentionNameList = rawMentionList.flatten().filter { 158 | StringUtils.isNotEmpty(it) 159 | } 160 | 161 | val roomMemberQueryFilter = RoomMemberQueryFilter() 162 | val flatten = mentionNameList.map { 163 | roomMemberQueryFilter.name = it 164 | room!!.memberAll(roomMemberQueryFilter) 165 | }.flatten() 166 | 167 | return flatten 168 | } 169 | 170 | fun content():String{ 171 | return text() 172 | } 173 | 174 | 175 | fun ready():Future{ 176 | 177 | return CompletableFuture.supplyAsync { 178 | 179 | if (isReady()) { 180 | return@supplyAsync null 181 | } 182 | 183 | this.payload = puppet.messagePayload(id!!).get() 184 | 185 | log.debug("message payload is {}",payload) 186 | 187 | if (payload == null) { 188 | throw Exception("no playload") 189 | } 190 | 191 | val fromId = payload!!.fromId 192 | val roomId = payload!!.roomId 193 | val toId = payload!!.toId 194 | 195 | if (StringUtils.isNotBlank(roomId)) { 196 | wechaty.roomManager.load(roomId!!).ready().get() 197 | } 198 | 199 | if (StringUtils.isNotBlank(fromId)) { 200 | wechaty.contactManager.load(fromId!!).ready() 201 | } 202 | 203 | if (StringUtils.isNotBlank(toId)) { 204 | wechaty.contactManager.load(toId!!).ready() 205 | } 206 | 207 | return@supplyAsync null 208 | } 209 | } 210 | 211 | fun isReady():Boolean{ 212 | return payload != null; 213 | } 214 | 215 | fun text():String{ 216 | if(payload == null){ 217 | throw Exception("no play") 218 | } 219 | 220 | return payload?.text ?: "" 221 | } 222 | 223 | fun talker():Contact{ 224 | return this.from()!! 225 | } 226 | 227 | fun toRecalled():Message?{ 228 | 229 | if(this.type() != MessageType.Recalled){ 230 | throw Exception("Cannot call toRecalled() on message which id not recalled type") 231 | } 232 | 233 | val originalMessageId = text() 234 | 235 | if(StringUtils.isEmpty(originalMessageId)){ 236 | throw Exception("Cannot find recalled Message") 237 | } 238 | 239 | return try { 240 | val message = wechaty.messageManager.load(originalMessageId) 241 | message.ready().get() 242 | message 243 | } catch (e: Exception){ 244 | log.warn("Can not retrieve the recalled message with id ${originalMessageId}") 245 | null 246 | } 247 | } 248 | 249 | fun mentionText():String{ 250 | val text = text() 251 | val room = room() 252 | 253 | val mentionList = mentionList() 254 | if(room == null || CollectionUtils.isEmpty(mentionList)){ 255 | return text 256 | } 257 | 258 | val toAliasName :(Contact) -> String = { 259 | val alias = room.alias(it) 260 | val name = it.name() 261 | if(StringUtils.isNotEmpty(alias)) alias!! else name 262 | } 263 | 264 | var textWithoutMention = text 265 | 266 | val mentionNameList = mentionList.map(toAliasName) 267 | 268 | mentionNameList.forEach{ 269 | val escapedCur = escapeRegExp(it) 270 | val regex = Regex("@${escapedCur}(\\u2005|\\u0020|\$)") 271 | textWithoutMention = regex.replace(text,"") 272 | } 273 | return textWithoutMention.trim() 274 | 275 | } 276 | 277 | fun mentionSelf():Boolean{ 278 | val selfId = puppet.selfId() 279 | val mentionList = this.mentionList() 280 | 281 | return mentionList.any { 282 | it.id == selfId 283 | } 284 | } 285 | 286 | fun file():FileBox{ 287 | return this.toFileBox() 288 | } 289 | 290 | fun toImage():Image{ 291 | if(this.type() != MessageType.Image){ 292 | throw Exception("not a image type, type is "+ this.type()) 293 | } 294 | return wechaty.imageManager.create(this.id); 295 | } 296 | 297 | fun toContact():Contact{ 298 | 299 | if(this.type() != MessageType.Contact){ 300 | throw Exception("message not a ShareCard") 301 | } 302 | 303 | val contactId = wechaty.getPuppet().messageContact(this.id).get() 304 | if(StringUtils.isEmpty(contactId)){ 305 | throw Exception("can not get contact id by message ${this.id}") 306 | } 307 | 308 | val contact = wechaty.contactManager.load(contactId) 309 | contact.ready() 310 | return contact 311 | } 312 | 313 | fun toUrlLink():UrlLink{ 314 | if(this.type() != MessageType.Url){ 315 | throw Exception("message not a Url Link") 316 | } 317 | 318 | val urlPayload = wechaty.getPuppet().messageUrl(this.id).get() 319 | return UrlLink(urlPayload) 320 | } 321 | 322 | fun toMiniProgram():MiniProgram{ 323 | 324 | if(this.type() != MessageType.MiniProgram){ 325 | throw Exception("message not a MiniProgram") 326 | } 327 | 328 | val miniProgramPayload = wechaty.getPuppet().messageMiniProgram(this.id).get() 329 | return MiniProgram(miniProgramPayload) 330 | } 331 | 332 | 333 | fun toFileBox():FileBox{ 334 | if(this.type() == MessageType.Text){ 335 | throw Exception("text message no file") 336 | } 337 | return wechaty.getPuppet().messageFile(this.id).get() 338 | } 339 | 340 | 341 | override fun toString():String{ 342 | return "Message(payload=$payload,id=$id)" 343 | } 344 | 345 | companion object{ 346 | private val log = LoggerFactory.getLogger(Message::class.java) 347 | } 348 | 349 | 350 | } 351 | 352 | fun multipleAt(str:String):List{ 353 | val re = Regex("^.*?@") 354 | val str1 = re.replace(str, "@") 355 | 356 | var name = "" 357 | val nameList = mutableListOf() 358 | 359 | str1.split("@") 360 | .filter { 361 | StringUtils.isNotEmpty(it) 362 | } 363 | .reversed() 364 | .forEach{mentionName -> 365 | name = "$mentionName@$name" 366 | nameList.add(name.dropLast(1)) 367 | } 368 | return nameList 369 | } 370 | 371 | val SPECIAL_REGEX_CHARS: Pattern = Pattern.compile("[{}()\\[\\].+*?^$\\\\|]") 372 | 373 | fun escapeRegExp(str: String): String? { 374 | return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0") 375 | } 376 | 377 | fun main(){ 378 | 379 | val str = "hello@a@b@c" 380 | 381 | val multipleAt = multipleAt(str) 382 | 383 | println(multipleAt) 384 | 385 | 386 | } 387 | 388 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/MiniProgram.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.schemas.MiniProgramPayload 4 | 5 | class MiniProgram(var payload: MiniProgramPayload) { 6 | 7 | fun appId():String?{ 8 | return payload.appId 9 | } 10 | 11 | fun titile():String?{ 12 | return payload.title 13 | } 14 | 15 | fun pagePath():String?{ 16 | return payload.pagePath 17 | } 18 | 19 | fun username():String?{ 20 | return payload.username 21 | } 22 | 23 | fun description():String?{ 24 | return payload.description 25 | } 26 | 27 | fun thumbUrl():String? { 28 | return payload.thumbUrl 29 | } 30 | 31 | fun thumbKey():String?{ 32 | return payload.thumbKey 33 | } 34 | 35 | 36 | companion object{ 37 | 38 | fun create():MiniProgram{ 39 | val payload = MiniProgramPayload() 40 | return MiniProgram(payload); 41 | 42 | } 43 | 44 | } 45 | } -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/Room.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.InviteListener 4 | import io.github.wechaty.JoinListener 5 | import io.github.wechaty.LeaveListener 6 | import io.github.wechaty.RoomInnerMessageListener 7 | import io.github.wechaty.TopicListener 8 | import io.github.wechaty.Accessory 9 | import io.github.wechaty.Puppet 10 | import io.github.wechaty.Wechaty 11 | import io.github.wechaty.eventEmitter.Event 12 | import io.github.wechaty.eventEmitter.Listener 13 | import io.github.wechaty.filebox.FileBox 14 | import io.github.wechaty.io.github.wechaty.schemas.EventEnum 15 | import io.github.wechaty.schemas.RoomMemberQueryFilter 16 | import io.github.wechaty.schemas.RoomPayload 17 | import io.github.wechaty.type.Sayable 18 | import io.github.wechaty.utils.QrcodeUtils 19 | import org.apache.commons.collections4.CollectionUtils 20 | import org.apache.commons.lang3.StringUtils 21 | import org.slf4j.LoggerFactory 22 | import java.util.* 23 | import java.util.concurrent.CompletableFuture 24 | import java.util.concurrent.Future 25 | 26 | const val FOUR_PER_EM_SPACE = "\u2005" 27 | 28 | class Room(wechaty: Wechaty, val id: String) : Accessory(wechaty), Sayable { 29 | 30 | private val puppet: Puppet = wechaty.getPuppet() 31 | private var payload: RoomPayload? = null 32 | 33 | fun sync(): Future { 34 | return ready(true) 35 | } 36 | 37 | fun isReady(): Boolean { 38 | return this.payload != null 39 | } 40 | 41 | override fun say(something: Any, contact: Contact): Future { 42 | 43 | var msgId: String? = null 44 | 45 | return CompletableFuture.supplyAsync { 46 | when (something) { 47 | 48 | is String -> { 49 | msgId = puppet.messageSendText(id, something).get() 50 | 51 | } 52 | 53 | } 54 | 55 | if (StringUtils.isNotEmpty(msgId)) { 56 | val message = wechaty.messageManager.load(msgId!!) 57 | message.ready().get() 58 | return@supplyAsync message 59 | } 60 | 61 | return@supplyAsync null 62 | 63 | } 64 | } 65 | 66 | fun say(something: Any, vararg varList: Any): Future { 67 | 68 | var msgId: String? 69 | var text: String 70 | 71 | return CompletableFuture.supplyAsync { 72 | when (something) { 73 | //TODO(array) 74 | 75 | is String -> { 76 | var mentionList = listOf() 77 | if (varList.isNotEmpty()) { 78 | val list = varList[0] as? List<*> ?: throw Exception("room say contact args not valid") 79 | list.forEach { 80 | if (it !is Contact) { 81 | throw Exception("mentionList must be contact when not using String array function call.") 82 | } 83 | } 84 | mentionList = list as List 85 | 86 | val mentionAlias = mentionList.map { contact -> 87 | val alias = alias(contact as Contact) 88 | val concatText = if (StringUtils.isNotBlank(alias)) { 89 | alias!! 90 | } else { 91 | contact.name() 92 | } 93 | return@map "@$concatText" 94 | } 95 | val mentionText = mentionAlias.joinToString(separator = FOUR_PER_EM_SPACE) 96 | text = "$mentionText $something" 97 | } else { 98 | text = something 99 | } 100 | 101 | msgId = wechaty.getPuppet().messageSendText(id, text, mentionList.map { c -> (c as Contact).id }).get() 102 | } 103 | is FileBox -> { 104 | msgId = wechaty.getPuppet().messageSendFile(id, something).get() 105 | } 106 | 107 | is Contact -> { 108 | msgId = wechaty.getPuppet().messageSendContact(id, something.id).get() 109 | } 110 | 111 | is UrlLink -> { 112 | msgId = wechaty.getPuppet().messageSendUrl(id, something.payload).get() 113 | } 114 | 115 | is MiniProgram -> { 116 | msgId = wechaty.getPuppet().messageSendMiniProgram(id, something.payload).get() 117 | } 118 | 119 | else -> { 120 | throw Exception("unknown message") 121 | } 122 | 123 | } 124 | 125 | if (msgId != null) { 126 | val msg = wechaty.messageManager.load(msgId!!) 127 | return@supplyAsync msg 128 | } 129 | 130 | return@supplyAsync null 131 | } 132 | } 133 | 134 | fun ready(forceSync: Boolean = false): Future { 135 | return CompletableFuture.supplyAsync { 136 | if (!forceSync && isRead()) { 137 | return@supplyAsync null 138 | } 139 | 140 | if (forceSync) { 141 | puppet.roomPayloadDirty(id).get() 142 | puppet.roomMemberPayloadDirty(id).get() 143 | } 144 | 145 | this.payload = puppet.roomPayload(id).get() 146 | log.debug("get room payload is {} by id {}", payload, id) 147 | if (payload == null) { 148 | throw Exception("no payload") 149 | } 150 | 151 | val memberIdList = puppet.roomMemberList(id).get() 152 | 153 | memberIdList.map { 154 | wechaty.contactManager.load(it) 155 | }.forEach { 156 | it.ready() 157 | } 158 | return@supplyAsync null 159 | } 160 | } 161 | 162 | fun onInvite(listener: InviteListener): Room { 163 | return on(EventEnum.INVITE, listener) 164 | } 165 | 166 | fun onLeave(listener: LeaveListener): Room { 167 | return on(EventEnum.LEAVE, listener) 168 | } 169 | 170 | fun onInnerMessage(listener: RoomInnerMessageListener): Room { 171 | return on(EventEnum.MESSAGE, listener) 172 | } 173 | 174 | fun onJoin(listener: JoinListener): Room { 175 | return on(EventEnum.JOIN, listener); 176 | } 177 | 178 | fun onTopic(listener: TopicListener): Room { 179 | return on(EventEnum.TOPIC, listener) 180 | } 181 | 182 | private fun on(eventName: Event, listener: InviteListener): Room { 183 | super.on(eventName, object : Listener { 184 | override fun handler(vararg any: Any) { 185 | listener.handler(any[0] as Contact, any[1] as RoomInvitation) 186 | } 187 | }) 188 | return this 189 | } 190 | 191 | private fun on(eventName: Event, listener: LeaveListener): Room { 192 | super.on(eventName, object : Listener { 193 | override fun handler(vararg any: Any) { 194 | listener.handler(any[0] as List, any[1] as Contact, any[2] as Date) 195 | } 196 | }) 197 | return this 198 | } 199 | 200 | private fun on(eventName: Event, listenerRoomInner: RoomInnerMessageListener): Room { 201 | super.on(eventName, object : Listener { 202 | override fun handler(vararg any: Any) { 203 | listenerRoomInner.handler(any[0] as Message, any[1] as Date) 204 | } 205 | }) 206 | return this 207 | } 208 | 209 | private fun on(eventName: Event, listener: JoinListener): Room { 210 | super.on(eventName, object : Listener { 211 | override fun handler(vararg any: Any) { 212 | listener.handler(any[0] as List, any[1] as Contact, any[2] as Date) 213 | } 214 | }) 215 | return this 216 | } 217 | 218 | private fun on(eventName: Event, listener: TopicListener): Room { 219 | super.on(eventName, object : Listener { 220 | override fun handler(vararg any: Any) { 221 | listener.handler(any[0] as String, any[1] as String, any[2] as Contact, any[3] as Date) 222 | } 223 | }) 224 | return this 225 | } 226 | 227 | fun add(contact: Contact): Future { 228 | return CompletableFuture.supplyAsync { 229 | puppet.roomAdd(this.id, contact.id).get() 230 | return@supplyAsync null 231 | } 232 | } 233 | 234 | fun del(contact: Contact): Future { 235 | return CompletableFuture.supplyAsync { 236 | puppet.roomDel(this.id, contact.id).get(); 237 | return@supplyAsync null 238 | } 239 | } 240 | 241 | fun quit(): Future { 242 | return CompletableFuture.supplyAsync { 243 | puppet.roomQuit(this.id).get() 244 | return@supplyAsync null 245 | } 246 | } 247 | 248 | fun getTopic(): Future { 249 | 250 | if (!isReady()) { 251 | log.warn("Room topic() room not ready") 252 | throw Exception("not ready") 253 | } 254 | 255 | if (payload != null && payload!!.topic != null) { 256 | return CompletableFuture.supplyAsync { 257 | return@supplyAsync payload!!.topic 258 | } 259 | } else { 260 | val memberIdList = puppet.roomMemberList(id).get() 261 | val memberList = memberIdList.filter { it != puppet.selfId() } 262 | .map { wechaty.contactManager.load(it) } 263 | 264 | var defaultTopic = "" 265 | if (memberList.isNotEmpty()) { 266 | defaultTopic = memberList[0].name() 267 | } 268 | 269 | if (memberList.size >= 2) { 270 | for (index in 1..2) { 271 | defaultTopic += ",${memberList[index].name()}" 272 | } 273 | } 274 | return CompletableFuture.supplyAsync { 275 | return@supplyAsync defaultTopic 276 | } 277 | } 278 | } 279 | 280 | fun setTopic(newTopic: String): Future { 281 | if (!isReady()) { 282 | log.warn("Room topic() room not ready") 283 | throw Exception("not ready") 284 | } 285 | 286 | return CompletableFuture.supplyAsync { 287 | try { 288 | val newTop = puppet.roomTopic(id, newTopic).get() 289 | return@supplyAsync puppet.roomTopic(id, newTopic).get() 290 | } catch (e: Exception) { 291 | log.warn("Room topic(newTopic=$newTopic) exception:$e") 292 | throw Exception(e) 293 | } 294 | } 295 | 296 | } 297 | 298 | @Deprecated("this function is deprecated! see getTopic,setTopic") 299 | fun topic(newTopic: String?): Future { 300 | if (!isReady()) { 301 | log.warn("Room topic() room not ready") 302 | throw Exception("not ready") 303 | } 304 | 305 | if (newTopic == null) { 306 | if (payload != null && payload!!.topic != null) { 307 | return CompletableFuture.supplyAsync { 308 | return@supplyAsync payload!!.topic 309 | } 310 | } else { 311 | val memberIdList = puppet.roomMemberList(id).get() 312 | val memberList = memberIdList.filter { it != puppet.selfId() } 313 | .map { wechaty.contactManager.load(it) } 314 | 315 | var defaultTopic = "" 316 | if (memberList.isNotEmpty()) { 317 | defaultTopic = memberList[0].name() 318 | } 319 | 320 | if (memberList.size >= 2) { 321 | for (index in 1..2) { 322 | defaultTopic += ",${memberList[index].name()}" 323 | } 324 | } 325 | return CompletableFuture.supplyAsync { 326 | return@supplyAsync defaultTopic 327 | } 328 | } 329 | } 330 | 331 | return CompletableFuture.supplyAsync { 332 | try { 333 | return@supplyAsync puppet.roomTopic(id, newTopic).get() 334 | } catch (e: Exception) { 335 | log.warn("Room topic(newTopic=$newTopic) exception:$e") 336 | throw Exception(e) 337 | } 338 | } 339 | 340 | } 341 | 342 | fun announce(text: String?): Future { 343 | return CompletableFuture.supplyAsync { 344 | if (text == null) { 345 | return@supplyAsync puppet.getRoomAnnounce(id).get() 346 | } else { 347 | return@supplyAsync puppet.setRoomAnnounce(id, text) 348 | } 349 | } 350 | } 351 | 352 | fun qrCode(): Future { 353 | return CompletableFuture.supplyAsync { 354 | val qrCodeValue = puppet.roomQRCode(id).get() 355 | return@supplyAsync QrcodeUtils.guardQrCodeValue(qrCodeValue) 356 | } 357 | } 358 | 359 | fun memberAll(query: RoomMemberQueryFilter?): List { 360 | 361 | if (query == null) { 362 | return memberList() 363 | } 364 | 365 | val contactIdList = wechaty.getPuppet().roomMemberSearch(this.id, query).get() 366 | val contactList = contactIdList.map { 367 | wechaty.contactManager.load(id) 368 | } 369 | 370 | return contactList 371 | 372 | } 373 | 374 | fun memberList(): List { 375 | 376 | val memberIdList = wechaty.getPuppet().roomMemberList(this.id).get() 377 | 378 | if (CollectionUtils.isEmpty(memberIdList)) { 379 | return listOf() 380 | } 381 | 382 | val contactList = memberIdList.map { 383 | wechaty.contactManager.load(id) 384 | } 385 | return contactList 386 | 387 | } 388 | 389 | fun alias(contact: Contact): String? { 390 | 391 | val roomMemberPayload = wechaty.getPuppet().roomMemberPayload(this.id, contact.id).get() 392 | 393 | return roomMemberPayload?.roomAlias 394 | 395 | 396 | } 397 | 398 | fun has(contact: Contact): Boolean { 399 | val memberIdList = puppet.roomMemberList(id).get() 400 | if (memberIdList.isEmpty()) { 401 | return false 402 | } 403 | return memberIdList.any { it == contact.id } 404 | } 405 | 406 | fun isRead(): Boolean { 407 | return payload != null 408 | } 409 | 410 | fun owner(): Contact? { 411 | val ownerId = payload?.ownerId 412 | 413 | return if (ownerId.isNullOrBlank()) { 414 | null 415 | } else { 416 | return wechaty.contactManager.load(ownerId) 417 | } 418 | } 419 | 420 | fun avatar(): FileBox { 421 | log.debug("avatar:{}", avatar()) 422 | return puppet.roomAvatar(this.id).get() 423 | } 424 | 425 | companion object { 426 | private val log = LoggerFactory.getLogger(Room::class.java) 427 | } 428 | 429 | } 430 | 431 | val ROOM_EVENT_DICT = mapOf( 432 | "invite" to "tbw", 433 | "join" to "tbw", 434 | "leave" to "tbw", 435 | "message" to "message that received in this room", 436 | "topic" to "tbw" 437 | ) 438 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/RoomInvitation.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | import org.apache.commons.collections4.CollectionUtils 6 | import org.slf4j.LoggerFactory 7 | import java.util.* 8 | 9 | class RoomInvitation(wechaty: Wechaty,val id:String) : Accessory(wechaty){ 10 | 11 | fun accept(){ 12 | wechaty.getPuppet().roomInvitationAccept(this.id) 13 | 14 | val inviter = inviter() 15 | val topic = topic() 16 | 17 | log.debug("accept with room{} & inviter {},read()",topic,inviter) 18 | inviter.ready() 19 | } 20 | 21 | fun roomTopic():String{ 22 | return this.topic(); 23 | } 24 | 25 | fun memberCount():Int{ 26 | val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() 27 | return payload.memberCount ?: 0 28 | } 29 | 30 | fun memeberList():List{ 31 | val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() 32 | 33 | val memberIdList = payload.memberIdList 34 | 35 | if(CollectionUtils.isNotEmpty(memberIdList)){ 36 | 37 | val contactList = memberIdList!!.map { 38 | wechaty.contactManager.load(it) 39 | } 40 | 41 | contactList.forEach{ 42 | it.ready() 43 | } 44 | return contactList 45 | } 46 | 47 | return listOf() 48 | } 49 | 50 | fun date(): Date { 51 | val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() 52 | return Date(payload.timestamp!! * 1000) 53 | } 54 | 55 | fun age():Long{ 56 | val recvDate = this.date() 57 | return System.currentTimeMillis() - recvDate.time; 58 | } 59 | 60 | fun inviter():Contact{ 61 | val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() 62 | return wechaty.contactManager.load(payload.inviterId!!) 63 | } 64 | 65 | fun topic():String { 66 | val payload = wechaty.getPuppet().roomInvitationPayload(this.id).get() 67 | return payload.topic ?:"" 68 | } 69 | 70 | companion object{ 71 | private val log = LoggerFactory.getLogger(RoomInvitation::class.java) 72 | } 73 | 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/Tag.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | import org.slf4j.LoggerFactory 6 | 7 | class Tag(wechaty:Wechaty,val id:String):Accessory(wechaty){ 8 | 9 | fun add(to:Contact){ 10 | wechaty.getPuppet().tagContactAdd(this.id,to.id!!).get() 11 | } 12 | 13 | fun remove(from:Contact){ 14 | wechaty.getPuppet().tagContactRemove(this.id!!,from.id!!).get() 15 | } 16 | 17 | 18 | companion object{ 19 | private val log = LoggerFactory.getLogger(Tag::class.java) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/UrlLink.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.opengraph.openGraph 4 | import io.github.wechaty.schemas.UrlLinkPayload 5 | import org.apache.commons.lang3.StringUtils 6 | import java.net.URL 7 | 8 | class UrlLink(val payload:UrlLinkPayload) { 9 | 10 | fun url (): String { 11 | return payload.url 12 | } 13 | 14 | fun title (): String { 15 | return payload.title 16 | } 17 | 18 | fun thumbnailUrl ():String? { 19 | return this.payload.thumbnailUrl 20 | } 21 | 22 | fun description (): String? { 23 | return this.payload.description 24 | } 25 | 26 | override fun toString(): String { 27 | return "UrlLink(payload=$payload)" 28 | } 29 | 30 | companion object{ 31 | fun create(url:String):UrlLink{ 32 | val meta = openGraph(url).get() 33 | 34 | var description:String? 35 | var imageUrl:String? 36 | val title:String? 37 | 38 | val images = meta.getContent("image") 39 | imageUrl = if(StringUtils.isNotEmpty(images)){ 40 | images 41 | }else{ 42 | val properties = meta.getProperties("image") 43 | if(properties!= null && properties.isNotEmpty()){ 44 | properties[0]!!.extendedData.getContent("url") 45 | }else{ 46 | "" 47 | } 48 | } 49 | 50 | if(!StringUtils.startsWith(images,"http")){ 51 | val url1 = URL(url) 52 | imageUrl = URL(url1,imageUrl).toString() 53 | } 54 | 55 | title = meta.getContent("title") 56 | 57 | description = meta.getContent("description") 58 | 59 | description = if(StringUtils.isEmpty(description)){ 60 | title 61 | }else{ 62 | description 63 | } 64 | 65 | val payload = UrlLinkPayload(title,url).apply { 66 | this.description = description 67 | this.thumbnailUrl = imageUrl 68 | } 69 | 70 | return UrlLink(payload) 71 | 72 | // 73 | } 74 | 75 | } 76 | 77 | } 78 | 79 | fun main(){ 80 | 81 | val create = UrlLink.create("https://xilidou.com") 82 | 83 | println(create) 84 | 85 | } 86 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/manager/ContactManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user.manager 2 | 3 | import com.github.benmanes.caffeine.cache.Cache 4 | import com.github.benmanes.caffeine.cache.Caffeine 5 | import io.github.wechaty.Accessory 6 | import io.github.wechaty.Wechaty 7 | import io.github.wechaty.schemas.ContactQueryFilter 8 | import io.github.wechaty.schemas.RoomMemberQueryFilter 9 | import io.github.wechaty.user.Contact 10 | import io.github.wechaty.user.ContactSelf 11 | import io.github.wechaty.user.Tag 12 | import org.apache.commons.collections4.CollectionUtils 13 | import org.slf4j.LoggerFactory 14 | import java.util.* 15 | 16 | class ContactManager(wechaty: Wechaty):Accessory(wechaty) { 17 | 18 | private val contactCache: Cache = Caffeine.newBuilder().build() 19 | 20 | fun load(id:String):Contact{ 21 | return contactCache.get(id){ 22 | Contact(wechaty, id) 23 | }!! 24 | } 25 | 26 | fun loadSelf(id:String):ContactSelf{ 27 | val contactSelf = ContactSelf(wechaty, id) 28 | contactCache.put(id,contactSelf) 29 | return contactSelf 30 | 31 | } 32 | 33 | 34 | 35 | 36 | fun find(queryFilter: ContactQueryFilter):Contact?{ 37 | 38 | val findAll = findAll(queryFilter) 39 | 40 | if(CollectionUtils.isEmpty(findAll)){ 41 | return null 42 | } 43 | 44 | return findAll[0] 45 | } 46 | 47 | fun findAll(queryFilter: ContactQueryFilter):List{ 48 | return try{ 49 | val contactIdList = wechaty.getPuppet().contactSearch(queryFilter).get() 50 | 51 | val contactList = contactIdList.mapNotNull { 52 | try { 53 | load(it) 54 | } catch (e: Exception) { 55 | log.error("findAll() contact.ready() exception:{}", e) 56 | null 57 | } 58 | } 59 | contactList 60 | }catch (e:Exception){ 61 | log.error("this.puppet.contactFindAll() rejected: {}",e) 62 | listOf() 63 | } 64 | 65 | } 66 | 67 | fun tags():List{ 68 | val tagIdList = wechaty.getPuppet().tagContactList().get() 69 | return tagIdList.map { 70 | wechaty.tagManager.load(it) 71 | } 72 | } 73 | 74 | companion object { 75 | private val log = LoggerFactory.getLogger(Contact::class.java) 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/manager/FriendshipManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user.manager 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | import io.github.wechaty.schemas.FriendshipPayload 6 | import io.github.wechaty.schemas.FriendshipSearchCondition 7 | import io.github.wechaty.user.Contact 8 | import io.github.wechaty.user.Friendship 9 | import io.github.wechaty.utils.JsonUtils 10 | import org.apache.commons.lang3.StringUtils 11 | import org.slf4j.Logger 12 | import org.slf4j.LoggerFactory 13 | 14 | class FriendshipManager (wechaty: Wechaty): Accessory(wechaty){ 15 | 16 | fun load(id:String): Friendship { 17 | return Friendship(wechaty,id) 18 | } 19 | 20 | fun search(queryFilter: FriendshipSearchCondition): Contact?{ 21 | log.debug("query filter {}",queryFilter) 22 | val contactId = wechaty.getPuppet().friendshipSearch(queryFilter).get(); 23 | if(StringUtils.isEmpty(contactId)){ 24 | return null 25 | } 26 | val contact = wechaty.contactManager.load(contactId!!) 27 | contact.ready() 28 | return contact 29 | } 30 | 31 | fun add(contact: Contact,hello:String){ 32 | log.debug("add {},{}",contact,hello) 33 | wechaty.getPuppet().friendshipAdd(contact.id,hello).get() 34 | } 35 | 36 | fun del(contact: Contact){ 37 | log.debug("del {}",contact) 38 | throw Exception("to be implemented") 39 | } 40 | 41 | fun fromJSON(payload:String):Friendship{ 42 | val readValue = JsonUtils.readValue(payload) 43 | return fromJSON(readValue) 44 | } 45 | 46 | fun fromJSON(friendshipPayload: FriendshipPayload):Friendship{ 47 | wechaty.getPuppet().friendshipPayload(friendshipPayload.id!!,friendshipPayload).get() 48 | return load(friendshipPayload.id!!) 49 | } 50 | 51 | companion object{ 52 | private val log: Logger = LoggerFactory.getLogger(FriendshipManager::class.java) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/manager/ImageManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user.manager 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | import io.github.wechaty.user.Image 6 | import org.slf4j.LoggerFactory 7 | 8 | class ImageManager (wechaty: Wechaty): Accessory(wechaty){ 9 | 10 | fun create(id:String):Image{ 11 | return Image(wechaty,id) 12 | } 13 | 14 | companion object { 15 | private val log = LoggerFactory.getLogger(ImageManager::class.java) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/manager/MessageManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user.manager 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | import io.github.wechaty.schemas.MessageQueryFilter 6 | import io.github.wechaty.user.Contact 7 | import io.github.wechaty.user.Message 8 | import org.apache.commons.collections4.CollectionUtils 9 | import org.slf4j.LoggerFactory 10 | import java.util.* 11 | 12 | class MessageManager (wechaty: Wechaty):Accessory(wechaty){ 13 | 14 | fun find(query: MessageQueryFilter): Message?{ 15 | val messageList = findAll(query) 16 | 17 | if(CollectionUtils.isEmpty(messageList)){ 18 | return null 19 | } 20 | 21 | if(messageList.size > 1){ 22 | log.warn("findAll() got more than one({}) result",messageList.size) 23 | } 24 | 25 | return messageList[0] 26 | } 27 | 28 | fun findAll(query: MessageQueryFilter):List{ 29 | log.debug("findAll({})",query) 30 | return try { 31 | val messageIdList = wechaty.getPuppet().messageSearch(query).get() 32 | val messageList = messageIdList.map { 33 | load(it) 34 | } 35 | return messageList.mapNotNull { 36 | try { 37 | it.ready() 38 | it 39 | } catch (e: Exception) { 40 | log.warn("findAll() message.ready() rejection: {}", e) 41 | null 42 | } 43 | } 44 | }catch (e:Exception){ 45 | log.warn("findAll() rejected: {}", e) 46 | listOf() 47 | } 48 | } 49 | 50 | fun load(id:String):Message{ 51 | return Message(wechaty,id) 52 | } 53 | 54 | fun create(id:String):Message{ 55 | return load(id) 56 | } 57 | 58 | companion object { 59 | private val log = LoggerFactory.getLogger(Contact::class.java) 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/manager/PuppetManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user.manager 2 | 3 | import io.github.wechaty.Puppet 4 | import io.github.wechaty.WechatyOptions 5 | import io.github.wechaty.schemas.PuppetOptions 6 | import io.github.wechaty.utils.JsonUtils 7 | import org.reflections.Reflections 8 | import org.reflections.util.ClasspathHelper 9 | import org.reflections.util.ConfigurationBuilder 10 | import org.slf4j.LoggerFactory 11 | import java.util.concurrent.CompletableFuture 12 | import java.util.concurrent.Future 13 | 14 | const val REFLECTION_BASE_PACKAGE = "io.github.wechaty" 15 | 16 | class PuppetManager { 17 | 18 | companion object { 19 | private val log = LoggerFactory.getLogger(PuppetManager::class.java) 20 | 21 | @JvmStatic 22 | fun resolveInstance(wechatyOptions: WechatyOptions): Future { 23 | log.info("PuppetManager resolveInstance(${JsonUtils.write(wechatyOptions)})") 24 | 25 | val reflections = Reflections(ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(REFLECTION_BASE_PACKAGE, Thread.currentThread().contextClassLoader))) 26 | 27 | val subTypes: Set<*> = reflections.getSubTypesOf(Puppet::class.java) 28 | if (subTypes.isEmpty()) { 29 | throw java.lang.RuntimeException("expect one puppet,but can not found any one.") 30 | } 31 | 32 | val filterPuppet = subTypes.filter { 33 | val clazz = it as Class<*> 34 | clazz.name == wechatyOptions.puppet 35 | } 36 | 37 | if (filterPuppet.size > 1) { 38 | throw RuntimeException("expect one puppet,but found ${subTypes.size}") 39 | } 40 | val clazz = filterPuppet.first() as Class<*> 41 | val declaredConstructor = clazz.getDeclaredConstructor(PuppetOptions::class.java) 42 | return CompletableFuture.completedFuture(declaredConstructor.newInstance(wechatyOptions.puppetOptions!!) as Puppet) 43 | } 44 | } 45 | 46 | 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/manager/RoomInvitationManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user.manager 2 | 3 | import io.github.wechaty.Accessory 4 | import io.github.wechaty.Wechaty 5 | import io.github.wechaty.user.Contact 6 | import io.github.wechaty.user.RoomInvitation 7 | import org.slf4j.LoggerFactory 8 | 9 | class RoomInvitationManager (wechaty: Wechaty):Accessory(wechaty){ 10 | 11 | 12 | fun load(id:String):RoomInvitation{ 13 | return RoomInvitation(wechaty,id) 14 | } 15 | 16 | 17 | companion object { 18 | private val log = LoggerFactory.getLogger(Contact::class.java) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/manager/RoomManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user.manager 2 | 3 | import com.github.benmanes.caffeine.cache.Cache 4 | import com.github.benmanes.caffeine.cache.Caffeine 5 | import io.github.wechaty.Accessory 6 | import io.github.wechaty.Wechaty 7 | import io.github.wechaty.schemas.RoomQueryFilter 8 | import io.github.wechaty.user.Contact 9 | import io.github.wechaty.user.Room 10 | import org.apache.commons.collections4.CollectionUtils 11 | import org.slf4j.LoggerFactory 12 | 13 | class RoomManager(wechaty: Wechaty) : Accessory(wechaty) { 14 | 15 | private val roomCache: Cache = Caffeine.newBuilder().build() 16 | 17 | fun create(contactList: List, topic: String?): Room { 18 | if (contactList.size < 2) { 19 | throw Exception("contactList need at least 2 contact to create a new room") 20 | } 21 | 22 | val contactIdList = contactList.map { 23 | it.id 24 | } 25 | 26 | try { 27 | val roomId = wechaty.getPuppet().roomCreate(contactIdList, topic).get() 28 | val room = load(roomId) 29 | return room 30 | } catch (e: Exception) { 31 | log.error("create() room error", e) 32 | throw e 33 | } 34 | } 35 | 36 | fun findAll(query: RoomQueryFilter): List { 37 | 38 | log.debug("findAll {}", query) 39 | 40 | return try { 41 | val roomIdList = wechaty.getPuppet().roomSearch(query).get() 42 | roomIdList.map { 43 | load(it) 44 | }.mapNotNull { 45 | try { 46 | it.ready().get() 47 | it 48 | } catch (e: Exception) { 49 | log.error("findAll() room.ready() rejection {}", e) 50 | null 51 | } 52 | } 53 | } catch (e: Exception) { 54 | log.error("findAll() rejected: {}", e) 55 | listOf() 56 | } 57 | 58 | } 59 | 60 | fun find(query: RoomQueryFilter): Room? { 61 | val roomList = findAll(query) 62 | if (CollectionUtils.isEmpty(roomList)) { 63 | return null 64 | } 65 | 66 | if (roomList.size > 1) { 67 | log.warn("find got more then one{} result", roomList.size) 68 | } 69 | 70 | roomList.forEach { 71 | val valid = wechaty.getPuppet().roomValidate(it.id).get() 72 | if (valid) { 73 | log.debug("find() confirm room{} with id={} is valid result, return it.", it, it.id) 74 | return it 75 | } else { 76 | log.debug("find() confirm room{} with id={} is INVALID result, try next", it, it.id) 77 | } 78 | } 79 | 80 | return null 81 | 82 | } 83 | 84 | fun load(id: String): Room { 85 | return roomCache.get(id) { 86 | Room(wechaty, id) 87 | }!! 88 | } 89 | 90 | companion object { 91 | private val log = LoggerFactory.getLogger(RoomManager::class.java) 92 | } 93 | 94 | 95 | } 96 | 97 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/user/manager/TagManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user.manager 2 | 3 | import com.github.benmanes.caffeine.cache.Cache 4 | import com.github.benmanes.caffeine.cache.Caffeine 5 | import io.github.wechaty.Accessory 6 | import io.github.wechaty.Wechaty 7 | import io.github.wechaty.user.Contact 8 | import io.github.wechaty.user.Tag 9 | import org.slf4j.LoggerFactory 10 | 11 | class TagManager(wechaty: Wechaty):Accessory(wechaty){ 12 | 13 | private val tagCache: Cache = Caffeine.newBuilder().build() 14 | 15 | fun load(id:String):Tag{ 16 | return tagCache.get(id){ 17 | Tag(wechaty, id) 18 | }!! 19 | } 20 | 21 | fun get(id:String):Tag{ 22 | return load(id) 23 | } 24 | 25 | fun delete(tag:Tag,target: Contact){ 26 | wechaty.getPuppet().tagContactDelete(tag.id) 27 | } 28 | 29 | companion object{ 30 | private val log = LoggerFactory.getLogger(TagManager::class.java) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /wechaty/src/main/kotlin/io/github/wechaty/utils/QrcodeUtils.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.utils 2 | 3 | import com.google.zxing.BarcodeFormat 4 | import com.google.zxing.EncodeHintType 5 | import com.google.zxing.MultiFormatWriter 6 | import com.google.zxing.WriterException 7 | import com.google.zxing.common.BitMatrix 8 | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel 9 | import java.util.* 10 | 11 | const val MAX_LEN = 7089 12 | 13 | class QrcodeUtils { 14 | 15 | 16 | companion object { 17 | 18 | @JvmStatic 19 | fun getQr(text: String): String { 20 | var s: String = "生成二维码失败" 21 | val width = 40 22 | val height = 40 23 | // 用于设置QR二维码参数 24 | val qrParam: Hashtable = Hashtable() 25 | // 设置QR二维码的纠错级别——这里选择最低L级别 26 | qrParam[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.L 27 | qrParam[EncodeHintType.CHARACTER_SET] = "utf-8" 28 | try { 29 | val bitMatrix: BitMatrix = MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, qrParam) 30 | s = toAscii(bitMatrix) 31 | } catch (e: WriterException) { 32 | // TODO Auto-generated catch block 33 | e.printStackTrace() 34 | } 35 | return s 36 | } 37 | 38 | fun toAscii(bitMatrix: BitMatrix): String { 39 | val sb = StringBuilder() 40 | for (rows in 0 until bitMatrix.getHeight()) { 41 | for (cols in 0 until bitMatrix.getWidth()) { 42 | val x: Boolean = bitMatrix.get(rows, cols) 43 | if (!x) { 44 | // white 45 | sb.append("\u001b[47m \u001b[0m") 46 | } else { 47 | sb.append("\u001b[30m \u001b[0m") 48 | } 49 | } 50 | sb.append("\n") 51 | } 52 | return sb.toString() 53 | } 54 | 55 | @JvmStatic 56 | fun guardQrCodeValue(value: String): String { 57 | if (value.length > MAX_LEN) { 58 | throw Exception("QR Code Value is larger then the max len. Did you return the image base64 text by mistake? See: https://github.com/wechaty/wechaty/issues/1889") 59 | } 60 | return value 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /wechaty/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /wechaty/src/test/kotlin/io/github/wechaty/user/RoomTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.wechaty.user 2 | 3 | import io.github.wechaty.MockPuppet 4 | import io.github.wechaty.Puppet 5 | import io.github.wechaty.Wechaty 6 | import io.github.wechaty.WechatyOptions 7 | import io.github.wechaty.schemas.PuppetOptions 8 | import org.junit.After 9 | import org.junit.Assert 10 | import org.junit.Before 11 | import org.junit.Test 12 | import org.mockito.Mockito.`when` 13 | import org.mockito.Mockito.spy 14 | 15 | /** 16 | * @author renxiaoya 17 | * @date 2020-05-29 18 | */ 19 | const val EXPECTED_ROOM_ID = "roomId" 20 | const val EXPECTED_ROOM_TOPIC = "test-topic" 21 | const val EXPECTED_CONTACT_1_ID = "contact1" 22 | const val EXPECTED_CONTACT_1_ALIAS = "little1" 23 | const val EXPECTED_CONTACT_2_ID = "contact2" 24 | const val EXPECTED_CONTACT_2_ALIAS = "big2" 25 | 26 | class RoomTest { 27 | 28 | lateinit var wechaty: Wechaty 29 | 30 | lateinit var room: Room 31 | 32 | lateinit var puppet: Puppet 33 | 34 | @Before 35 | fun setUp() { 36 | puppet = MockPuppet(PuppetOptions()) 37 | val wechatyOptions = WechatyOptions() 38 | wechatyOptions.name = "MockWechaty" 39 | wechatyOptions.puppet = "io.github.wechaty.MockPuppet" 40 | wechatyOptions.puppetOptions = PuppetOptions() 41 | wechaty = Wechaty.instance(wechatyOptions) 42 | wechaty.start() 43 | 44 | room = wechaty.roomManager.load(EXPECTED_ROOM_ID) 45 | room.sync() 46 | } 47 | 48 | @After 49 | fun tearDown() { 50 | wechaty.stop() 51 | } 52 | 53 | @Test 54 | fun sayStringWithMentionList() { 55 | val contact1 = wechaty.contactManager.load(EXPECTED_CONTACT_1_ID) 56 | val contact2 = wechaty.contactManager.load(EXPECTED_CONTACT_2_ID) 57 | contact1.sync() 58 | contact2.sync() 59 | 60 | val spyRoom: Room = spy(room) 61 | `when`(spyRoom.alias(contact1)).thenReturn("test-contact1-alias") 62 | `when`(spyRoom.alias(contact2)).thenReturn("test-contact2-alias") 63 | 64 | val text = "test-text" 65 | val resMsg = spyRoom.say(text, listOf(contact1, contact2)).get() 66 | 67 | Assert.assertEquals((resMsg as Message).id, "mock-msg-$EXPECTED_ROOM_ID") 68 | } 69 | } 70 | --------------------------------------------------------------------------------