Repository: lz1998/rs-qq Branch: master Commit: 46c44a3eda7a Files: 263 Total size: 894.4 KB Directory structure: gitextract_sa4m0udb/ ├── .gitignore ├── Cargo.toml ├── LICENSE ├── examples/ │ ├── .gitignore │ ├── Cargo.toml │ ├── password_login/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── qrcode_login/ │ │ ├── Cargo.toml │ │ └── src/ │ │ └── main.rs │ ├── ricq-axum-api/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src/ │ │ ├── bin/ │ │ │ └── main.rs │ │ ├── handler/ │ │ │ ├── bot.rs │ │ │ ├── mod.rs │ │ │ ├── password.rs │ │ │ └── qrcode.rs │ │ ├── lib.rs │ │ ├── processor.rs │ │ └── u8_protocol.rs │ └── token_login/ │ ├── Cargo.toml │ └── src/ │ └── main.rs ├── ricq/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── client/ │ │ ├── api/ │ │ │ ├── friend.rs │ │ │ ├── group.rs │ │ │ ├── login.rs │ │ │ └── mod.rs │ │ ├── event.rs │ │ ├── handler/ │ │ │ └── mod.rs │ │ ├── highway/ │ │ │ ├── codec.rs │ │ │ ├── mod.rs │ │ │ └── net.rs │ │ ├── mod.rs │ │ ├── net.rs │ │ ├── processor/ │ │ │ ├── c2c/ │ │ │ │ ├── friend_msg.rs │ │ │ │ ├── friend_system_msg.rs │ │ │ │ ├── group_system_msg.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── new_member.rs │ │ │ │ └── temp_session.rs │ │ │ ├── config_push_svc.rs │ │ │ ├── message_svc.rs │ │ │ ├── mod.rs │ │ │ ├── online_push.rs │ │ │ ├── reg_prxy_svc.rs │ │ │ ├── stat_svc.rs │ │ │ └── wtlogin.rs │ │ ├── qimei.rs │ │ └── tcp.rs │ ├── config.rs │ ├── ext/ │ │ ├── common.rs │ │ ├── image.rs │ │ ├── login.rs │ │ ├── mod.rs │ │ └── reconnect.rs │ ├── lib.rs │ ├── qsign.rs │ └── structs/ │ ├── image_info.rs │ └── mod.rs ├── ricq-core/ │ ├── Cargo.toml │ ├── build.rs │ └── src/ │ ├── binary/ │ │ ├── binary_reader.rs │ │ ├── binary_writer.rs │ │ ├── mod.rs │ │ └── packet_writer.rs │ ├── command/ │ │ ├── common.rs │ │ ├── config_push_svc/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── friendlist/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── group_anonymous_generate_nick/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── group_member_card/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── heartbeat/ │ │ │ ├── builder.rs │ │ │ └── mod.rs │ │ ├── img_store/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── long_conn/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── longmsg/ │ │ │ ├── builder.rs │ │ │ └── mod.rs │ │ ├── message_svc/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── multi_msg/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── oidb_svc/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── online_push/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── pb_message_svc/ │ │ │ ├── builder.rs │ │ │ └── mod.rs │ │ ├── profile_service/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── ptt_center_svr/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── ptt_store/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── reg_prxy_svc/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── signature/ │ │ │ ├── builder.rs │ │ │ └── mod.rs │ │ ├── stat_svc/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── summary_card/ │ │ │ ├── builder.rs │ │ │ ├── decoder.rs │ │ │ └── mod.rs │ │ ├── visitor_svc/ │ │ │ ├── builder.rs │ │ │ └── mod.rs │ │ └── wtlogin/ │ │ ├── builder.rs │ │ ├── decoder.rs │ │ ├── mod.rs │ │ ├── tlv_reader.rs │ │ └── tlv_writer.rs │ ├── common.rs │ ├── crypto/ │ │ ├── encrypt.rs │ │ ├── mod.rs │ │ └── qqtea.rs │ ├── error.rs │ ├── hex.rs │ ├── highway/ │ │ └── mod.rs │ ├── jce/ │ │ └── mod.rs │ ├── lib.rs │ ├── msg/ │ │ ├── elem/ │ │ │ ├── anonymous.rs │ │ │ ├── at.rs │ │ │ ├── face.rs │ │ │ ├── flash_image.rs │ │ │ ├── friend_image.rs │ │ │ ├── group_image.rs │ │ │ ├── light_app.rs │ │ │ ├── market_face.rs │ │ │ ├── mod.rs │ │ │ ├── reply.rs │ │ │ ├── rich_msg.rs │ │ │ ├── text.rs │ │ │ └── video_file.rs │ │ ├── fragment.rs │ │ ├── macros.rs │ │ └── mod.rs │ ├── pb/ │ │ ├── cmd0x346/ │ │ │ └── cmd0x346.proto │ │ ├── cmd0x352/ │ │ │ └── cmd0x352.proto │ │ ├── cmd0x388/ │ │ │ └── cmd0x388.proto │ │ ├── cmd0x3bb/ │ │ │ └── cmd0x3bb.proto │ │ ├── cmd0x6ff/ │ │ │ ├── smbcmd0x519.proto │ │ │ └── subcmd0x501.proto │ │ ├── cmd0x899/ │ │ │ └── cmd0x899.proto │ │ ├── data.proto │ │ ├── longmsg/ │ │ │ └── longmsg.proto │ │ ├── mod.rs │ │ ├── msf/ │ │ │ └── register_proxy.proto │ │ ├── msg/ │ │ │ ├── TextMsgExt.proto │ │ │ ├── head.proto │ │ │ ├── msg.proto │ │ │ ├── objmsg.proto │ │ │ └── report.proto │ │ ├── msgtype0x210/ │ │ │ └── subMsgType0x27.proto │ │ ├── multimsg/ │ │ │ └── multimsg.proto │ │ ├── notify/ │ │ │ └── group0x857.proto │ │ ├── oidb/ │ │ │ ├── oidb.proto │ │ │ ├── oidb0x6d6.proto │ │ │ ├── oidb0x6d8.proto │ │ │ ├── oidb0x758.proto │ │ │ ├── oidb0x769.proto │ │ │ ├── oidb0x88d.proto │ │ │ ├── oidb0x8a7.proto │ │ │ ├── oidb0x8fc.proto │ │ │ ├── oidb0x990.proto │ │ │ ├── oidb0xb77.proto │ │ │ ├── oidb0xe07.proto │ │ │ ├── oidb0xeac.proto │ │ │ └── oidb0xeb7.proto │ │ ├── online_status/ │ │ │ └── OnlineStatusExtInfo.java.proto │ │ ├── profilecard/ │ │ │ ├── busi.proto │ │ │ └── gate.proto │ │ ├── short_video/ │ │ │ └── short_video.proto │ │ ├── sig_act/ │ │ │ └── sig_act.proto │ │ └── structmsg/ │ │ └── structmsg.proto │ ├── protocol/ │ │ ├── device.rs │ │ ├── mod.rs │ │ ├── oicq.rs │ │ ├── packet.rs │ │ ├── qimei.rs │ │ ├── sig.rs │ │ ├── transport.rs │ │ └── version.rs │ ├── structs.rs │ ├── token.rs │ ├── utils/ │ │ ├── mod.rs │ │ └── option_set.rs │ └── wtlogin.rs ├── ricq-guild/ │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src/ │ ├── client/ │ │ ├── builder.rs │ │ ├── decoder.rs │ │ ├── mod.rs │ │ └── processor.rs │ ├── lib.rs │ └── protocol/ │ ├── core/ │ │ ├── cmd0x346/ │ │ │ └── cmd0x346.proto │ │ ├── cmd0x352/ │ │ │ └── cmd0x352.proto │ │ ├── cmd0x388/ │ │ │ └── cmd0x388.proto │ │ ├── cmd0x3bb/ │ │ │ └── cmd0x3bb.proto │ │ ├── cmd0x6ff/ │ │ │ ├── smbcmd0x519.proto │ │ │ └── subcmd0x501.proto │ │ ├── cmd0x899/ │ │ │ └── cmd0x899.proto │ │ ├── data.proto │ │ ├── longmsg/ │ │ │ └── longmsg.proto │ │ ├── msf/ │ │ │ └── register_proxy.proto │ │ ├── msg/ │ │ │ ├── TextMsgExt.proto │ │ │ ├── head.proto │ │ │ ├── msg.proto │ │ │ ├── objmsg.proto │ │ │ └── report.proto │ │ ├── msgtype0x210/ │ │ │ └── subMsgType0x27.proto │ │ ├── multimsg/ │ │ │ └── multimsg.proto │ │ ├── notify/ │ │ │ └── group0x857.proto │ │ ├── oidb/ │ │ │ ├── oidb.proto │ │ │ ├── oidb0x6d6.proto │ │ │ ├── oidb0x758.proto │ │ │ ├── oidb0x769.proto │ │ │ ├── oidb0x88d.proto │ │ │ ├── oidb0x8a7.proto │ │ │ ├── oidb0x8fc.proto │ │ │ ├── oidb0x990.proto │ │ │ ├── oidb0xb77.proto │ │ │ ├── oidb0xe07.proto │ │ │ ├── oidb0xeac.proto │ │ │ └── oidb0xeb7.proto │ │ ├── online_status/ │ │ │ └── OnlineStatusExtInfo.java.proto │ │ ├── profilecard/ │ │ │ ├── busi.proto │ │ │ └── gate.proto │ │ ├── short_video/ │ │ │ └── short_video.proto │ │ ├── sig_act/ │ │ │ └── sig_act.proto │ │ └── structmsg/ │ │ └── structmsg.proto │ ├── mod.rs │ └── protobuf/ │ ├── GuildChannelBase.proto │ ├── GuildFeedCloudMeta.proto │ ├── GuildFeedCloudRead.proto │ ├── GuildWriter.proto │ ├── MsgResponsesSvr.proto │ ├── common.proto │ ├── msgpush.proto │ ├── oidb0xf62.proto │ ├── servtype.proto │ ├── synclogic.proto │ └── unknown.proto └── rust-toolchain.toml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea /target /examples/target /examples/*/target .DS_Store Cargo.lock .vscode qrcode.png device.json test.png *.amr *.silk *.token *.mp4 ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ "ricq", "ricq-core", "ricq-guild" ] [workspace.dependencies] async-trait = "0.1" bytes = "1" cached = { version = "0.35", default-features = false } derivative = "2" flate2 = { version = "1", features = ["rust_backend"], default-features = false } futures-util = "0.3" image = "0.24" jcers = "0.1" md5 = "0.7" prost = { version = "0.9", default-features = false } rand = "0.8" serde = "1" tokio = "1" tokio-util = "0.7" tracing = "0.1" byteorder = "1" generic-array = "0.14" p256 = { version = "0.10", default-features = false } prost-types = "0.9" thiserror = "1" dynamic-protobuf = "0" reqwest = "0.11" crypto-common = "0.1" serde_json = "1.0" block-padding = "0.3" spki = "0.7" base64 = "0.21" aes = "0.8" rsa = "0.9" x509-cert = "0.2" chrono = "0.4" cbc = "0.1" sha2 = "0.10" ================================================ FILE: LICENSE ================================================ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ================================================ FILE: examples/.gitignore ================================================ static/ ================================================ FILE: examples/Cargo.toml ================================================ [workspace] members = ["*"] exclude = ["target", "static"] [profile.release] opt-level = 'z' debug = false lto = true incremental = false codegen-units = 1 strip = true ================================================ FILE: examples/password_login/Cargo.toml ================================================ [package] name = "password_login" version = "0.1.0" edition = "2021" [dependencies] ricq = { path = "../../ricq" } tokio = { version = "1", features = ["full"] } tokio-util = { version = "0.7", features = ["codec"] } futures-util = "0.3" tracing = "0.1" serde_json = "1" tracing-subscriber = { version = "0.3", features = ["fmt", "local-time"] } time = { version = "0.3", features = ["macros", "local-offset"] } rand = "0.8" ================================================ FILE: examples/password_login/src/main.rs ================================================ use std::sync::Arc; use std::time::Duration; use futures_util::StreamExt; use rand::prelude::StdRng; use rand::SeedableRng; use tokio_util::codec::{FramedRead, LinesCodec}; use tracing::Level; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use ricq::client::qimei::get_qimei; use ricq::client::{Connector as _, DefaultConnector}; use ricq::ext::common::after_login; use ricq::handler::DefaultHandler; use ricq::qsign::QSignClient; use ricq::structs::ExtOnlineStatus; use ricq::{Client, Device, Protocol}; use ricq::{LoginDeviceLocked, LoginNeedCaptcha, LoginResponse, LoginSuccess, LoginUnknownStatus}; #[tokio::main(flavor = "current_thread")] async fn main() { tracing_subscriber::registry() .with( tracing_subscriber::fmt::layer() .with_target(true) .with_timer(tracing_subscriber::fmt::time::OffsetTime::new( time::UtcOffset::__from_hms_unchecked(8, 0, 0), time::macros::format_description!( "[year repr:last_two]-[month]-[day] [hour]:[minute]:[second]" ), )), ) .with( tracing_subscriber::filter::Targets::new() .with_target("ricq", Level::DEBUG) .with_target("password_login", Level::DEBUG), ) .init(); // load uin and password from env let uin: i64 = std::env::var("UIN") .expect("failed to read UIN from env") .parse() .expect("failed to parse UIN"); let password = std::env::var("PASSWORD").expect("failed to read PASSWORD from env"); let mut rng = StdRng::seed_from_u64(uin as u64); let version = Protocol::AndroidPhone.into(); let mut device = Device::random_with_rng(&mut rng); let qimei = get_qimei(&mut rng, &device, &version) .await .expect("failed to get qimei"); device.set_qimei(qimei); let client = Arc::new(Client::new( device, version, Arc::new( QSignClient::new( String::from("http://localhost:8080"), String::from("114514"), Duration::from_secs(60), ) .unwrap(), ), DefaultHandler, )); let handle = tokio::spawn({ let client = client.clone(); // 连接所有服务器,哪个最快用哪个,可以使用 TcpStream::connect 代替 let stream = DefaultConnector.connect(&client).await.unwrap(); async move { client.start(stream).await } }); tokio::task::yield_now().await; // 等一下,确保连上了 let mut resp = client .password_login(uin, &password) .await .expect("failed to login with password"); loop { match resp { LoginResponse::Success(LoginSuccess { ref account_info, .. }) => { tracing::info!("login success: {:?}", account_info); break; } LoginResponse::DeviceLocked(LoginDeviceLocked { ref sms_phone, ref verify_url, ref message, .. }) => { tracing::info!("device locked: {:?}", message); tracing::info!("sms_phone: {:?}", sms_phone); tracing::info!("verify_url: {:?}", verify_url); tracing::info!("手机打开url,处理完成后重启程序"); std::process::exit(0); //也可以走短信验证 // resp = client.request_sms().await.expect("failed to request sms"); } LoginResponse::NeedCaptcha(LoginNeedCaptcha { ref verify_url, // 图片应该没了 image_captcha: ref _image_captcha, .. }) => { tracing::info!("滑块URL: {:?}", verify_url); tracing::info!("请输入ticket:"); let mut reader = FramedRead::new(tokio::io::stdin(), LinesCodec::new()); let ticket = reader .next() .await .transpose() .expect("failed to read ticket") .expect("failed to read ticket"); resp = client .submit_ticket(&ticket) .await .expect("failed to submit ticket"); } LoginResponse::DeviceLockLogin { .. } => { resp = client .device_lock_login() .await .expect("failed to login with device lock"); } LoginResponse::AccountFrozen => { panic!("account frozen"); } LoginResponse::TooManySMSRequest => { panic!("too many sms request"); } LoginResponse::UnknownStatus(LoginUnknownStatus { ref status, ref tlv_map, ref message, }) => { panic!( "unknown login status: {:?}, {:?}, {:?}", message, status, tlv_map ); } } } tracing::info!("{:?}", resp); after_login(&client).await; { tracing::info!("{:?}", client.get_friend_list().await); tracing::info!("{:?}", client.get_group_list().await); } let d = client.get_allowed_clients().await; tracing::info!("{:?}", d); // 等一下,收到 ConfigPushSvc.PushReq 才可以发 // use ricq::msg::MessageChain; // tokio::time::sleep(std::time::Duration::from_secs(1)).await; // let img_bytes = tokio::fs::read("test.png").await.unwrap(); // let group_image = client // .upload_group_image(982166018, img_bytes) // .await // .unwrap(); // let mut chain = MessageChain::default(); // chain.push(group_image); // client.send_group_message(982166018, chain).await.ok(); let aaa = client .update_online_status(ExtOnlineStatus::StudyOnline) .await; println!("{:?}", aaa); // client.delete_essence_message(1095020555, 8114, 2107692422).await // let mem_info = client.get_group_member_info(335783090, 875543543).await; // println!("{:?}", mem_info); // let mem_list = client.get_group_member_list(335783090).await; // println!("{:?}", mem_list); handle.await.unwrap(); } ================================================ FILE: examples/qrcode_login/Cargo.toml ================================================ [package] name = "qrcode_login" version = "0.1.0" edition = "2021" [dependencies] ricq = { path = "../../ricq" } tokio = { version = "1", features = ["full"] } serde_json = "1" bytes = "1" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt"] } ================================================ FILE: examples/qrcode_login/src/main.rs ================================================ use std::path::Path; use std::sync::Arc; use bytes::Bytes; use tokio::time::{sleep, Duration}; use tracing::Level; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use ricq::client::{Connector as _, DefaultConnector}; use ricq::ext::common::after_login; use ricq::handler::DefaultHandler; use ricq::qsign::QSignClient; use ricq::{Client, Device, Protocol}; use ricq::{LoginResponse, QRCodeConfirmed, QRCodeImageFetch, QRCodeState}; #[tokio::main(flavor = "current_thread")] async fn main() { tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().with_target(true)) .with( tracing_subscriber::filter::Targets::new() .with_target("ricq", Level::DEBUG) .with_target("qrcode_login", Level::DEBUG), ) .init(); let device = match Path::new("device.json").exists() { true => serde_json::from_str( &tokio::fs::read_to_string("device.json") .await .expect("failed to read device.json"), ) .expect("failed to parse device info"), false => { let d = Device::random(); tokio::fs::write("device.json", serde_json::to_string(&d).unwrap()) .await .expect("failed to write device info to file"); d } }; let client = Arc::new(Client::new( device, Protocol::AndroidWatch.into(), Arc::new( QSignClient::new( String::from("http://localhost:8080"), String::from("114514"), Duration::from_secs(60), ) .unwrap(), ), DefaultHandler, )); let handle = tokio::spawn({ let client = client.clone(); let stream = DefaultConnector.connect(&client).await.unwrap(); async move { client.start(stream).await } }); tokio::task::yield_now().await; // 等一下,确保连上了 let mut resp = client.fetch_qrcode().await.expect("failed to fetch qrcode"); // // vvv 如果不关心二维码状态,可以用这个替换下面的 vvv // use ricq::ext::login::auto_query_qrcode; // match resp { // QRCodeState::QRCodeImageFetch { // ref image_data, // ref sig, // } => { // tokio::fs::write("qrcode.png", &image_data).await.expect("failed to write file"); // if let Err(err) = auto_query_qrcode(&client, sig).await { // panic!("登录失败 {}", err) // }; // } // _ => { // panic!("resp error") // } // } // // ^^^ 如果不关心二维码状态,可以用这个替换下面的 ^^^ // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // vvv 如果关心二维码状态,可以用这个 vvv let mut image_sig = Bytes::new(); loop { match resp { QRCodeState::ImageFetch(QRCodeImageFetch { ref image_data, ref sig, }) => { tokio::fs::write("qrcode.png", &image_data) .await .expect("failed to write file"); image_sig = sig.clone(); tracing::info!("二维码: qrcode.png"); } QRCodeState::WaitingForScan => { tracing::info!("二维码待扫描") } QRCodeState::WaitingForConfirm => { tracing::info!("二维码待确认") } QRCodeState::Timeout => { tracing::info!("二维码已超时,重新获取"); if let QRCodeState::ImageFetch(QRCodeImageFetch { ref image_data, ref sig, }) = client.fetch_qrcode().await.expect("failed to fetch qrcode") { tokio::fs::write("qrcode.png", &image_data) .await .expect("failed to write file"); image_sig = sig.clone(); tracing::info!("二维码: qrcode.png"); } } QRCodeState::Confirmed(QRCodeConfirmed { ref tmp_pwd, ref tmp_no_pic_sig, ref tgt_qr, .. }) => { tracing::info!("二维码已确认"); let mut login_resp = client .qrcode_login(tmp_pwd, tmp_no_pic_sig, tgt_qr) .await .expect("failed to qrcode login"); if let LoginResponse::DeviceLockLogin { .. } = login_resp { login_resp = client .device_lock_login() .await .expect("failed to device lock login"); } tracing::info!("{:?}", login_resp); break; } QRCodeState::Canceled => { panic!("二维码已取消") } } sleep(Duration::from_secs(5)).await; resp = client .query_qrcode_result(&image_sig) .await .expect("failed to query qrcode result"); } // ^^^ 如果不关心二维码状态,可以用这个 ^^^ after_login(&client).await; { tracing::info!("{:?}", client.get_friend_list().await); tracing::info!("{:?}", client.get_group_list().await); } handle.await.unwrap(); } ================================================ FILE: examples/ricq-axum-api/Cargo.toml ================================================ [package] name = "ricq-axum-api" version = "0.1.0" edition = "2021" description = "ricq axum api" license = "AGPL-3.0" homepage = "https://github.com/lz1998/ricq" repository = "https://github.com/lz1998/ricq" readme = "README.md" keywords = ["ricq", "axum", "http", "api"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] tokio = { version = "1", features = ["full"] } ricq = { path = "../../ricq" } dashmap = "5.2" bytes = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" axum = { version = "0.5", features = ["json"] } async-trait = "0.1" rand = "0.8" tracing = "0.1" base64 = "0.13" tower = { version = "0.4" } tower-http = { version = "0.3", features = ["fs"] } tracing-subscriber = { version = "0.3", features = ["fmt", "local-time"] } time = { version = "0.3", features = ["macros", "local-offset"] } ================================================ FILE: examples/ricq-axum-api/README.md ================================================ # ricq-axum-api 1. 下载 UI 文件,放在工作目录 static 文件夹 ```bash wget https://github.com/lz1998/ricq-react-ui/releases/latest/download/static.zip && unzip static.zip && rm static.zip ``` 2. 实现 Processor trait(仅包含登录后处理逻辑) > 参考 [processor.rs](https://github.com/lz1998/ricq/blob/master/examples/ricq-axum-api/src/processor.rs#L28) ```rust #[async_trait::async_trait] pub trait Processor { async fn on_login_success( &self, client: Arc, event_receiver: broadcast::Receiver, credential: Credential, network_join_handle: JoinHandle<()>, ); async fn list_client(&self) -> Vec; async fn delete_client(&self, uin: i64, protocol: u8); } ``` 3. 创建 RicqAxumApi,并启动 axum 服务器 ```rust #![feature(async_closure)] use std::net::SocketAddr; use std::str::FromStr; use std::sync::Arc; use axum::{ routing::{get, get_service, post}, Extension, Router, }; use dashmap::DashMap; use tower::ServiceBuilder; use tower_http::services::ServeDir; use ricq::Client; use ricq_axum_api::handler::{bot, password, qrcode}; use ricq_axum_api::RicqAxumApi; type ClientProcessor = DashMap<(i64, u8), Arc>; #[tokio::main] async fn main() { // 默认处理器,登录后什么也不做,仅作为容器 let processor = ClientProcessor::default(); let ricq_axum_api = Arc::new(RicqAxumApi::new(processor)); let app = Router::new() .route("/ping", get(async move || "pong")) .nest( "/login", Router::new() .nest( "/qrcode", Router::new() .route("/create", post(qrcode::create)) .route("/list", get(qrcode::list)) .route("/delete", post(qrcode::delete)) .route("/query", post(qrcode::query)), ) .nest( "/password", Router::new() .route("/create", post(password::login)) .route("/request_sms", post(password::request_sms)) .route("/submit_sms", post(password::submit_sms)) .route("/submit_ticket", post(password::submit_ticket)) .route("/list", get(password::list)) .route("/delete", post(password::delete)), ), ) .nest( "/bot", Router::new() .route("/list", get(bot::list)) .route("/delete", post(bot::delete)), ) .fallback(get_service(ServeDir::new("static")).handle_error(handle_error)) .layer( ServiceBuilder::new() .layer(Extension(ricq_axum_api)) .into_inner(), ); let addr = SocketAddr::from_str("0.0.0.0:9000").expect("failed to parse bind_addr"); println!("listening on {}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); } async fn handle_error(_: std::io::Error) -> impl axum::response::IntoResponse { (axum::http::StatusCode::NOT_FOUND, "Something went wrong...") } ``` 4. 访问 `http://localhost:9000` ================================================ FILE: examples/ricq-axum-api/src/bin/main.rs ================================================ #![feature(async_closure)] use std::net::SocketAddr; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use axum::{ routing::{get, get_service, post}, Extension, Router, }; use dashmap::DashMap; use tokio::sync::broadcast; use tokio::task::JoinHandle; use tower::ServiceBuilder; use tower_http::services::ServeDir; use tracing::Level; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use ricq::client::event::{FriendMessageEvent, GroupMessageEvent}; use ricq::client::{DefaultConnector, NetworkStatus}; use ricq::ext::common::after_login; use ricq::ext::reconnect::{auto_reconnect, Credential}; use ricq::handler::QEvent; use ricq::Client; use ricq_axum_api::handler::{bot, password, qrcode}; use ricq_axum_api::processor::Processor; use ricq_axum_api::u8_protocol::U8Protocol; use ricq_axum_api::{ClientInfo, RicqAxumApi}; // 默认处理器 struct ClientProcessor(DashMap<(i64, u8), Arc>); #[async_trait::async_trait] impl Processor for ClientProcessor { async fn on_login_success( &self, client: Arc, mut event_receiver: broadcast::Receiver, credential: Credential, network_join_handle: JoinHandle<()>, ) { let uin = client.uin().await; let protocol = client.version().await.protocol.to_u8(); self.0.insert((uin, protocol), client.clone()); after_login(&client).await; tokio::spawn(async move { while let Ok(event) = event_receiver.recv().await { match event { QEvent::GroupMessage(e) => { let GroupMessageEvent { inner: message, client, } = e; tracing::info!( "GROUP_MSG, code: {}, content: {}", message.group_code, message.elements.to_string() ); client .send_group_message(message.group_code, message.elements) .await .ok(); } QEvent::FriendMessage(e) => { let FriendMessageEvent { inner: message, client, } = e; tracing::info!( "FRIEND_MSG, code: {}, content: {}", message.from_uin, message.elements.to_string() ); client .send_friend_message(message.from_uin, message.elements) .await .ok(); } other => { tracing::info!("{:?}", other) } } } }); // DONT BLOCK tokio::spawn(async move { network_join_handle.await.ok(); auto_reconnect( client, credential, Duration::from_secs(10), 10, DefaultConnector, ) .await; }); } async fn list_client(&self) -> Vec { let mut infos = Vec::new(); for cli in self.0.iter() { let (uin, protocol) = cli.key(); let client = cli.value(); infos.push(ClientInfo { uin: *uin, nick: client.account_info.read().await.nickname.clone(), status: client.get_status(), protocol: *protocol, }); } infos } async fn delete_client(&self, uin: i64, protocol: u8) { if let Some((_, client)) = self.0.remove(&(uin, protocol)) { client.stop(NetworkStatus::Stop); } } } #[tokio::main] async fn main() { // 初始化日志 tracing_subscriber::registry() .with( tracing_subscriber::fmt::layer() .with_target(true) .with_timer(tracing_subscriber::fmt::time::OffsetTime::new( time::UtcOffset::__from_hms_unchecked(8, 0, 0), time::macros::format_description!( "[year repr:last_two]-[month]-[day] [hour]:[minute]:[second]" ), )), ) .with( tracing_subscriber::filter::Targets::new() .with_target("main", Level::DEBUG) .with_target("ricq", Level::DEBUG) .with_target("ricq_axum_api", Level::DEBUG), ) .init(); let processor = ClientProcessor(Default::default()); let ricq_axum_api = Arc::new(RicqAxumApi::new(processor)); let app = Router::new() .route("/ping", get(async move || "pong")) .nest( "/login", Router::new() .nest( "/qrcode", Router::new() .route("/create", post(qrcode::create::)) .route("/list", get(qrcode::list::)) .route("/delete", post(qrcode::delete::)) .route("/query", post(qrcode::query::)), ) .nest( "/password", Router::new() .route("/create", post(password::login::)) .route( "/request_sms", post(password::request_sms::), ) .route("/submit_sms", post(password::submit_sms::)) .route( "/submit_ticket", post(password::submit_ticket::), ) .route("/list", get(password::list::)) .route("/delete", post(password::delete::)), ), ) .nest( "/bot", Router::new() .route("/list", get(bot::list::)) .route("/delete", post(bot::delete::)), ) .fallback(get_service(ServeDir::new("static")).handle_error(handle_error)) .layer( ServiceBuilder::new() .layer(Extension(ricq_axum_api)) .into_inner(), ); let addr = SocketAddr::from_str("0.0.0.0:9000").expect("failed to parse bind_addr"); println!("listening on {}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); } async fn handle_error(_: std::io::Error) -> impl axum::response::IntoResponse { (axum::http::StatusCode::NOT_FOUND, "Something went wrong...") } ================================================ FILE: examples/ricq-axum-api/src/handler/bot.rs ================================================ use std::sync::Arc; use axum::http::StatusCode; use axum::{Extension, Json}; use serde::{Deserialize, Serialize}; use crate::processor::Processor; use crate::{ClientInfo, RicqAxumApi}; #[derive(Debug, Default, Serialize, Deserialize)] pub struct ListBotResp { pub bots: Vec, } pub async fn list( ricq_axum_api: Extension>>, ) -> Result, StatusCode> { Ok(Json(ListBotResp { bots: ricq_axum_api.processor.list_client().await, })) } #[derive(Debug, Default, Serialize, Deserialize)] pub struct DeleteBotReq { uin: i64, protocol: u8, } #[derive(Debug, Default, Serialize, Deserialize)] pub struct DeleteBotResp {} pub async fn delete( Json(req): Json, ricq_axum_api: Extension>>, ) -> Result, StatusCode> { ricq_axum_api .processor .delete_client(req.uin, req.protocol) .await; Ok(Json(DeleteBotResp {})) } ================================================ FILE: examples/ricq-axum-api/src/handler/mod.rs ================================================ pub mod bot; pub mod password; pub mod qrcode; ================================================ FILE: examples/ricq-axum-api/src/handler/password.rs ================================================ use std::sync::Arc; use std::time::Duration; use axum::http::StatusCode; use axum::{Extension, Json}; use rand::{prelude::StdRng, SeedableRng}; use serde::{Deserialize, Serialize}; use ricq::client::{Connector as _, DefaultConnector, NetworkStatus}; use ricq::ext::reconnect::{Credential, Password}; use ricq::qsign::QSignClient; use ricq::version::get_version; use ricq::{Client, Device, LoginDeviceLocked, LoginNeedCaptcha, LoginResponse, Protocol}; use crate::processor::Processor; use crate::u8_protocol::U8Protocol; use crate::{PasswordClient, RicqAxumApi}; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct CreateClientReq { pub uin: i64, pub protocol: u8, pub password: String, pub device_seed: Option, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct SubmitTicketReq { pub uin: i64, pub protocol: u8, pub ticket: String, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct RequestSmsReq { pub uin: i64, pub protocol: u8, } #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct SubmitSmsReq { pub uin: i64, pub protocol: u8, pub sms: String, } #[derive(Default, Debug, Clone, Serialize)] pub struct PasswordLoginResp { pub state: String, pub captcha_url: Option, pub verify_url: Option, pub sms_phone: Option, pub message: Option, } impl From for PasswordLoginResp { fn from(login_response: LoginResponse) -> Self { let mut resp = PasswordLoginResp::default(); match login_response { LoginResponse::Success(_) => { resp.state = "success".into(); } LoginResponse::NeedCaptcha(LoginNeedCaptcha { ref verify_url, .. }) => { resp.state = "need_captcha".into(); resp.captcha_url = verify_url.clone(); } LoginResponse::AccountFrozen => { resp.state = "account_frozen".into(); } LoginResponse::DeviceLocked(LoginDeviceLocked { ref verify_url, ref message, ref sms_phone, .. }) => { resp.state = "device_locked".into(); resp.verify_url = verify_url.clone(); resp.sms_phone = sms_phone.clone(); resp.message = message.clone(); } LoginResponse::TooManySMSRequest => { resp.state = "too_many_sms_request".into(); } LoginResponse::DeviceLockLogin(_) => { resp.state = "device_lock_login".into(); } LoginResponse::UnknownStatus(status) => { resp.state = "unknown".into(); resp.message = Some(format!( "status: {} message: {}", status.status, status.message )); } }; resp } } pub async fn login( Json(req): Json, ricq_axum_api: Extension>>, ) -> Result, StatusCode> { let mut rand_seed = req.device_seed.unwrap_or(req.uin as u64); if rand_seed == 0 { rand_seed = req.uin as u64; } let device = Device::random_with_rng(&mut StdRng::seed_from_u64(rand_seed)); let protocol = Protocol::from_u8(req.protocol); let (sender, receiver) = tokio::sync::broadcast::channel(10); let cli = Arc::new(Client::new( device, get_version(protocol.clone()), Arc::new( QSignClient::new( String::from("http://localhost:8080"), String::from("114514"), Duration::from_secs(60), ) .unwrap(), ), sender, )); let connector = DefaultConnector; let stream = connector .connect(&cli) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let c = cli.clone(); let network_join_handle = tokio::spawn(async move { c.start(stream).await }); tokio::task::yield_now().await; let mut resp = cli .password_login(req.uin, &req.password) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if let LoginResponse::DeviceLockLogin(_) = resp { resp = cli .device_lock_login() .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; } let credential = Credential::Password(Password { uin: req.uin, password: req.password, }); if let LoginResponse::Success(_) = resp { tracing::info!("login success: {} {:?}", req.uin, req.protocol); ricq_axum_api .processor .on_login_success(cli, receiver, credential, network_join_handle) .await; } else if let Some(old) = ricq_axum_api.password_clients.insert( (req.uin, protocol.to_u8()), PasswordClient { client: cli, login_response: resp.clone(), event_receiver: receiver, network_join_handle, credential, }, ) { old.client.stop(NetworkStatus::Stop); } Ok(Json(PasswordLoginResp::from(resp))) } pub async fn submit_ticket( Json(req): Json, ricq_axum_api: Extension>>, ) -> Result, StatusCode> { let mut resp = ricq_axum_api .password_clients .get(&(req.uin, req.protocol)) .ok_or(StatusCode::BAD_REQUEST)? .client .submit_ticket(&req.ticket) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if let LoginResponse::DeviceLockLogin(_) = resp { resp = ricq_axum_api .password_clients .get(&(req.uin, req.protocol)) .ok_or(StatusCode::INTERNAL_SERVER_ERROR)? .client .device_lock_login() .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; } if let LoginResponse::Success(_) = resp { if let Some(((uin, protocol), client)) = ricq_axum_api .password_clients .remove(&(req.uin, req.protocol)) { tracing::info!("login success: {} {:?}", uin, Protocol::from_u8(protocol)); ricq_axum_api .processor .on_login_success( client.client, client.event_receiver, client.credential, client.network_join_handle, ) .await; } else { tracing::warn!("failed to remove client: {}", req.uin); } } else { ricq_axum_api .password_clients .get_mut(&(req.uin, req.protocol)) .ok_or(StatusCode::INTERNAL_SERVER_ERROR)? .login_response = resp.clone(); } Ok(Json(PasswordLoginResp::from(resp))) } pub async fn request_sms( Json(req): Json, ricq_axum_api: Extension>>, ) -> Result, StatusCode> { let resp = ricq_axum_api .password_clients .get(&(req.uin, req.protocol)) .ok_or(StatusCode::BAD_REQUEST)? .client .request_sms() .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; ricq_axum_api .password_clients .get_mut(&(req.uin, req.protocol)) .ok_or(StatusCode::INTERNAL_SERVER_ERROR)? .login_response = resp.clone(); Ok(Json(PasswordLoginResp::from(resp))) } pub async fn submit_sms( Json(req): Json, ricq_axum_api: Extension>>, ) -> Result, StatusCode> { let mut resp = ricq_axum_api .password_clients .get(&(req.uin, req.protocol)) .ok_or(StatusCode::BAD_REQUEST)? .client .submit_sms_code(&req.sms) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if let LoginResponse::DeviceLockLogin(_) = resp { resp = ricq_axum_api .password_clients .get(&(req.uin, req.protocol)) .ok_or(StatusCode::INTERNAL_SERVER_ERROR)? .client .device_lock_login() .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; } if let LoginResponse::Success(_) = resp { let cli = ricq_axum_api .password_clients .remove(&(req.uin, req.protocol)); if let Some(((uin, protocol), client)) = cli { tracing::info!("login success: {} {:?}", uin, Protocol::from_u8(protocol)); ricq_axum_api .processor .on_login_success( client.client, client.event_receiver, client.credential, client.network_join_handle, ) .await; } else { tracing::warn!("failed to remove client: {}", req.uin); } } else { ricq_axum_api .password_clients .get_mut(&(req.uin, req.protocol)) .ok_or(StatusCode::INTERNAL_SERVER_ERROR)? .login_response = resp.clone(); } Ok(Json(PasswordLoginResp::from(resp))) } #[derive(Default, Serialize)] pub struct ListClientResp { pub clients: Vec, } #[derive(Default, Serialize)] pub struct ListClientRespClient { pub uin: i64, pub protocol: u8, pub resp: PasswordLoginResp, } pub async fn list( ricq_axum_api: Extension>>, ) -> Result, StatusCode> { let mut clients = Vec::new(); for c in ricq_axum_api.password_clients.iter() { clients.push(ListClientRespClient { uin: c.key().0, protocol: c.client.version().await.protocol.to_u8(), resp: PasswordLoginResp::from(c.login_response.clone()), }) } Ok(Json(ListClientResp { clients })) } #[derive(Default, Serialize, Deserialize)] pub struct DeleteClientReq { pub uin: i64, pub protocol: u8, } #[derive(Default, Serialize, Deserialize)] pub struct DeleteClientResp {} pub async fn delete( Json(req): Json, ricq_axum_api: Extension>>, ) -> Result, StatusCode> { if let Some((_, cli)) = ricq_axum_api .password_clients .remove(&(req.uin, req.protocol)) { cli.client.stop(NetworkStatus::Stop); } Ok(Json(DeleteClientResp {})) } ================================================ FILE: examples/ricq-axum-api/src/handler/qrcode.rs ================================================ use std::sync::Arc; use std::time::Duration; use axum::http::StatusCode; use axum::{Extension, Json}; use bytes::Bytes; use rand::{prelude::StdRng, SeedableRng}; use serde::{Deserialize, Serialize}; use ricq::client::NetworkStatus; use ricq::client::{Connector as _, DefaultConnector}; use ricq::device::Device; use ricq::ext::reconnect::Credential; use ricq::qsign::QSignClient; use ricq::version::{get_version, Protocol}; use ricq::{Client, LoginResponse, QRCodeState}; use crate::processor::Processor; use crate::u8_protocol::U8Protocol; use crate::QRCodeClient; use crate::RicqAxumApi; mod base64 { extern crate base64; use serde::{de, Deserialize, Deserializer, Serializer}; pub fn serialize(bytes: &[u8], serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&base64::encode(bytes)) } pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let s = <&str>::deserialize(deserializer)?; base64::decode(s).map_err(de::Error::custom) } } #[derive(Debug, Default, Serialize, Deserialize)] pub struct CreateClientReq { pub device_seed: Option, pub protocol: u8, } #[derive(Debug, Default, Serialize, Deserialize)] pub struct CreateClientResp { #[serde(with = "base64")] pub sig: Vec, #[serde(with = "base64")] pub image: Vec, } pub async fn create( Json(req): Json, ricq_axum_api: Extension>>, ) -> Result, StatusCode> { let rand_seed = req.device_seed.unwrap_or_else(rand::random); let device = Device::random_with_rng(&mut StdRng::seed_from_u64(rand_seed)); let protocol = match Protocol::from_u8(req.protocol) { Protocol::MacOS => Protocol::MacOS, Protocol::AndroidWatch => Protocol::AndroidWatch, _ => return Err(StatusCode::BAD_REQUEST), }; let (sender, receiver) = tokio::sync::broadcast::channel(10); let cli = Arc::new(Client::new( device, get_version(protocol), Arc::new( QSignClient::new( String::from("http://localhost:8080"), String::from("114514"), Duration::from_secs(60), ) .unwrap(), ), sender, )); let connector = DefaultConnector; let stream = connector .connect(&cli) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let c = cli.clone(); let network_join_handle = tokio::spawn(async move { c.start(stream).await }); tokio::task::yield_now().await; let resp = cli .fetch_qrcode() .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if let QRCodeState::ImageFetch(image_fetch) = resp { ricq_axum_api.qrcode_clients.insert( image_fetch.sig.clone(), QRCodeClient { sig: image_fetch.sig.to_vec(), image: image_fetch.image_data.to_vec(), state: QRCodeState::ImageFetch(image_fetch.clone()), client: cli, event_receiver: receiver, network_join_handle, }, ); Ok(Json(CreateClientResp { sig: image_fetch.sig.to_vec(), image: image_fetch.image_data.to_vec(), })) } else { Err(StatusCode::INTERNAL_SERVER_ERROR) } } #[derive(Debug, Default, Serialize, Deserialize)] pub struct QueryQRCodeReq { #[serde(with = "base64")] pub sig: Vec, } #[derive(Debug, Default, Serialize, Deserialize)] pub struct QueryQRCodeResp { pub state: String, } pub async fn query( Json(req): Json, ricq_axum_api: Extension>>, ) -> Result, StatusCode> { let sig = Bytes::from(req.sig); let resp = ricq_axum_api .qrcode_clients .get(&sig) .ok_or(StatusCode::BAD_REQUEST)? .client .query_qrcode_result(&sig) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let state = match resp { QRCodeState::ImageFetch(_) => "image_fetch", QRCodeState::WaitingForScan => "waiting_for_scan", QRCodeState::WaitingForConfirm => "waiting_for_confirm", QRCodeState::Timeout => "timeout", QRCodeState::Confirmed(_) => "confirmed", QRCodeState::Canceled => "canceled", } .to_string(); ricq_axum_api .qrcode_clients .get_mut(&sig) .ok_or(StatusCode::INTERNAL_SERVER_ERROR)? .state = resp.clone(); if let QRCodeState::Confirmed(confirmed) = resp { let (_, cli) = ricq_axum_api.qrcode_clients.remove(&sig).unwrap(); let mut resp = cli .client .qrcode_login( &confirmed.tmp_pwd, &confirmed.tmp_no_pic_sig, &confirmed.tgt_qr, ) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; if let LoginResponse::DeviceLockLogin(_) = resp { resp = cli .client .device_lock_login() .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; } if let LoginResponse::Success(_) = resp { let uin = cli.client.uin().await; let credential = Credential::Token(cli.client.gen_token().await); tracing::info!("login success: {}", uin); ricq_axum_api .processor .on_login_success( cli.client, cli.event_receiver, credential, cli.network_join_handle, ) .await; } } Ok(Json(QueryQRCodeResp { state })) } #[derive(Default, Serialize)] pub struct ListClientResp { pub clients: Vec, } #[derive(Default, Serialize)] pub struct ListClientRespClient { #[serde(with = "base64")] pub sig: Vec, #[serde(with = "base64")] pub image: Vec, pub protocol: u8, pub state: String, } pub async fn list( ricq_axum_api: Extension>>, ) -> Result, StatusCode> { let mut clients = Vec::new(); for c in ricq_axum_api.qrcode_clients.iter() { clients.push(ListClientRespClient { sig: c.sig.to_vec(), image: c.image.clone(), protocol: c.client.version().await.protocol.to_u8(), state: match c.state { QRCodeState::ImageFetch(_) => "image_fetch", QRCodeState::WaitingForScan => "waiting_for_scan", QRCodeState::WaitingForConfirm => "waiting_for_confirm", QRCodeState::Timeout => "timeout", QRCodeState::Confirmed(_) => "confirmed", QRCodeState::Canceled => "canceled", } .into(), }) } Ok(Json(ListClientResp { clients })) } #[derive(Default, Serialize, Deserialize)] pub struct DeleteClientReq { #[serde(with = "base64")] pub sig: Vec, } #[derive(Default, Serialize, Deserialize)] pub struct DeleteClientResp {} pub async fn delete( Json(req): Json, ricq_axum_api: Extension>>, ) -> Result, StatusCode> { if let Some((_, cli)) = ricq_axum_api.qrcode_clients.remove(&Bytes::from(req.sig)) { cli.client.stop(NetworkStatus::Stop); } Ok(Json(DeleteClientResp {})) } ================================================ FILE: examples/ricq-axum-api/src/lib.rs ================================================ use bytes::Bytes; use dashmap::DashMap; use ricq::ext::reconnect::Credential; use ricq::handler::QEvent; use ricq::{Client, LoginResponse, QRCodeState}; use std::sync::Arc; use tokio::sync::broadcast; use tokio::task::JoinHandle; pub mod handler; pub mod processor; pub mod u8_protocol; use serde::{Deserialize, Serialize}; use crate::processor::Processor; #[derive(Debug, Default, Serialize, Deserialize)] pub struct ClientInfo { pub uin: i64, pub nick: String, pub status: u8, pub protocol: u8, } pub struct PasswordClient { pub client: Arc, pub login_response: LoginResponse, pub event_receiver: broadcast::Receiver, pub network_join_handle: JoinHandle<()>, pub credential: Credential, } pub struct QRCodeClient { pub sig: Vec, pub image: Vec, pub state: QRCodeState, pub client: Arc, pub event_receiver: broadcast::Receiver, pub network_join_handle: JoinHandle<()>, } pub struct RicqAxumApi { // key: uin+protocol password_clients: DashMap<(i64, u8), PasswordClient>, // key: sig qrcode_clients: DashMap, // 仅负责登录后的逻辑 processor: P, } impl RicqAxumApi

{ pub fn new(processor: P) -> Self { Self { password_clients: Default::default(), qrcode_clients: Default::default(), processor, } } } ================================================ FILE: examples/ricq-axum-api/src/processor.rs ================================================ use std::sync::Arc; use tokio::sync::broadcast; use tokio::task::JoinHandle; use ricq::{ext::reconnect::Credential, handler::QEvent, Client}; use crate::ClientInfo; #[async_trait::async_trait] pub trait Processor { async fn on_login_success( &self, client: Arc, event_receiver: broadcast::Receiver, credential: Credential, network_join_handle: JoinHandle<()>, ); async fn list_client(&self) -> Vec; async fn delete_client(&self, uin: i64, protocol: u8); } ================================================ FILE: examples/ricq-axum-api/src/u8_protocol.rs ================================================ use ricq::version::Protocol; pub trait U8Protocol { fn to_u8(&self) -> u8; fn from_u8(n: u8) -> Self; } impl U8Protocol for Protocol { fn to_u8(&self) -> u8 { match self { Protocol::AndroidPhone => 1, Protocol::AndroidWatch => 2, Protocol::MacOS => 3, Protocol::QiDian => 4, Protocol::IPad => 5, Protocol::AndroidPad => 6, } } fn from_u8(n: u8) -> Self { match n { 1 => Protocol::AndroidPhone, 2 => Protocol::AndroidWatch, 3 => Protocol::MacOS, 4 => Protocol::QiDian, 5 => Protocol::IPad, _ => Protocol::IPad, } } } ================================================ FILE: examples/token_login/Cargo.toml ================================================ [package] name = "token_login" version = "0.1.0" edition = "2021" [dependencies] ricq = { path = "../../ricq" } tokio = { version = "1", features = ["full"] } tracing = "0.1" serde_json = "1" tracing-subscriber = { version = "0.3", features = ["fmt", "local-time"] } time = { version = "0.3", features = ["macros", "local-offset"] } rand = "0.8" ================================================ FILE: examples/token_login/src/main.rs ================================================ use std::sync::Arc; use std::time::Duration; use rand::prelude::StdRng; use rand::SeedableRng; use tracing::Level; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use ricq::client::{Connector as _, DefaultConnector, Token}; use ricq::ext::common::after_login; use ricq::handler::DefaultHandler; use ricq::qsign::QSignClient; use ricq::{Client, Device, Protocol}; #[tokio::main(flavor = "current_thread")] async fn main() { tracing_subscriber::registry() .with( tracing_subscriber::fmt::layer() .with_target(true) .with_timer(tracing_subscriber::fmt::time::OffsetTime::new( time::UtcOffset::__from_hms_unchecked(8, 0, 0), time::macros::format_description!( "[year repr:last_two]-[month]-[day] [hour]:[minute]:[second]" ), )), ) .with( tracing_subscriber::filter::Targets::new() .with_target("ricq", Level::DEBUG) .with_target("token_login", Level::DEBUG), ) .init(); let token = tokio::fs::read_to_string("session.token") .await .expect("failed to read token"); let token: Token = serde_json::from_str(&token).expect("failed to parse token"); let device = Device::random_with_rng(&mut StdRng::seed_from_u64(token.uin as u64)); let client = Arc::new(Client::new( device, Protocol::IPad.into(), Arc::new( QSignClient::new( String::from("http://localhost:8080"), String::from("114514"), Duration::from_secs(60), ) .unwrap(), ), DefaultHandler, )); let handle = tokio::spawn({ let client = client.clone(); let stream = DefaultConnector.connect(&client).await.unwrap(); async move { client.start(stream).await } }); // 直接使用 TcpStream,目前不推荐 // let stream = TcpStream::connect(client.get_address()) // .await // .expect("failed to connect"); // let c = client.clone(); // let handle = tokio::spawn(async move { c.start(stream).await }); tokio::task::yield_now().await; // 等一下,确保连上了 let resp = client .token_login(token) .await .expect("failed to login with token"); tracing::info!("{:?}", resp); after_login(&client).await; { tracing::info!("{:?}", client.get_friend_list().await); tracing::info!("{:?}", client.get_group_list().await); } let d = client.get_allowed_clients().await; tracing::info!("{:?}", d); handle.await.unwrap(); } ================================================ FILE: ricq/Cargo.toml ================================================ [package] name = "ricq" version = "0.1.20" edition = "2021" description = "Android IM protocol" license = "MPL-2.0" homepage = "https://github.com/lz1998/ricq" repository = "https://github.com/lz1998/ricq" readme = "README.md" keywords = ["qq", "protocol", "android", "mirai"] [features] default = [] image-detail = ["image"] [dependencies] ricq-core = { path = "../ricq-core" } async-trait.workspace = true bytes.workspace = true cached = { workspace = true, default-features = false } derivative.workspace = true flate2 = { workspace = true, features = ["rust_backend"], default-features = false } futures-util = { workspace = true, features = ["sink"] } image = { workspace = true, optional = true } jcers.workspace = true md5.workspace = true prost = { workspace = true, features = ["std"], default-features = false } rand.workspace = true serde = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["rt", "macros", "net", "time"] } tokio-util = { workspace = true, features = ["codec"] } tracing.workspace = true reqwest = { workspace = true, features = ["json"] } async-recursion = "1.0" ================================================ FILE: ricq/README.md ================================================ # RICQ ![](https://socialify.git.ci/lz1998/ricq/image?forks=1&issues=1&language=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Dark) QQ Android 协议的 Rust 实现,移植于 [OICQ](https://github.com/takayama-lily/oicq)。 - [ricq](https://crates.io/crates/ricq):提供异步 API - [ricq-core](https://crates.io/crates/ricq-core):不带 IO 的数据包构造器、解析器(通常用于 FFI) - [ricq-axum-api](https://github.com/lz1998/ricq/tree/master/examples/ricq-axum-api):提供 HTTP API 形式的登录接口,配合 [ricq-react-ui](https://github.com/lz1998/ricq-react-ui),只需要开发登录后的逻辑。 ## 如何使用 本项目是协议 Lib,如果需要直接使用,可以参考 [examples](https://github.com/lz1998/ricq/tree/master/examples) 中的例子进行开发。 可以配合前端界面 [ricq-react-ui](https://github.com/lz1998/ricq-react-ui/releases) 解压 static.zip 后,运行 [ricq-axum-api](https://github.com/lz1998/ricq/blob/master/examples/ricq-axum-api/src/bin/main.rs#L125),使用浏览器 F12-Network 查看调用方式。 普通开发者推荐使用 SDK、框架进行开发: | 框架 / SDK | 语言 | 说明 | | ---- | ---- | ---- | | [rust_proc_qq](https://github.com/niuhuan/rust_proc_qq) | Rust | 模仿rocket | | [Walle-Q](https://github.com/abrahum/walle-q) | - | onebot协议 | | [pbrq](https://github.com/ProtobufBot/pbrq) | - | websocket+protobuf协议(附带[Web-UI](https://github.com/ProtobufBot/pbrq-react-ui)) | | [atri_qq](https://github.com/LaoLittle/atri_qq) | - | 加载原生动态库插件,高性能低占用 | | [awr](https://github.com/Wybxc/awr) | Python | 基于 ricq 包装,供 Python 使用的 QQ 无头客户端。 | > 本项目是一个年轻的项目,请使用 Nightly 工具链构建本项目哦(正经人谁用 Stable 啊) ## 相关项目 | 项目 | 描述 | | ---- | ---- | | [lomirus/gtk-qq](https://github.com/lomirus/gtk-qq) | Unofficial Linux QQ client, based on GTK4 and libadwaita, developed with Rust and Relm4. | | [a1967629423/esp32c3-rs-qq](https://github.com/a1967629423/esp32c3-rs-qq) | 在单片机上运行QQ | | [ricq-react-ui](https://github.com/lz1998/ricq-react-ui) + [ricq-axum-api](https://github.com/lz1998/ricq/blob/master/examples/ricq-axum-api/src/bin/main.rs#L125) | 登录 demo,附带前端 UI | ## 已完成功能 / 开发计划 ### 登录 - [x] 账号密码登录 - [x] 二维码登录 - [x] 验证码提交 - [x] 设备锁验证 - [x] 错误信息解析 ### 消息类型 - [x] 文本 - [x] 表情 - [x] At - [x] 回复 - [x] 匿名 - [x] 骰子 - [x] 石头剪刀布 - [x] 图片 - [x] 语音 - [x] 长消息(仅支持群聊发送) - [x] 合并转发(仅支持群聊发送) - [x] 链接分享 - [ ] 小程序(暂只支持 RAW) - [ ] 短视频 - [ ] 群文件(上传与接收信息) ### 事件 - [x] 群消息 - [x] 好友消息 - [x] 新好友请求 - [x] 收到其他用户进群请求 - [x] 新好友 - [x] 群禁言 - [x] 好友消息撤回 - [x] 群消息撤回 - [x] 收到邀请进群请求 - [x] 群名称变更 - [x] 好友删除 - [x] 群成员权限变更 - [x] 新成员进群 / 退群 - [x] 登录号加群 - [x] 临时会话消息 - [x] 群解散 - [x] 登录号退群(包含踢出) - [x] 客户端离线 - [ ] 群提示(戳一戳 / 运气王等) ### 主动操作 > 为防止滥用,将不支持主动邀请新成员进群 - [x] 修改昵称 - [x] 发送群消息 - [x] 获取群列表 - [x] 获取群成员列表 - [x] 获取好友列表 / 分组 - [x] 获取好友个性签名 - [x] 添加 / 删除 / 重命名好友分组 - [x] 群成员禁言 / 解除禁言 - [x] 踢出群成员 - [x] 戳一戳群友 - [x] 戳一戳好友 - [x] 设置群管理员 - [x] 设置群公告 - [x] 设置群名称 - [x] 全员禁言 - [x] 获取群@全体剩余次数 - [x] 翻译 - [x] 修改群成员头衔 - [x] 设置群精华消息 - [x] 发送好友消息 - [x] 发送临时会话消息 - [x] 修改群成员 Card - [x] 撤回群消息 - [x] 撤回好友消息 - [x] 处理被邀请加群请求 - [x] 处理加群请求 - [x] 处理好友请求 - [x] 删除好友 - [x] 获取陌生人信息 - [x] 设置在线状态 - [x] 修改个人资料 - [x] 修改个性签名 - [x] 获取群文件下载链接 - [ ] 获取群荣誉(龙王 / 群聊火焰等) - [ ] ~~群成员邀请~~ ### 敏感操作 > 由于 [QQ 钱包支付用户服务协议](https://www.tenpay.com/v2/html5/basic/public/agreement/protocol_mqq_pay.shtml), 将不提供一切有关 QQ 钱包的功能。 > 4.13 您不得利用本服务实施下列任一的行为: > \ > (9) **侵害 QQ 钱包支付服务系統;** - [ ] ~~QQ 钱包协议(收款 / 付款等)~~ ================================================ FILE: ricq/src/client/api/friend.rs ================================================ use std::time::Duration; use bytes::BufMut; use ricq_core::command::long_conn::OffPicUpResp; use ricq_core::command::oidb_svc::{LinkShare, MusicShare, MusicVersion, ShareTarget}; use ricq_core::command::{friendlist::*, profile_service::*}; use ricq_core::hex::encode_hex; use ricq_core::highway::BdhInput; use ricq_core::msg::elem::FriendImage; use ricq_core::msg::MessageChain; use ricq_core::pb; use ricq_core::pb::msg::routing_head::RoutingHead; use ricq_core::structs::FriendAudio; use ricq_core::structs::MessageReceipt; use crate::structs::ImageInfo; use crate::{RQError, RQResult}; impl super::super::Client { /// 获取好友请求 pub async fn get_friend_system_messages(&self) -> RQResult { let req = self .engine .read() .await .build_system_msg_new_friend_packet(); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_system_msg_friend_packet(resp.body) } /// 处理好友申请 pub async fn solve_friend_system_message( &self, msg_seq: i64, req_uin: i64, accept: bool, ) -> RQResult<()> { let pkt = self .engine .read() .await .build_system_msg_friend_action_packet(msg_seq, req_uin, accept); self.send_and_wait(pkt).await?; Ok(()) } /// 获取好友列表 /// 第一个参数offset,从0开始;第二个参数count,150,另外两个都是0 pub async fn _get_friend_list( &self, friend_start_index: i16, friend_list_count: i16, group_start_index: i16, group_list_count: i16, ) -> RQResult { let req = self .engine .read() .await .build_friend_group_list_request_packet( friend_start_index, friend_list_count, group_start_index, group_list_count, ); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_friend_group_list_response(resp.body) } /// 删除好友 /// ## Args /// - `del_uin` 为要删除的好友QQid /// /// ## Return /// - 如果删除好友成功 返回 Ok(()) /// - 如果删除好友失败 返回 Err(RQError::Other) /// - 其他异常 返回 Err(..) pub async fn delete_friend(&self, del_uin: i64) -> RQResult<()> { let req = self.engine.read().await.build_delete_friend_packet(del_uin); let resp = self.send_and_wait(req).await?; let resp = self.engine.read().await.decode_remove_friend(resp.body)?; if resp.error_code != 0 { Err(RQError::Other(format!( "Delete Friend Failure : code = {}", resp.error_code ))) } else { Ok(()) } } /// 刷新好友列表 pub async fn get_friend_list(&self) -> RQResult { let mut output = FriendListResponse::default(); loop { let resp = self ._get_friend_list(output.friends.len() as i16, 150, 0, 0) .await?; output.friend_groups.extend(resp.friend_groups); output.friends.extend(resp.friends); output.total_count = resp.total_count; if output.friends.len() as i16 >= resp.total_count { break; } } Ok(output) } /// 好友列表-添加好友分组 pub async fn friend_list_add_group(&self, sort_id: u8, group_name: String) -> RQResult<()> { let req = self .engine .read() .await .build_friend_list_add_group_req_packet(sort_id, &group_name); let _ = self.send_and_wait(req).await?; Ok(()) } /// 好友列表-重命名好友分组 pub async fn friend_list_rename_group(&self, group_id: u8, group_name: String) -> RQResult<()> { let req = self .engine .read() .await .build_friend_list_rename_group_req_packet(group_id, &group_name); let _ = self.send_and_wait(req).await?; Ok(()) } /// 好友列表-删除好友分组 pub async fn friend_list_del_group(&self, group_id: u8) -> RQResult<()> { let req = self .engine .read() .await .build_friend_list_del_group_req_packet(group_id); let _ = self.send_and_wait(req).await?; Ok(()) } /// 好友戳一戳 pub async fn friend_poke(&self, target: i64) -> RQResult<()> { let req = self.engine.read().await.build_friend_poke_packet(target); let _ = self.send_and_wait(req).await?; Ok(()) } /// 发送好友消息 pub async fn send_friend_message( &self, target: i64, message_chain: MessageChain, ) -> RQResult { self._send_friend_message(target, message_chain, None).await } /// 发送好友语音 pub async fn send_friend_audio( &self, target: i64, audio: FriendAudio, ) -> RQResult { self._send_friend_message(target, MessageChain::default(), Some(audio.0)) .await } async fn _send_friend_message( &self, target: i64, message_chain: MessageChain, ptt: Option, ) -> RQResult { self.send_message( RoutingHead::C2c(pb::msg::C2c { to_uin: Some(target), }), message_chain, ptt, ) .await } pub async fn upload_friend_image(&self, target: i64, data: &[u8]) -> RQResult { let image_info = ImageInfo::try_new(data)?; let image_store = self.get_off_pic_store(target, &image_info).await?; let friend_image = match image_store { OffPicUpResp::Exist { res_id, uuid } => image_info.into_friend_image(res_id, uuid), OffPicUpResp::UploadRequired { res_id, uuid, upload_key, mut upload_addrs, } => { let addr = match self.highway_addrs.read().await.first() { Some(addr) => *addr, None => upload_addrs .pop() .ok_or(RQError::EmptyField("upload_addrs"))?, }; self.highway_upload_bdh( addr.into(), BdhInput { command_id: 1, ticket: upload_key, ext: vec![], encrypt: false, chunk_size: 256 * 1024, send_echo: true, }, data, ) .await?; image_info.into_friend_image(res_id, uuid) } }; Ok(friend_image) } pub async fn get_off_pic_store( &self, target: i64, image_info: &ImageInfo, ) -> RQResult { let req = self.engine.read().await.build_off_pic_up_packet( target, image_info.filename.clone(), image_info.md5.clone(), image_info.size as u64, image_info.width, image_info.height, image_info.image_type as u32, ); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_off_pic_up_response(resp.body) } /// 分享好友音乐 pub async fn send_friend_music_share( &self, uin: i64, music_share: MusicShare, music_version: MusicVersion, ) -> RQResult<()> { let req = self.engine.read().await.build_share_music_request_packet( ShareTarget::Friend(uin), music_share, music_version, ); let _ = self.send_and_wait(req).await?; Ok(()) } /// 分享链接 pub async fn send_friend_link_share(&self, uin: i64, link_share: LinkShare) -> RQResult<()> { let req = self .engine .read() .await .build_share_link_request_packet(ShareTarget::Friend(uin), link_share); let _ = self.send_and_wait(req).await?; Ok(()) } // 撤回好友消息 pub async fn recall_friend_message( &self, uin: i64, msg_time: i64, seqs: Vec, rands: Vec, ) -> RQResult<()> { let req = self .engine .read() .await .build_friend_recall_packet(uin, msg_time, seqs, rands); let _ = self.send_and_wait(req).await?; Ok(()) } pub async fn upload_friend_audio( &self, target: i64, data: &[u8], audio_duration: Duration, ) -> RQResult { let md5 = md5::compute(data).to_vec(); let size = data.len(); let ext = self.engine.read().await.build_friend_try_up_ptt_req( target, md5.clone(), size as i64, size as i32, ); let addr = self .highway_addrs .read() .await .first() .copied() .ok_or(RQError::EmptyField("highway_addrs"))?; let ticket = self .highway_session .read() .await .sig_session .clone() .to_vec(); let resp = self .highway_upload_bdh( addr.into(), BdhInput { command_id: 26, ticket, ext: ext.to_vec(), encrypt: false, chunk_size: 256 * 1024, send_echo: true, }, data, ) .await?; let uuid = self .engine .read() .await .decode_friend_try_up_ptt_resp(resp)?; Ok(FriendAudio(pb::msg::Ptt { file_type: Some(4), src_uin: Some(self.uin().await), file_uuid: Some(uuid), file_name: Some(format!("{}.amr", encode_hex(&md5))), file_md5: Some(md5), file_size: Some(size as i32), reserve: Some({ let mut w = Vec::new(); w.put_u8(3); // tlv count { w.put_u8(8); w.put_u16(4); w.put_u32(1); // codec } { w.put_u8(9); w.put_u16(4); w.put_u32(audio_duration.as_secs() as u32); // voiceLength } { w.put_u8(10); w.put_u16(6); w.put_slice(&[0x08, 0x00, 0x28, 0x00, 0x38, 0x00]); // change_voice+redpack_type+autototext_voice } w }), bool_valid: Some(true), ..Default::default() })) } pub async fn get_friend_audio_url( &self, sender_uin: i64, audio: FriendAudio, ) -> RQResult { let req = self.engine.read().await.build_c2c_ptt_down_req( sender_uin, audio.0.file_uuid.ok_or(RQError::EmptyField("file_uuid"))?, ); let resp = self.send_and_wait(req).await?; self.engine.read().await.decode_c2c_ptt_down(resp.body) } /// 标记私聊消息已读 TODO 待测试 pub async fn mark_friend_message_readed(&self, uin: i64, time: i64) -> RQResult<()> { let req = self .engine .read() .await .build_friend_msg_readed_packet(uin, time); let _ = self.send_and_wait(req).await?; Ok(()) } /// 获取好友个性签名 pub async fn get_friend_rich_sig(&self, user_ids: Vec) -> RQResult> { let req = self .engine .read() .await .build_get_rich_sig_request_packet(user_ids); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_get_rich_sig_response_packet(resp.body) } } ================================================ FILE: ricq/src/client/api/group.rs ================================================ use std::collections::HashMap; use std::time::{Duration, UNIX_EPOCH}; use bytes::Bytes; use cached::Cached; use prost::Message; use ricq_core::command::common::PbToBytes; use ricq_core::command::img_store::GroupImageStoreResp; use ricq_core::command::multi_msg::gen_forward_preview; use ricq_core::command::{friendlist::*, oidb_svc::*, profile_service::*}; use ricq_core::common::group_code2uin; use ricq_core::hex::encode_hex; use ricq_core::highway::BdhInput; use ricq_core::msg::elem::{Anonymous, GroupImage, RichMsg, VideoFile}; use ricq_core::msg::MessageChain; use ricq_core::pb; use ricq_core::pb::short_video::ShortVideoUploadRsp; use ricq_core::structs::{ForwardMessage, GroupFileCount, GroupFileList, MessageNode}; use ricq_core::structs::{GroupAudio, GroupMemberPermission}; use ricq_core::structs::{GroupInfo, GroupMemberInfo, MessageReceipt}; use crate::structs::ImageInfo; use crate::{RQError, RQResult}; impl super::super::Client { /// 获取进群申请信息 async fn get_group_system_messages(&self, suspicious: bool) -> RQResult { let req = self .engine .read() .await .build_system_msg_new_group_packet(suspicious); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_system_msg_group_packet(resp.body) } /// 获取所有进群请求 pub async fn get_all_group_system_messages(&self) -> RQResult { let mut resp = self.get_group_system_messages(false).await?; let risk_resp = self.get_group_system_messages(true).await?; resp.join_group_requests .extend(risk_resp.join_group_requests); resp.self_invited.extend(risk_resp.self_invited); Ok(resp) } /// 处理加群申请 #[allow(clippy::too_many_arguments)] pub async fn solve_group_system_message( &self, msg_seq: i64, req_uin: i64, group_code: i64, suspicious: bool, is_invite: bool, accept: bool, block: bool, reason: String, ) -> RQResult<()> { let pkt = self .engine .read() .await .build_system_msg_group_action_packet( msg_seq, req_uin, group_code, if suspicious { 2 } else { 1 }, is_invite, accept, block, reason, ); self.send_and_wait(pkt).await?; Ok(()) } /// 获取群列表 /// 第一个参数offset,从0开始;第二个参数count,150,另外两个都是0 pub async fn _get_group_list(&self, vec_cookie: &[u8]) -> RQResult { let req = self .engine .read() .await .build_group_list_request_packet(vec_cookie); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_list_response(resp.body) } /// 发送群消息 pub async fn send_group_message( &self, group_code: i64, message_chain: MessageChain, ) -> RQResult { self._send_group_message(group_code, message_chain.into(), None) .await } /// 发送群语音 pub async fn send_group_audio( &self, group_code: i64, group_audio: GroupAudio, ) -> RQResult { self._send_group_message(group_code, vec![], Some(group_audio.0)) .await } async fn _send_group_message( &self, group_code: i64, elems: Vec, ptt: Option, ) -> RQResult { let ran = (rand::random::() >> 1) as i32; let (tx, rx) = tokio::sync::oneshot::channel(); { self.receipt_waiters.lock().await.cache_set(ran, tx); } let req = self .engine .read() .await .build_group_sending_packet(group_code, elems, ptt, ran, 1, 0, 0, false); let _ = self.send_and_wait(req).await?; let mut receipt = MessageReceipt { seqs: vec![0], rands: vec![ran], time: UNIX_EPOCH.elapsed().unwrap().as_secs() as i64, }; match tokio::time::timeout(Duration::from_secs(5), rx).await { Ok(Ok(seq)) => { if let Some(s) = receipt.seqs.first_mut() { *s = seq; } } Ok(Err(_)) => {} //todo Err(_) => {} } Ok(receipt) } /// 发送群成员临时消息 pub async fn send_group_temp_message( &self, group_code: i64, user_uin: i64, message_chain: MessageChain, ) -> RQResult { self.send_message( pb::msg::routing_head::RoutingHead::GrpTmp(pb::msg::GrpTmp { group_uin: Some(group_code2uin(group_code)), to_uin: Some(user_uin), }), message_chain, None, ) .await } /// 获取群成员信息 pub async fn get_group_member_info( &self, group_code: i64, uin: i64, ) -> RQResult { let req = self .engine .read() .await .build_group_member_info_request_packet(group_code, uin); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_member_info_response(resp.body) } /// 批量获取群信息 pub async fn get_group_infos(&self, group_codes: Vec) -> RQResult> { let req = self .engine .read() .await .build_group_info_request_packet(group_codes); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_info_response(resp.body) } /// 获取群信息 pub async fn get_group_info(&self, group_code: i64) -> RQResult> { Ok(self.get_group_infos(vec![group_code]).await?.pop()) } /// 刷新群列表 pub async fn get_group_list(&self) -> RQResult> { // 获取群列表 let mut vec_cookie = Bytes::new(); let mut groups = Vec::new(); loop { let resp = self._get_group_list(&vec_cookie).await?; vec_cookie = resp.vec_cookie; for g in resp.groups { groups.push(g); } if vec_cookie.is_empty() { break; } } Ok(groups) } /// 获取群成员列表 (low level api) async fn _get_group_member_list( &self, group_code: i64, next_uin: i64, group_owner_uin: i64, ) -> RQResult { let req = self .engine .read() .await .build_group_member_list_request_packet(group_code, next_uin); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_member_list_response(resp.body, group_owner_uin) } /// 获取群成员列表 pub async fn get_group_member_list( &self, group_code: i64, group_owner_uin: i64, ) -> RQResult> { let mut next_uin = 0; let mut list = Vec::new(); loop { let mut resp = self ._get_group_member_list(group_code, next_uin, group_owner_uin) .await?; if resp.list.is_empty() { return Err(RQError::EmptyField("GroupMemberListResponse.list")); } for m in resp.list.iter_mut() { m.group_code = group_code; } list.append(&mut resp.list); next_uin = resp.next_uin; if next_uin == 0 { break; } } Ok(list) } /// 标记群消息已读 pub async fn mark_group_message_readed(&self, group_code: i64, seq: i32) -> RQResult<()> { let req = self .engine .read() .await .build_group_msg_readed_packet(group_code, seq); let _ = self.send_and_wait(req).await?; Ok(()) } /// 群禁言 (解除禁言 duration=0) pub async fn group_mute( &self, group_code: i64, member_uin: i64, duration: std::time::Duration, ) -> RQResult<()> { let req = self.engine.read().await.build_group_mute_packet( group_code, member_uin, duration.as_secs() as u32, ); let _ = self.send_and_wait(req).await?; Ok(()) } /// 全员禁言 pub async fn group_mute_all(&self, group_code: i64, mute: bool) -> RQResult<()> { let req = self .engine .read() .await .build_group_mute_all_packet(group_code, mute); let _ = self.send_and_wait(req).await?; Ok(()) } /// 修改群名称 pub async fn update_group_name(&self, group_code: i64, name: String) -> RQResult<()> { let req = self .engine .read() .await .build_group_name_update_packet(group_code, name); let _ = self.send_and_wait(req).await?; Ok(()) } /// 设置群公告 pub async fn update_group_memo(&self, group_code: i64, memo: String) -> RQResult<()> { let req = self .engine .read() .await .build_group_memo_update_packet(group_code, memo); let _ = self.send_and_wait(req).await?; Ok(()) } /// 设置群管理员 /// /// flag: true 设置管理员 false 取消管理员 pub async fn group_set_admin(&self, group_code: i64, member: i64, flag: bool) -> RQResult<()> { let req = self .engine .read() .await .build_group_admin_set_packet(group_code, member, flag); let _ = self.send_and_wait(req).await?; Ok(()) } /// 群戳一戳 pub async fn group_poke(&self, group_code: i64, target: i64) -> RQResult<()> { let req = self .engine .read() .await .build_group_poke_packet(group_code, target); let _ = self.send_and_wait(req).await?; Ok(()) } /// 群踢人 pub async fn group_kick( &self, group_code: i64, member_uins: Vec, kick_msg: &str, block: bool, ) -> RQResult<()> { let req = self.engine.read().await.build_group_kick_packet( group_code, member_uins, kick_msg, block, ); let _ = self.send_and_wait(req).await?; Ok(()) } pub async fn group_invite(&self, group_code: i64, uin: i64) -> RQResult<()> { let req = self .engine .read() .await .build_group_invite_packet(group_code, uin); let _ = self.send_and_wait(req).await?; Ok(()) } pub async fn group_quit(&self, group_code: i64) -> RQResult<()> { let req = self.engine.read().await.build_quit_group_packet(group_code); let _ = self.send_and_wait(req).await?; Ok(()) } /// 获取群 @全体成员 剩余次数 pub async fn group_at_all_remain(&self, group_code: i64) -> RQResult { let req = self .engine .read() .await .build_group_at_all_remain_request_packet(group_code); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_at_all_remain_response(resp.body) } /// 设置群头衔 pub async fn group_edit_special_title( &self, group_code: i64, member_uin: i64, new_title: String, ) -> RQResult<()> { let req = self .engine .read() .await .build_edit_special_title_packet(group_code, member_uin, new_title); let _ = self.send_and_wait(req).await?; Ok(()) } /// 获取自己的匿名信息(用于发送群消息) pub async fn get_anony_info(&self, group_code: i64) -> RQResult> { let req = self .engine .read() .await .build_get_anony_info_request(group_code); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_get_anony_info_response(resp.body) } /// 分享群音乐 pub async fn send_group_music_share( &self, group_code: i64, music_share: MusicShare, music_version: MusicVersion, ) -> RQResult<()> { let req = self.engine.read().await.build_share_music_request_packet( ShareTarget::Group(group_code), music_share, music_version, ); let _ = self.send_and_wait(req).await?; Ok(()) } /// 分享链接 pub async fn send_group_link_share( &self, group_code: i64, link_share: LinkShare, ) -> RQResult<()> { let req = self .engine .read() .await .build_share_link_request_packet(ShareTarget::Group(group_code), link_share); let _ = self.send_and_wait(req).await?; Ok(()) } /// 修改群名片 pub async fn edit_group_member_card( &self, group_code: i64, member_uin: i64, card: String, ) -> RQResult<()> { let req = self .engine .read() .await .build_edit_group_tag_packet(group_code, member_uin, card); let _ = self.send_and_wait(req).await?; Ok(()) } /// 撤回群消息 pub async fn recall_group_message( &self, group_code: i64, seqs: Vec, rands: Vec, ) -> RQResult<()> { let req = self .engine .read() .await .build_group_recall_packet(group_code, seqs, rands); let _ = self.send_and_wait(req).await?; Ok(()) } // 用 highway 上传群图片之前调用,获取 upload_key pub async fn get_group_image_store( &self, group_code: i64, image_info: &ImageInfo, ) -> RQResult { let req = self.engine.read().await.build_group_image_store_packet( group_code, image_info.filename.clone(), image_info.md5.clone(), image_info.size as u64, image_info.width, image_info.height, image_info.image_type as u32, ); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_image_store_response(resp.body) } /// 上传群图片 pub async fn upload_group_image(&self, group_code: i64, data: &[u8]) -> RQResult { let image_info = ImageInfo::try_new(data)?; let image_store = self.get_group_image_store(group_code, &image_info).await?; let signature = self.highway_session.read().await.session_key.to_vec(); let group_image = match image_store { GroupImageStoreResp::Exist { file_id, addrs } => image_info.into_group_image( file_id, addrs.first().copied().unwrap_or_default(), signature, ), GroupImageStoreResp::NotExist { file_id, upload_key, mut upload_addrs, } => { let addr = match self.highway_addrs.read().await.first() { Some(addr) => *addr, None => upload_addrs .pop() .ok_or(RQError::EmptyField("upload_addrs"))?, }; self.highway_upload_bdh( addr.into(), BdhInput { command_id: 2, ticket: upload_key, ext: vec![], encrypt: false, chunk_size: 256 * 1024, send_echo: true, }, data, ) .await?; image_info.into_group_image(file_id, addr, signature) } }; Ok(group_image) } /// 上传群音频 codec: 0-amr, 1-silk pub async fn upload_group_audio( &self, group_code: i64, data: &[u8], codec: u32, ) -> RQResult { let md5 = md5::compute(data).to_vec(); let size = data.len(); let ext = self.engine.read().await.build_group_try_up_ptt_req( group_code, md5.clone(), size as u64, codec, size as u32, ); let addr = self .highway_addrs .read() .await .first() .copied() .ok_or(RQError::EmptyField("highway_addrs"))?; let ticket = self .highway_session .read() .await .sig_session .clone() .to_vec(); let resp = self .highway_upload_bdh( addr.into(), BdhInput { command_id: 29, ticket, ext: ext.to_vec(), encrypt: false, chunk_size: 256 * 1024, send_echo: true, }, data, ) .await?; let file_key = self .engine .read() .await .decode_group_try_up_ptt_resp(resp)?; Ok(GroupAudio(pb::msg::Ptt { file_type: Some(4), src_uin: Some(self.uin().await), file_name: Some(format!("{}.amr", encode_hex(&md5))), file_md5: Some(md5), file_size: Some(size as i32), bool_valid: Some(true), pb_reserve: Some(vec![8, 0, 40, 0, 56, 0]), group_file_key: Some(file_key), ..Default::default() })) } pub async fn get_group_audio_url( &self, group_code: i64, audio: GroupAudio, ) -> RQResult { let req = self.engine.read().await.build_group_ptt_down_req( group_code, audio.0.file_md5.ok_or(RQError::EmptyField("file_md5"))?, ); let resp = self.send_and_wait(req).await?; self.engine.read().await.decode_group_ptt_down(resp.body) } // 用 highway 上传群视频之前调用,获取 upload_key pub async fn get_group_short_video_store( &self, short_video_upload_req: pb::short_video::ShortVideoUploadReq, ) -> RQResult { let req = self .engine .read() .await .build_group_video_store_packet(short_video_upload_req); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_video_store_response(resp.body) } /// 上传群短视频 参数:群号,视频数据,封面数据 /// TODO 未来可能会改成输入 std::io::Read pub async fn upload_group_short_video( &self, group_code: i64, video_data: &[u8], thumb_data: &[u8], ) -> RQResult { let video_md5 = md5::compute(video_data).to_vec(); let thumb_md5 = md5::compute(thumb_data).to_vec(); let video_size = video_data.len(); let thumb_size = thumb_data.len(); let short_video_up_req = self.engine.read().await.build_short_video_up_req( group_code, video_md5.clone(), thumb_md5.clone(), video_size as i64, thumb_size as i64, ); let ext = short_video_up_req.to_bytes().to_vec(); let video_store = self.get_group_short_video_store(short_video_up_req).await?; if video_store.file_exists == 1 { return Ok(VideoFile { name: format!("{}.mp4", encode_hex(&video_md5)), uuid: video_store.file_id, size: video_size as i32, thumb_size: thumb_size as i32, md5: video_md5, thumb_md5, }); } let addr = self .highway_addrs .read() .await .first() .copied() .ok_or(RQError::EmptyField("highway_addrs"))?; if self.highway_session.read().await.session_key.is_empty() { return Err(RQError::EmptyField("highway_session_key")); } let ticket = self.highway_session.read().await.sig_session.to_vec(); let mut data = Vec::with_capacity(thumb_size + video_size); data.copy_from_slice(thumb_data); data[thumb_size..].copy_from_slice(video_data); let rsp = self .highway_upload_bdh( addr.into(), BdhInput { command_id: 25, ticket, ext, encrypt: true, chunk_size: 256 * 1024, send_echo: true, }, &data, ) .await?; let rsp = pb::short_video::ShortVideoUploadRsp::decode(&*rsp) .map_err(|_| RQError::Decode("ShortVideoUploadRsp".into()))?; Ok(VideoFile { name: format!("{}.mp4", encode_hex(&video_md5)), uuid: rsp.file_id, size: video_size as i32, thumb_size: thumb_size as i32, md5: video_md5, thumb_md5, }) } /// 设置群精华消息 pub async fn operate_group_essence( &self, group_code: i64, msg_seq: i32, msg_rand: i32, flag: bool, ) -> RQResult { let req = self .engine .read() .await .build_essence_msg_operate_packet(group_code, msg_seq, msg_rand, flag); let resp = self.send_and_wait(req).await?; let decode = self .engine .read() .await .decode_essence_msg_response(resp.body)?; Ok(decode) } /// 发送群消息 /// 仅在多张图片时需要,发送文字不需要 pub async fn send_group_long_message( &self, group_code: i64, message_chain: MessageChain, ) -> RQResult { let brief = "[图片][图片][图片]"; // TODO brief let res_id = self .upload_msgs( group_code, vec![MessageNode { sender_id: self.uin().await, time: UNIX_EPOCH.elapsed().unwrap().as_secs() as i32, sender_name: self.account_info.read().await.nickname.clone(), elements: message_chain, } .into()], true, ) .await?; let template=format!( "{}

点击查看完整消息", brief, res_id, UNIX_EPOCH.elapsed().unwrap().as_millis(), brief); let mut chain = MessageChain::default(); chain.push(RichMsg { service_id: 35, template1: template, }); chain.0.extend(vec![ pb::msg::elem::Elem::Text(pb::msg::Text { str: Some("你的QQ暂不支持查看[转发多条消息],请期待后续版本。".into()), ..Default::default() }), pb::msg::elem::Elem::GeneralFlags(pb::msg::GeneralFlags { long_text_flag: Some(1), long_text_resid: Some(res_id), pendant_id: Some(0), pb_reserve: Some(vec![0x78, 0x00, 0xF8, 0x01, 0x00, 0xC8, 0x02, 0x00]), // TODO 15=73255? ..Default::default() }), ]); self._send_group_message(group_code, chain.into(), None) .await } /// 发送转发消息 pub async fn send_group_forward_message( &self, group_code: i64, msgs: Vec, ) -> RQResult { let t_sum = msgs.len(); let preview = gen_forward_preview(&msgs); let res_id = self.upload_msgs(group_code, msgs, false).await?; // TODO friend template? let template = format!( r##"群聊的聊天记录{}查看{}条转发消息"##, res_id, UNIX_EPOCH.elapsed().unwrap().as_millis(), // TODO m_filename? t_sum, preview, t_sum ); let mut chain = MessageChain::default(); chain.push(RichMsg { service_id: 35, template1: template, }); chain .0 .push(pb::msg::elem::Elem::GeneralFlags(pb::msg::GeneralFlags { pendant_id: Some(0), pb_reserve: Some(vec![0x78, 0x00, 0xF8, 0x01, 0x00, 0xC8, 0x02, 0x00]), ..Default::default() })); self._send_group_message(group_code, chain.into(), None) .await } /// 获取群主/管理员列表 pub async fn get_group_admin_list( &self, group_code: i64, ) -> RQResult> { let req = self .engine .read() .await .build_get_group_admin_list_request_packet(group_code as u64); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_get_group_admin_list_response(resp.body) } /// 群聊打卡 pub async fn group_sign_in(&self, group_code: i64) -> RQResult<()> { let req = self .engine .read() .await .build_group_sign_in_packet(group_code); self.send_and_wait(req).await?; Ok(()) } // 获取群文件列表 pub async fn get_group_file_list( &self, group_code: u64, folder_id: &str, start_index: u32, ) -> RQResult { let req = self .engine .read() .await .build_group_file_list_request_packet(group_code, folder_id.into(), start_index); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_file_list_response(resp.body) } /// 获取群文件总数 pub async fn get_group_files_count(&self, group_code: u64) -> RQResult { let req = self .engine .read() .await .build_group_file_count_request_packet(group_code); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_file_count_response(resp.body) } /// 获取文件下载链接 /// # Examples /// ``` /// # async fn test(client: &ricq::Client) { /// let group_code: i64 = 123456789; /// let file_list = client.get_group_file_list(group_code.try_into().unwrap(), "/", 0).await.unwrap(); /// for item_info in file_list.items { /// let url = client /// .get_group_file_download( /// group_code, /// &item_info.file_info.file_id, /// item_info.file_info.bus_id, /// &item_info.file_info.file_name, /// ) /// .await; /// println!("{:?}", url); /// } /// # } ///``` pub async fn get_group_file_download( &self, group_code: i64, file_id: &str, bus_id: u32, file_name: &str, ) -> RQResult { let req = self .engine .read() .await .build_group_file_download_request_packet(group_code, file_id.into(), bus_id as i32); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_group_file_download_response(resp.body, file_name) } } ================================================ FILE: ricq/src/client/api/login.rs ================================================ use std::sync::atomic::Ordering; use crate::jce::SvcRespRegister; use crate::qsign::QSignClient; use crate::{RQError, RQResult}; use ricq_core::command::wtlogin::*; use ricq_core::hex::decode_hex; use ricq_core::token::Token; /// 登录相关 impl super::super::Client { /// 二维码登录 - 获取二维码 pub async fn fetch_qrcode(&self) -> RQResult { let req = self.engine.read().await.build_qrcode_fetch_request_packet(); let resp = self.send_and_wait(req).await?; let resp = self .engine .read() .await .decode_trans_emp_response(resp.body)?; self.process_trans_emp_response(&resp).await; Ok(resp) } /// 二维码登录 - 查询二维码状态 pub async fn query_qrcode_result(&self, sig: &[u8]) -> RQResult { let req = self .engine .read() .await .build_qrcode_result_query_request_packet(sig); let resp = self.send_and_wait(req).await?; let resp = self .engine .read() .await .decode_trans_emp_response(resp.body)?; self.process_trans_emp_response(&resp).await; Ok(resp) } /// 二维码登录 - 登录 ( 可能还需要 device_lock_login ) pub async fn qrcode_login( &self, tmp_pwd: &[u8], tmp_no_pic_sig: &[u8], tgt_qr: &[u8], ) -> RQResult { let req = self.engine .read() .await .build_qrcode_login_packet(tmp_pwd, tmp_no_pic_sig, tgt_qr); let resp = self.send_and_wait(req).await?; let resp = self.engine.read().await.decode_login_response(resp.body)?; self.process_login_response(&resp).await; Ok(resp) } pub async fn sign(&self, data: &str) -> RQResult> { let uin = self.uin().await; let engine = self.engine.read().await; let sub_cmd = u8::from_str_radix(&data[4..], 16).unwrap(); let salt = QSignClient::calc_salt( uin as u64, &engine.transport.sig.guid, &engine.transport.version.sdk_version, sub_cmd as u32, ); let resp = self .qsign_client .custom_energy( uin, data, &salt, &engine.transport.sig.guid, &engine.transport.device.android_id, ) .await .map_err(|e| RQError::Other(e.to_string()))?; if resp.code != 0 { return Err(RQError::Other(format!("failed to energy {}", resp.msg))); } decode_hex(&resp.data) .map_err(|err| RQError::Other(format!("failed to decode hex: {}", err))) } /// 密码登录 - 提交密码md5 pub async fn password_md5_login( &self, uin: i64, password_md5: &[u8], ) -> RQResult { self.engine.read().await.uin.store(uin, Ordering::Relaxed); let sign = self.sign("810_9").await?; let req = self .engine .read() .await .build_login_packet(password_md5, &sign, true); let resp = self.send_and_wait(req).await?; let resp = self.engine.read().await.decode_login_response(resp.body)?; self.process_login_response(&resp).await; Ok(resp) } pub async fn password_login(&self, uin: i64, password: &str) -> RQResult { self.password_md5_login(uin, &md5::compute(password).to_vec()) .await } /// 密码登录 - 请求短信验证码 pub async fn request_sms(&self) -> RQResult { let req = self.engine.read().await.build_sms_request_packet(); let resp = self.send_and_wait(req).await?; let resp = self.engine.read().await.decode_login_response(resp.body)?; self.process_login_response(&resp).await; Ok(resp) } /// 密码登录 - 提交短信验证码 pub async fn submit_sms_code(&self, code: &str) -> RQResult { let sign = self.sign("810_7").await?; let req = self .engine .read() .await .build_sms_code_submit_packet(code.trim(), &sign); let resp = self.send_and_wait(req).await?; let resp = self.engine.read().await.decode_login_response(resp.body)?; self.process_login_response(&resp).await; Ok(resp) } /// 密码登录 - 提交滑块ticket pub async fn submit_ticket(&self, ticket: &str) -> RQResult { let sign = self.sign("810_2").await?; let req = self .engine .read() .await .build_ticket_submit_packet(ticket, &sign); let resp = self.send_and_wait(req).await?; let resp = self.engine.read().await.decode_login_response(resp.body)?; self.process_login_response(&resp).await; Ok(resp) } /// 设备锁登录 - 二维码、密码登录都需要 pub async fn device_lock_login(&self) -> RQResult { let req = self.engine.read().await.build_device_lock_login_packet(); let resp = self.send_and_wait(req).await?; let resp = self.engine.read().await.decode_login_response(resp.body)?; self.process_login_response(&resp).await; Ok(resp) } /// token 登录 pub async fn token_login(&self, token: Token) -> RQResult { self.load_token(token).await; self.request_change_sig(None).await } /// 换 token,使用后需要重新 register pub async fn request_change_sig(&self, main_sig_map: Option) -> RQResult { let req = self .engine .read() .await .build_request_change_sig_packet(main_sig_map); let resp = self.send_and_wait(req).await?; let resp = self .engine .read() .await .decode_exchange_emp_response(resp.body)?; self.process_login_response(&resp).await; Ok(resp) } /// 注册客户端,登录后必须注册 pub async fn register_client(&self) -> RQResult { let req = self.engine.read().await.build_client_register_packet(); let resp = self.send_and_wait(req).await?; let resp = self .engine .read() .await .decode_client_register_response(resp.body)?; if !resp.result.is_empty() || resp.reply_code != 0 { return Err(RQError::Other(resp.result + &resp.reply_code.to_string())); } self.online.store(true, Ordering::SeqCst); Ok(resp) } pub async fn heartbeat(&self) -> RQResult<()> { let req = self.engine.read().await.build_heartbeat_packet(); let _ = self.send_and_wait(req).await?; Ok(()) } // 系统强制下线 response pub(crate) async fn send_msg_offline_rsp(&self, uin: i64, seq_no: i64) -> RQResult<()> { let req = self .engine .read() .await .build_msf_force_offline_rsp(uin, seq_no); let _ = self.send_and_wait(req).await?; Ok(()) } pub(crate) async fn send_sid_ticket_expired_response(&self, seq: i32) -> RQResult<()> { let rsp = self .engine .read() .await .build_sid_ticket_expired_response(seq); self.send(rsp).await?; Ok(()) } } ================================================ FILE: ricq/src/client/api/mod.rs ================================================ use std::net::SocketAddr; use std::sync::atomic::Ordering; use std::time::UNIX_EPOCH; use bytes::Bytes; use cached::Cached; use ricq_core::command::message_svc::MessageSyncResponse; use ricq_core::command::oidb_svc::*; use ricq_core::common::{group_code2uin, RQAddr}; use ricq_core::highway::BdhInput; use ricq_core::msg::MessageChain; use ricq_core::pb; use ricq_core::structs::Status; use ricq_core::structs::SummaryCardInfo; use ricq_core::structs::{ForwardMessage, MessageReceipt}; use crate::jce::SvcDevLoginInfo; use crate::{RQError, RQResult}; mod friend; mod group; mod login; /// API impl super::Client { /// 设置在线状态 TODO net_type pub async fn update_online_status(&self, status: T) -> RQResult<()> where T: Into, { let status = status.into(); if let Some(ref custom_status) = status.custom_status { if custom_status.wording.is_empty() || custom_status.wording.chars().count() > 4 { return Err(RQError::Other("invalid wording length".into())); } } let req = self.engine.read().await.build_set_online_status_packet( status.online_status, status.ext_online_status, status.custom_status, ); let _ = self.send_and_wait(req).await?; Ok(()) } /// 修改签名 pub async fn update_signature(&self, signature: String) -> RQResult<()> { let req = self .engine .read() .await .build_update_signature_packet(signature); let _ = self.send_and_wait(req).await?; Ok(()) } /// 修改个人资料 pub async fn update_profile_detail(&self, profile: ProfileDetailUpdate) -> RQResult<()> { let req = self .engine .read() .await .build_update_profile_detail_packet(profile); let _ = self.send_and_wait(req).await?; Ok(()) } /// 刷新客户端状态 pub async fn refresh_status(&self) -> RQResult<()> { let req = self .engine .read() .await .build_get_offline_msg_request_packet(self.last_message_time.load(Ordering::SeqCst)); let _resp = self.send_and_wait(req).await?; Ok(()) } /// 获取通过安全验证的设备 pub async fn get_allowed_clients(&self) -> RQResult> { let req = self.engine.read().await.build_device_list_request_packet(); let resp = self.send_and_wait(req).await?; self.engine.read().await.decode_dev_list_response(resp.body) } /// 文本翻译 pub async fn translate( &self, src_language: String, dst_language: String, src_text_list: Vec, ) -> RQResult> { let list_len = src_text_list.len(); let req = self.engine.read().await.build_translate_request_packet( src_language, dst_language, src_text_list, ); let resp = self.send_and_wait(req).await?; let translations = self .engine .read() .await .decode_translate_response(resp.body)?; if translations.len() != list_len { return Err(RQError::Other("translate length error".into())); } Ok(translations) } // source 0-自己 1-好友 2-群成员 // cookie source=1时 在 summary info 获取 pub async fn send_like( &self, uin: i64, count: i32, source: i32, cookies: Bytes, ) -> RQResult<()> { let req = self .engine .read() .await .build_send_like_packet(uin, count, source, cookies); let _ = self.send_and_wait(req).await?; Ok(()) } // TODO 待完善 // 图片 OCR pub async fn image_ocr( &self, img_url: String, md5: String, size: i32, wight: i32, height: i32, ) -> RQResult { let req = self .engine .read() .await .build_image_ocr_request_packet(img_url, md5, size, wight, height); let resp = self.send_and_wait(req).await?; let decode = self .engine .read() .await .decode_image_ocr_response(resp.body)?; Ok(decode) } // 标记消息已收到,server 不再重复推送 pub async fn delete_message(&self, items: Vec) -> RQResult<()> { let req = self .engine .read() .await .build_delete_message_request_packet(items); let _ = self.send_and_wait(req).await?; Ok(()) } // 标记 online_push 已收到,server 不再重复推送 pub async fn delete_online_push( &self, uin: i64, svrip: i32, push_token: Bytes, seq: u16, del_msg: Vec, ) -> RQResult<()> { let req = self .engine .read() .await .build_delete_online_push_packet(uin, svrip, push_token, seq, del_msg); self.send(req).await?; Ok(()) } // sync message async fn sync_message(&self, sync_flag: i32) -> RQResult { let time = UNIX_EPOCH.elapsed().unwrap().as_secs() as i64; let req = self .engine .read() .await .build_get_message_request_packet(sync_flag, time); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_message_svc_packet(resp.body) } // 从服务端拉取通知 pub(crate) async fn sync_all_message(&self) -> RQResult> { const SYNC_START: i32 = 0; const _SYNC_CONTINUE: i32 = 1; const SYNC_STOP: i32 = 2; let mut sync_flag = SYNC_START; let mut msgs = Vec::new(); loop { let resp = match self.sync_message(sync_flag).await { Ok(resp) => resp, Err(_) => { tracing::warn!("failed to sync_message"); break; } }; if let Err(err) = self .delete_message( resp.msgs .iter() .map(|m| { let head = m.head.as_ref().unwrap(); pb::MessageItem { from_uin: head.from_uin(), to_uin: head.to_uin(), msg_type: head.msg_type(), msg_seq: head.msg_seq(), msg_uid: head.msg_uid(), ..Default::default() } }) .collect(), ) .await { tracing::warn!("failed to delete_message: {}", err); break; } match resp.msg_rsp_type { 0 => { let mut engine = self.engine.write().await; if let Some(sync_cookie) = resp.sync_cookie { engine.transport.sig.sync_cookie = Bytes::from(sync_cookie) } if let Some(pub_account_cookie) = resp.pub_account_cookie { engine.transport.sig.pub_account_cookie = Bytes::from(pub_account_cookie) } } 1 => { let mut engine = self.engine.write().await; if let Some(sync_cookie) = resp.sync_cookie { engine.transport.sig.sync_cookie = Bytes::from(sync_cookie) } } 2 => { let mut engine = self.engine.write().await; if let Some(pub_account_cookie) = resp.pub_account_cookie { engine.transport.sig.pub_account_cookie = Bytes::from(pub_account_cookie) } } _ => {} } msgs.extend(resp.msgs); sync_flag = resp.sync_flag; if sync_flag == SYNC_STOP { break; } } Ok(msgs) } // 获取名片信息 pub async fn get_summary_info(&self, uin: i64) -> RQResult { let req = self .engine .read() .await .build_summary_card_request_packet(uin); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_summary_card_response(resp.body) } // 准备上传消息,获取 ukey, resid, ip, port async fn multi_msg_apply_up( &self, dst_uin: i64, data: &[u8], is_long: bool, ) -> RQResult { let req = self.engine.read().await.build_multi_msg_apply_up_req( data.len() as i64, md5::compute(data).to_vec(), if is_long { 1 } else { 2 }, dst_uin, ); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_multi_msg_apply_up_resp(resp.body) } // 上传长消息、转发消息 私聊未测试 pub async fn upload_msgs( &self, group_code: i64, msgs: Vec, is_long: bool, ) -> RQResult { let data = self .engine .read() .await .calculate_validation_data(msgs, group_code); let rsp = self .multi_msg_apply_up(group_code2uin(group_code), &data, is_long) .await?; let resid = rsp.msg_resid; if self.highway_session.read().await.session_key.is_empty() { return Err(RQError::EmptyField("highway_session_key is empty")); } let addrs: Vec = rsp .uint32_up_ip .into_iter() .zip(rsp.uint32_up_port) .map(|(ip, port)| RQAddr(ip as u32, port as u16)) .collect(); let body = self.engine .read() .await .build_long_req(group_code2uin(group_code), data, rsp.msg_ukey); for addr in addrs { match self .highway_upload_bdh( addr.into(), BdhInput { command_id: 27, ticket: rsp.msg_sig.clone(), chunk_size: 8192 * 8, ..Default::default() }, &body, ) .await { Ok(_) => return Ok(resid), Err(_) => continue, } } Err(RQError::Other("failed to upload long message".into())) } // 获取转发消息下载地址和 key async fn multi_msg_apply_down( &self, res_id: String, ) -> RQResult { let req = self .engine .read() .await .build_multi_msg_apply_down_req(res_id); let resp = self.send_and_wait(req).await?; self.engine .read() .await .decode_multi_msg_apply_down_resp(resp.body) } pub async fn download_msgs(&self, res_id: String) -> RQResult> { let mut resp = self.multi_msg_apply_down(res_id).await?; if resp.result != 0 { return Err(RQError::Other(format!( "multi_msg_apply_down result {}", resp.result ))); } let prefix=if let Some(pb::multimsg::ExternMsg { channel_type }) = resp.msg_extern_info && channel_type == 2 { "https://ssl.htdata.qq.com".into() } else { let addr = SocketAddr::from(RQAddr(resp.down_ip.pop().ok_or(RQError::EmptyField("down_ip"))?,resp.down_port.pop().ok_or(RQError::EmptyField("down_port"))? as u16)); format!("http://{addr}") }; let _url = format!( "{}{}", prefix, String::from_utf8_lossy(&resp.thumb_down_para) ); let _encrypt_key = resp.msg_key; // TODO get data and decrypt // TODO decoder -> LongRspBody // TODO uncompress // TODO link message, convert to Vec todo!() } /// 发送消息 pub async fn send_message( &self, routing_head: pb::msg::routing_head::RoutingHead, message_chain: MessageChain, ptt: Option, ) -> RQResult { let time = UNIX_EPOCH.elapsed().unwrap().as_secs() as i64; let seq = self.engine.read().await.next_friend_seq(); let ran = (rand::random::() >> 1) as i32; let (tx, _) = tokio::sync::oneshot::channel(); { self.receipt_waiters.lock().await.cache_set(ran, tx); } let req = self.engine.read().await.build_send_message_packet( routing_head, message_chain.into(), ptt, seq, ran, time, ); self.send_and_wait(req).await?; let receipt = MessageReceipt { seqs: vec![seq], rands: vec![ran], time: UNIX_EPOCH.elapsed().unwrap().as_secs() as i64, }; // 除了群聊,都不需要等 receipt 的 seq Ok(receipt) } } ================================================ FILE: ricq/src/client/event.rs ================================================ use std::sync::Arc; use ricq_core::command::profile_service::{JoinGroupRequest, NewFriendRequest, SelfInvited}; use ricq_core::structs::{ DeleteFriend, FriendAudioMessage, FriendInfo, FriendMessageRecall, FriendPoke, GroupAudioMessage, GroupDisband, GroupLeave, GroupMessageRecall, GroupMute, GroupNameUpdate, GroupPoke, GroupTempMessage, MemberPermissionChange, NewMember, }; use ricq_core::{jce, RQResult}; use crate::client::NetworkStatus; use crate::structs::{FriendMessage, GroupMessage}; use crate::Client; #[derive(Clone, derivative::Derivative)] #[derivative(Debug)] pub struct EventWithClient { #[derivative(Debug = "ignore")] pub client: Arc, pub inner: T, } pub type GroupMessageEvent = EventWithClient; impl GroupMessageEvent { pub async fn recall(&self) -> RQResult<()> { // TODO check permission self.client .recall_group_message( self.inner.group_code, self.inner.seqs.clone(), self.inner.rands.clone(), ) .await } } pub type FriendMessageEvent = EventWithClient; pub type GroupTempMessageEvent = EventWithClient; pub type JoinGroupRequestEvent = EventWithClient; impl JoinGroupRequestEvent { pub async fn accept(&self) -> RQResult<()> { self.client .solve_group_system_message( self.inner.msg_seq, self.inner.req_uin, self.inner.group_code, self.inner.suspicious, self.inner.invitor_uin.is_some(), true, false, "".into(), ) .await } pub async fn reject(&self, reason: String, block: bool) -> RQResult<()> { self.client .solve_group_system_message( self.inner.msg_seq, self.inner.req_uin, self.inner.group_code, self.inner.suspicious, self.inner.invitor_uin.is_some(), false, block, reason, ) .await } } pub type NewFriendRequestEvent = EventWithClient; impl NewFriendRequestEvent { pub async fn accept(&self) -> RQResult<()> { self.client .solve_friend_system_message(self.inner.msg_seq, self.inner.req_uin, true) .await } pub async fn reject(&self) -> RQResult<()> { self.client .solve_friend_system_message(self.inner.msg_seq, self.inner.req_uin, false) .await } } pub type NewMemberEvent = EventWithClient; pub type GroupMuteEvent = EventWithClient; pub type FriendMessageRecallEvent = EventWithClient; pub type GroupMessageRecallEvent = EventWithClient; pub type NewFriendEvent = EventWithClient; pub type GroupLeaveEvent = EventWithClient; pub type GroupDisbandEvent = EventWithClient; pub type FriendPokeEvent = EventWithClient; pub type GroupPokeEvent = EventWithClient; pub type GroupNameUpdateEvent = EventWithClient; pub type DeleteFriendEvent = EventWithClient; pub type MemberPermissionChangeEvent = EventWithClient; pub type SelfInvitedEvent = EventWithClient; pub type GroupAudioMessageEvent = EventWithClient; impl GroupAudioMessageEvent { pub async fn url(&self) -> RQResult { self.client .get_group_audio_url(self.inner.group_code, self.inner.audio.clone()) .await } } pub type FriendAudioMessageEvent = EventWithClient; impl FriendAudioMessageEvent { pub async fn url(&self) -> RQResult { self.client .get_friend_audio_url(self.inner.from_uin, self.inner.audio.clone()) .await } } pub type KickedOfflineEvent = EventWithClient; pub type MSFOfflineEvent = EventWithClient; #[derive(Copy, Clone, Debug)] #[repr(u8)] pub enum DisconnectReason { /// 主动断开 Actively(NetworkStatus), /// 网络原因 Network, } impl DisconnectReason { /// 客户端网络状态 pub fn status(&self) -> NetworkStatus { match self { Self::Actively(s) => *s, Self::Network => NetworkStatus::NetworkOffline, } } } pub type ClientDisconnect = EventWithClient; impl ClientDisconnect { pub fn reason(&self) -> DisconnectReason { self.inner } } ================================================ FILE: ricq/src/client/handler/mod.rs ================================================ use std::future::Future; use std::pin::Pin; use async_trait::async_trait; use tokio::sync::{ broadcast::Sender as BroadcastSender, mpsc::{Sender as MpscSender, UnboundedSender}, watch::Sender as WatchSender, }; use crate::client::event::*; /// 所有需要外发的数据的枚举打包 #[derive(Clone, derivative::Derivative)] #[derivative(Debug)] pub enum QEvent { /// 登录成功事件 Login(i64), /// 群消息 GroupMessage(GroupMessageEvent), /// 群语音 GroupAudioMessage(GroupAudioMessageEvent), /// 好友消息 FriendMessage(FriendMessageEvent), /// 群语音 FriendAudioMessage(FriendAudioMessageEvent), /// 群临时消息 GroupTempMessage(GroupTempMessageEvent), /// 加群申请 GroupRequest(JoinGroupRequestEvent), /// 加群申请 SelfInvited(SelfInvitedEvent), /// 加好友申请 NewFriendRequest(NewFriendRequestEvent), /// 新成员入群 NewMember(NewMemberEvent), /// 成员被禁言 GroupMute(GroupMuteEvent), /// 好友消息撤回 FriendMessageRecall(FriendMessageRecallEvent), /// 群消息撤回 GroupMessageRecall(GroupMessageRecallEvent), /// 新好友 NewFriend(NewFriendEvent), /// 退群/被踢 GroupLeave(GroupLeaveEvent), /// 群解散 GroupDisband(GroupDisbandEvent), /// 好友戳一戳 FriendPoke(FriendPokeEvent), /// 群成员戳一戳 GroupPoke(GroupPokeEvent), /// 群名称修改 GroupNameUpdate(GroupNameUpdateEvent), /// 好友删除 DeleteFriend(DeleteFriendEvent), /// 群成员权限变更 MemberPermissionChange(MemberPermissionChangeEvent), /// 被其他客户端踢下线 /// 不能用于掉线重连,掉线重连以 start 返回为准 KickedOffline(KickedOfflineEvent), /// 服务端强制下线 /// 不能用于掉线重连,掉线重连以 start 返回为准 MSFOffline(MSFOfflineEvent), /// 网络原因/客户端主动掉线 /// 可用于掉线重连 ClientDisconnect(ClientDisconnect), } /// 处理外发数据的接口 /// /// 同时,所有 `async fn(QEvent)` 都已自动实现 `Handler`。 /// /// # Examples /// /// 你可以为自己的 struct 实现 Handler: /// /// ```ignore /// struct MyHandler; /// impl Handler for MyHandler { ... } /// ``` /// /// 或者只定义单个事件处理函数,更简洁: /// /// ``` /// # use ricq::{Client, Device, Protocol, handler::QEvent}; /// # fn test(device: Device) { /// async fn on_event(e: QEvent) { /// dbg!(e); /// } /// let client = Client::new( /// device, /// Protocol::MacOS.into(), /// on_event as fn(_) -> _, /// ); /// # } /// ``` #[async_trait] pub trait Handler: Sync { async fn handle(&self, event: QEvent); } // 这里还有一种 Fn(QEvent) -> Fut 的写法,但是会与 PartlyHandler 冲突 impl Handler for fn(QEvent) -> Fut where Fut: Future + Send, { fn handle<'a: 'b, 'b>(&'a self, e: QEvent) -> Pin + Send + 'b>> { Box::pin(async move { self(e).await }) } } /// 一个默认 Handler,只是把信息打印出来 pub struct DefaultHandler; #[async_trait] impl Handler for DefaultHandler { async fn handle(&self, e: QEvent) { match e { QEvent::GroupMessage(m) => { tracing::info!( "MESSAGE (GROUP={}): {}", m.inner.group_code, m.inner.elements ) } QEvent::FriendMessage(m) => { tracing::info!( "MESSAGE (FRIEND={}): {}", m.inner.from_uin, m.inner.elements ) } QEvent::GroupTempMessage(m) => { tracing::info!("MESSAGE (TEMP={}): {}", m.inner.from_uin, m.inner.elements) } QEvent::GroupRequest(m) => { tracing::info!( "REQUEST (GROUP={}, UIN={}): {}", m.inner.group_code, m.inner.req_uin, m.inner.message ) } QEvent::NewFriendRequest(m) => { tracing::info!("REQUEST (UIN={}): {}", m.inner.req_uin, m.inner.message) } _ => tracing::info!("{:?}", e), } } } #[async_trait] impl Handler for BroadcastSender { async fn handle(&self, msg: QEvent) { self.send(msg).ok(); } } #[async_trait] impl Handler for MpscSender { async fn handle(&self, msg: QEvent) { self.send(msg).await.ok(); } } #[async_trait] impl Handler for UnboundedSender { async fn handle(&self, msg: QEvent) { self.send(msg).ok(); } } #[async_trait] impl Handler for WatchSender { async fn handle(&self, msg: QEvent) { self.send(msg).ok(); } } #[async_trait] pub trait PartlyHandler: Sync { async fn handle_login(&self, _: i64) {} async fn handle_group_message(&self, _event: GroupMessageEvent) {} async fn handle_group_audio(&self, _event: GroupAudioMessageEvent) {} async fn handle_friend_message(&self, _event: FriendMessageEvent) {} async fn handle_friend_audio(&self, _event: FriendAudioMessageEvent) {} async fn handle_group_temp_message(&self, _event: GroupTempMessageEvent) {} async fn handle_group_request(&self, _event: JoinGroupRequestEvent) {} async fn handle_self_invited(&self, _event: SelfInvitedEvent) {} async fn handle_friend_request(&self, _event: NewFriendRequestEvent) {} async fn handle_new_member(&self, _event: NewMemberEvent) {} async fn handle_group_mute(&self, _event: GroupMuteEvent) {} async fn handle_friend_message_recall(&self, _event: FriendMessageRecallEvent) {} async fn handle_group_message_recall(&self, _event: GroupMessageRecallEvent) {} async fn handle_new_friend(&self, _event: NewFriendEvent) {} async fn handle_group_leave(&self, _event: GroupLeaveEvent) {} async fn handle_group_disband(&self, _event: GroupDisbandEvent) {} async fn handle_friend_poke(&self, _event: FriendPokeEvent) {} async fn handle_group_poke(&self, _event: GroupPokeEvent) {} async fn handle_group_name_update(&self, _event: GroupNameUpdateEvent) {} async fn handle_delete_friend(&self, _event: DeleteFriendEvent) {} async fn handle_member_permission_change(&self, _event: MemberPermissionChangeEvent) {} async fn handle_kicked_offline(&self, _event: KickedOfflineEvent) {} async fn handle_msf_offline(&self, _event: MSFOfflineEvent) {} async fn handle_client_disconnect(&self, _event: ClientDisconnect) {} } #[async_trait] impl Handler for PH where PH: PartlyHandler, { async fn handle(&self, event: QEvent) { match event { QEvent::Login(uin) => self.handle_login(uin).await, QEvent::GroupMessage(m) => self.handle_group_message(m).await, QEvent::GroupAudioMessage(m) => self.handle_group_audio(m).await, QEvent::FriendMessage(m) => self.handle_friend_message(m).await, QEvent::FriendAudioMessage(m) => self.handle_friend_audio(m).await, QEvent::GroupTempMessage(m) => self.handle_group_temp_message(m).await, QEvent::GroupRequest(m) => self.handle_group_request(m).await, QEvent::SelfInvited(m) => self.handle_self_invited(m).await, QEvent::NewFriendRequest(m) => self.handle_friend_request(m).await, QEvent::NewMember(m) => self.handle_new_member(m).await, QEvent::GroupMute(m) => self.handle_group_mute(m).await, QEvent::FriendMessageRecall(m) => self.handle_friend_message_recall(m).await, QEvent::GroupMessageRecall(m) => self.handle_group_message_recall(m).await, QEvent::NewFriend(m) => self.handle_new_friend(m).await, QEvent::GroupLeave(m) => self.handle_group_leave(m).await, QEvent::GroupDisband(m) => self.handle_group_disband(m).await, QEvent::FriendPoke(m) => self.handle_friend_poke(m).await, QEvent::GroupPoke(m) => self.handle_group_poke(m).await, QEvent::GroupNameUpdate(m) => self.handle_group_name_update(m).await, QEvent::DeleteFriend(m) => self.handle_delete_friend(m).await, QEvent::MemberPermissionChange(m) => self.handle_member_permission_change(m).await, QEvent::KickedOffline(m) => self.handle_kicked_offline(m).await, QEvent::MSFOffline(m) => self.handle_msf_offline(m).await, QEvent::ClientDisconnect(m) => self.handle_client_disconnect(m).await, } } } ================================================ FILE: ricq/src/client/highway/codec.rs ================================================ use bytes::{Buf, BufMut, BytesMut}; use tokio_util::codec::{Decoder, Encoder}; use ricq_core::RQError; use crate::client::highway::HighwayFrame; pub struct HighwayCodec; impl Encoder for HighwayCodec { type Error = RQError; fn encode(&mut self, item: HighwayFrame, dst: &mut BytesMut) -> Result<(), Self::Error> { dst.put_u8(40); dst.put_u32(item.head.len() as u32); dst.put_u32(item.body.len() as u32); dst.put_slice(&item.head); dst.put_slice(&item.body); dst.put_u8(41); Ok(()) } } impl Decoder for HighwayCodec { type Item = HighwayFrame; type Error = RQError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { if src.len() < 10 { return Ok(None); } src.get_u8(); let head_length = src.get_u32() as usize; let body_length = src.get_u32() as usize; if head_length + body_length + 1 > src.remaining() { return Ok(None); } let head = src.copy_to_bytes(head_length); let body = src.copy_to_bytes(body_length); src.get_u8(); Ok(Some(Self::Item { head, body })) } } ================================================ FILE: ricq/src/client/highway/mod.rs ================================================ use bytes::Bytes; mod codec; mod net; pub struct HighwayFrame { pub head: Bytes, pub body: Bytes, } ================================================ FILE: ricq/src/client/highway/net.rs ================================================ use std::net::SocketAddr; use std::time::Duration; use bytes::Bytes; use futures_util::{SinkExt, StreamExt}; use tokio::net::TcpStream; use tokio_util::codec::Framed; use ricq_core::command::common::PbToBytes; use ricq_core::crypto::qqtea_encrypt; use ricq_core::highway::BdhInput; use ricq_core::{pb, RQError, RQResult}; use crate::client::highway::codec::HighwayCodec; use crate::client::highway::HighwayFrame; use crate::client::tcp::tcp_connect_timeout; use crate::Client; impl Client { pub async fn highway_upload_bdh( &self, addr: SocketAddr, mut input: BdhInput, data: &[u8], ) -> RQResult { if input.encrypt { let session_key = self.highway_session.read().await.session_key.clone(); input.ext = qqtea_encrypt(&input.ext, &session_key) } let stream = tcp_connect_timeout(addr, Duration::from_secs(5)) .await .map_err(RQError::IO)?; let mut stream = Framed::new(stream, HighwayCodec); // send heartbeat let sum = md5::compute(data).to_vec(); let length = data.len(); if input.send_echo { stream .send(HighwayFrame { head: self.highway_session.read().await.build_heartbreak(), body: Bytes::new(), }) .await?; let _ = read_response(&mut stream).await?; } let mut ticket = input.ticket; let mut rsp_ext = Bytes::new(); let data = Bytes::copy_from_slice(data); let len = data.len(); let chunk_size = input.chunk_size; for i in (0..len).step_by(chunk_size) { let min = std::cmp::min(i + chunk_size, len); let chunk = data.slice(i..min); let head = pb::ReqDataHighwayHead { msg_basehead: Some(self.highway_session.read().await.build_basehead( "PicUp.DataUp".into(), 4096, input.command_id, 2052, )), msg_seghead: Some(self.highway_session.read().await.build_seghead( length as i64, i as i64, &chunk, ticket.clone(), sum.clone(), )), req_extendinfo: input.ext.clone(), ..Default::default() }; stream .send(HighwayFrame { head: head.to_bytes(), body: chunk, }) .await?; let resp = read_response(&mut stream).await?; let rsp_head = self .highway_session .read() .await .decode_rsp_head(resp.head)?; if rsp_head.error_code != 0 { return Err(RQError::Other(format!( "error_code = {}", rsp_head.error_code ))); } if !rsp_head.rsp_extendinfo.is_empty() { rsp_ext = Bytes::from(rsp_head.rsp_extendinfo) } if let Some(h) = rsp_head.msg_seghead { if !h.serviceticket.is_empty() { ticket = h.serviceticket } } } Ok(rsp_ext) } } async fn read_response(stream: &mut Framed) -> RQResult { loop { if let Some(resp) = stream.next().await { return resp; } } } ================================================ FILE: ricq/src/client/mod.rs ================================================ use bytes::Bytes; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU8, Ordering}; use std::sync::Arc; use std::time::UNIX_EPOCH; use cached::Cached; use futures_util::StreamExt; use tokio::sync::{broadcast, RwLock}; use tokio::sync::{oneshot, Mutex}; use tokio::time::{sleep, Duration}; pub use net::{Connector, DefaultConnector}; use ricq_core::command::common::PbToBytes; use ricq_core::command::online_push::GroupMessagePart; use ricq_core::command::profile_service::GroupSystemMessages; use ricq_core::common::RQAddr; use ricq_core::hex::decode_hex; use ricq_core::protocol::version::Version; use ricq_core::protocol::{device::Device, packet::Packet}; use ricq_core::structs::{AccountInfo, AddressInfo, OtherClientInfo}; use ricq_core::Engine; pub use ricq_core::Token; use crate::qsign::{QSignClient, QSignResponse, RequestCallback, SignData}; use crate::{RQError, RQResult}; mod api; pub mod event; pub mod handler; mod highway; pub(crate) mod net; mod processor; pub mod qimei; mod tcp; const SIGN_COMMANDS: &str = r#"ConnAuthSvr.fast_qq_login ConnAuthSvr.sdk_auth_api ConnAuthSvr.sdk_auth_api_emp FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoBarrage FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoComment FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoFollow FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoLike FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoPush FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.DoReply FeedCloudSvr.trpc.feedcloud.commwriter.ComWriter.PublishFeed FeedCloudSvr.trpc.videocircle.circleprofile.CircleProfile.SetProfile friendlist.addFriend friendlist.AddFriendReq friendlist.ModifyGroupInfoReq MessageSvc.PbSendMsg MsgProxy.SendMsg OidbSvc.0x4ff_9 OidbSvc.0x4ff_9_IMCore OidbSvc.0x56c_6 OidbSvc.0x6d9_4 OidbSvc.0x758 OidbSvc.0x758_0 OidbSvc.0x758_1 OidbSvc.0x88d_0 OidbSvc.0x89a_0 OidbSvc.0x89b_1 OidbSvc.0x8a1_0 OidbSvc.0x8a1_7 OidbSvc.0x8ba OidbSvc.0x9fa OidbSvc.oidb_0x758 OidbSvcTrpcTcp.0x101e_1 OidbSvcTrpcTcp.0x101e_2 OidbSvcTrpcTcp.0x1100_1 OidbSvcTrpcTcp.0x1105_1 OidbSvcTrpcTcp.0x1107_1 OidbSvcTrpcTcp.0x55f_0 OidbSvcTrpcTcp.0x6d9_4 OidbSvcTrpcTcp.0xf55_1 OidbSvcTrpcTcp.0xf57_1 OidbSvcTrpcTcp.0xf57_106 OidbSvcTrpcTcp.0xf57_9 OidbSvcTrpcTcp.0xf65_1 OidbSvcTrpcTcp.0xf65_10 OidbSvcTrpcTcp.0xf67_1 OidbSvcTrpcTcp.0xf67_5 OidbSvcTrpcTcp.0xf6e_1 OidbSvcTrpcTcp.0xf88_1 OidbSvcTrpcTcp.0xf89_1 OidbSvcTrpcTcp.0xfa5_1 ProfileService.getGroupInfoReq ProfileService.GroupMngReq QChannelSvr.trpc.qchannel.commwriter.ComWriter.DoComment QChannelSvr.trpc.qchannel.commwriter.ComWriter.DoReply QChannelSvr.trpc.qchannel.commwriter.ComWriter.PublishFeed qidianservice.135 qidianservice.207 qidianservice.269 qidianservice.290 SQQzoneSvc.addComment SQQzoneSvc.addReply SQQzoneSvc.forward SQQzoneSvc.like SQQzoneSvc.publishmood SQQzoneSvc.shuoshuo trpc.group_pro.msgproxy.sendmsg trpc.login.ecdh.EcdhService.SsoNTLoginPasswordLoginUnusualDevice trpc.o3.ecdh_access.EcdhAccess.SsoEstablishShareKey trpc.o3.ecdh_access.EcdhAccess.SsoSecureA2Access trpc.o3.ecdh_access.EcdhAccess.SsoSecureA2Establish trpc.o3.ecdh_access.EcdhAccess.SsoSecureAccess trpc.o3.report.Report.SsoReport trpc.passwd.manager.PasswdManager.SetPasswd trpc.passwd.manager.PasswdManager.VerifyPasswd trpc.qlive.relationchain_svr.RelationchainSvr.Follow trpc.qlive.word_svr.WordSvr.NewPublicChat trpc.qqhb.qqhb_proxy.Handler.sso_handle trpc.springfestival.redpacket.LuckyBag.SsoSubmitGrade wtlogin.device_lock wtlogin.exchange_emp wtlogin.login wtlogin.name2uin wtlogin.qrlogin wtlogin.register wtlogin.trans_emp wtlogin_device.login wtlogin_device.tran_sim_emp"#; pub struct Client { /// QEvent Handler 调用 handle 方法外发 QEvent handler: Box, pub engine: RwLock, // 状态相关 /// 网络状态 status: AtomicU8, /// 停止网络信号 Sender disconnect_signal: broadcast::Sender<()>, /// 是否在线 pub online: AtomicBool, /// 心跳包是否已启用 pub heartbeat_enabled: AtomicBool, // 包相关 /// 外发包 Sender out_pkt_sender: net::OutPktSender, /// send_and_wait WaitMap packet_promises: RwLock>>, /// 当前客户端发送消息后使用 cache 避免上报自身消息事件 receipt_waiters: Mutex>>, // account info pub account_info: RwLock, // address pub address: RwLock, /// 其他同时在线客户端 pub online_clients: RwLock>, // statics pub last_message_time: AtomicI64, /// 调用 new 方法时的时间戳 pub start_time: i32, /// 群消息 builder 寄存 : parts is sorted by pkg_index group_message_builder: RwLock>>, /// 每个 28 Byte c2c_cache: RwLock>, push_req_cache: RwLock>, push_trans_cache: RwLock>, group_sys_message_cache: RwLock, pub highway_session: RwLock, pub highway_addrs: RwLock>, packet_handler: RwLock>>, pub qsign_client: Arc, } impl super::Client { /// 新建 Clinet /// /// **Notice: 该方法仅新建 Client 需要调用 start 方法连接到服务器** pub fn new( device: Device, version: Version, qsign_client: Arc, handler: H, ) -> Client where H: crate::client::handler::Handler + 'static + Sync + Send, { let (out_pkt_sender, _) = tokio::sync::broadcast::channel(1024); let (disconnect_signal, _) = tokio::sync::broadcast::channel(8); Client { handler: Box::new(handler), engine: RwLock::new(Engine::new(device, version)), status: AtomicU8::new(NetworkStatus::Unknown as u8), heartbeat_enabled: AtomicBool::new(false), online: AtomicBool::new(false), out_pkt_sender, disconnect_signal, // out_going_packet_session_id: RwLock::new(Bytes::from_static(&[0x02, 0xb0, 0x5b, 0x8b])), packet_promises: Default::default(), receipt_waiters: Mutex::new(cached::TimedCache::with_lifespan(60)), account_info: Default::default(), address: Default::default(), online_clients: Default::default(), last_message_time: Default::default(), start_time: UNIX_EPOCH.elapsed().unwrap().as_secs() as i32, group_message_builder: RwLock::new(cached::TimedCache::with_lifespan(600)), c2c_cache: RwLock::new(cached::TimedCache::with_lifespan(3600)), push_req_cache: RwLock::new(cached::TimedCache::with_lifespan(30)), push_trans_cache: RwLock::new(cached::TimedCache::with_lifespan(15)), group_sys_message_cache: RwLock::new(Default::default()), highway_session: RwLock::new(Default::default()), highway_addrs: RwLock::new(Default::default()), packet_handler: Default::default(), qsign_client, } } /// 新建 Clinet /// /// **Notice: 该方法仅新建 Client 需要调用 start 方法连接到服务器** pub fn new_with_config( config: crate::Config, qsign_client: Arc, handler: H, ) -> Self where H: crate::client::handler::Handler + 'static + Sync + Send, { Self::new(config.device, config.version, qsign_client, handler) } /// 获取当前 Client uin pub async fn uin(&self) -> i64 { self.engine.read().await.uin.load(Ordering::Relaxed) } pub async fn sign_packet(&self, pkt: &mut Packet) -> RQResult> { if !SIGN_COMMANDS.contains(&pkt.command_name) { return Ok(Default::default()); } let engine = self.engine.read().await; let resp = self .qsign_client .sign( pkt.uin, engine.transport.version.qua, &pkt.command_name, pkt.seq_id, &pkt.body, &engine .transport .device .qimei .as_ref() .map(|qimei| qimei.q36.as_str()) .unwrap_or_default(), &engine.transport.device.android_id, &engine.transport.sig.guid, ) .await .map_err(|err| RQError::Other(format!("failed to sign packet: {err}")))?; if resp.code != 0 { return Err(RQError::Other(format!( "failed to sign packet, msg: {}", resp.msg ))); } let sign = ricq_core::pb::SsoReserveField { flag: 0, qimei: engine .transport .device .qimei .clone() .unwrap_or_default() .q16, newconn_flag: 0, uid: pkt.uin.to_string(), imsi: 0, network_type: 1, ip_stack_type: 1, message_type: 0, sec_info: Some(ricq_core::pb::SsoSecureInfo { sec_sig: decode_hex(&resp.data.sign).unwrap_or_default(), sec_device_token: decode_hex(&resp.data.token).unwrap_or_default(), sec_extra: decode_hex(&resp.data.extra).unwrap_or_default(), }), sso_ip_origin: 0, } .to_bytes(); pkt.sign = Some(sign); Ok(resp) } pub async fn process_sign_callback(&self, callbacks: Vec) { let callbacks: Vec<(i64, Packet)> = { let engine = self.engine.read().await; callbacks .into_iter() .map(|cb| { ( cb.callback_id, engine.uni_packet( &cb.cmd, Bytes::from(decode_hex(&cb.body).unwrap_or_default()), ), ) }) .collect() }; let _: Vec<_> = futures_util::stream::iter(callbacks) .map(|(id, pkt)| async move { let uin = pkt.uin; let cmd = pkt.command_name.clone(); let resp = self.send_and_wait(pkt).await; if let Err(ref err) = resp { tracing::error!( "failed to process sign callback, id: {id}, cmd: {cmd}, err: {err}" ) } let resp = resp.unwrap_or_default(); if let Err(err) = self.qsign_client.submit(uin, &cmd, id, &resp.body).await { tracing::error!("failed to submit sign callback, err: {err}") } }) .buffered(10) .collect() .await; } /// 向服务器发包 pub async fn send(&self, pkt: Packet) -> RQResult { tracing::trace!("sending pkt {}-{},", pkt.command_name, pkt.seq_id); let data = self.engine.read().await.transport.encode_packet(pkt); self.out_pkt_sender .send(data) .map_err(|_| RQError::Other("failed to send out_pkt".into())) } /// 向服务器发包并等待接收返回的包,15 秒后超时返回 `Err(RQError::Timeout)` #[async_recursion::async_recursion] pub async fn send_and_wait(&self, mut pkt: Packet) -> RQResult { let callbacks = self.sign_packet(&mut pkt).await; if let Err(ref err) = callbacks { tracing::error!("failed to sign packet, err: {err}"); } let callbacks = callbacks.unwrap_or_default().data.request_callback; let callback_future = self.process_sign_callback(callbacks); tracing::trace!("send_and_waitting pkt {}-{},", pkt.command_name, pkt.seq_id); let seq = pkt.seq_id; let expect = pkt.command_name.clone(); let data = self.engine.read().await.transport.encode_packet(pkt); let (sender, receiver) = oneshot::channel(); { let mut packet_promises = self.packet_promises.write().await; packet_promises.insert(seq, sender); } if self.out_pkt_sender.send(data).is_err() { let mut packet_promises = self.packet_promises.write().await; packet_promises.remove(&seq); return Err(RQError::Network); } let packet_future = tokio::time::timeout(std::time::Duration::from_secs(15), receiver); let (resp, _) = tokio::join!(packet_future, callback_future); match resp { Ok(p) => p.unwrap().check_command_name(&expect), Err(_) => { tracing::trace!("waiting pkt {}-{} timeout", expect, seq); self.packet_promises.write().await.remove(&seq); Err(RQError::Timeout) } } } /// 向服务器发送心跳包,并自动注册客户端 /// /// 该方法会阻塞当前协程,通常 spawn 使用 pub async fn do_heartbeat(&self) { self.heartbeat_enabled.store(true, Ordering::SeqCst); let mut times = 0; while self.online.load(Ordering::SeqCst) { sleep(Duration::from_secs(30)).await; if self.heartbeat().await.is_ok() { times += 1; if times >= 7 { if self.register_client().await.is_err() { break; } times = 0; } } } self.heartbeat_enabled.store(false, Ordering::SeqCst); } /// 生成 token pub async fn gen_token(&self) -> Token { self.engine.read().await.gen_token() } /// 从 token 恢复 pub async fn load_token(&self, token: Token) { self.engine.write().await.load_token(token) } pub async fn device(&self) -> Device { self.engine.read().await.transport.device.clone() } pub async fn version(&self) -> Version { self.engine.read().await.transport.version.clone() } pub async fn get_highway_session_key(&self) -> Vec { self.highway_session.read().await.session_key.to_vec() } /// 监听指定 command 数据包 pub async fn listen_command(&self, command: S) -> broadcast::Receiver { self.packet_handler .write() .await .cache_get_or_set_with(command.to_string(), || broadcast::channel(10).0) .subscribe() } } impl Drop for Client { fn drop(&mut self) { self.stop(NetworkStatus::Drop); } } #[derive(Copy, Clone, Debug)] #[repr(u8)] pub enum NetworkStatus { // 未启动 Unknown = 0, // 运行中 Running = 1, // 用户手动停止 Stop = 2, // 内存释放 Drop = 3, // 网络原因掉线 NetworkOffline = 4, // 其他客户端踢下线 KickedOffline = 5, // 服务端强制下线 MsfOffline = 6, } ================================================ FILE: ricq/src/client/net.rs ================================================ use std::net::SocketAddr; use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::Duration; use crate::client::event::{ClientDisconnect, DisconnectReason}; use async_trait::async_trait; use bytes::Bytes; use futures_util::{SinkExt, StreamExt}; use tokio::io::{self, AsyncRead, AsyncWrite}; use tokio::net::TcpStream; use tokio::sync::broadcast; use tokio_util::codec::LengthDelimitedCodec; use crate::client::tcp::tcp_connect_fastest; use crate::client::NetworkStatus; use crate::handler::QEvent; use super::Client; pub type OutPktSender = broadcast::Sender; #[async_trait] pub trait Connector { async fn connect(&self, client: &Client) -> io::Result; } pub struct DefaultConnector; #[async_trait] impl Connector for DefaultConnector { async fn connect(&self, client: &Client) -> io::Result { tcp_connect_fastest(client.get_address_list().await, Duration::from_secs(5)).await } } impl crate::Client { /// 获取服务器地址 pub async fn get_address_list(&self) -> Vec { const BUILD_IN: [([u8; 4], u16); 6] = [ ([42, 81, 172, 81], 80), ([114, 221, 148, 59], 14000), ([42, 81, 172, 147], 443), ([125, 94, 60, 146], 80), ([114, 221, 144, 215], 80), ([42, 81, 172, 22], 80), ]; let mut addrs: Vec<_> = BUILD_IN.into_iter().map(SocketAddr::from).collect(); if let Ok(res) = tokio::net::lookup_host(("msfwifi.3g.qq.com", 8080)).await { addrs.extend(res); } // TODO: src/client/processor/config_push_svc.rs addrs } /// 获取网络状态 pub fn get_status(&self) -> u8 { self.status.load(Ordering::Relaxed) } /// 开始处理流数据,阻塞当前 Task。该方法返回即为断线。 /// /// **Notice: 该方法仅开始处理包,需要手动登录并开始心跳包** pub async fn start(self: &Arc, stream: impl AsyncRead + AsyncWrite) { self.status .store(NetworkStatus::Running as u8, Ordering::Relaxed); self.net_loop(stream).await; // 阻塞到断开 self.disconnect(); self.online.store(false, Ordering::Relaxed); match self.status.compare_exchange( NetworkStatus::Running as u8, NetworkStatus::NetworkOffline as u8, Ordering::Relaxed, Ordering::Relaxed, ) { Ok(_) => { self.handler .handle(QEvent::ClientDisconnect(ClientDisconnect { client: Arc::clone(self), inner: DisconnectReason::Network, })) .await; } Err(status) => { self.handler .handle(QEvent::ClientDisconnect(ClientDisconnect { client: Arc::clone(self), inner: { let network = match status { 0 => NetworkStatus::Unknown, 1 => NetworkStatus::Running, 2 => NetworkStatus::Stop, 3 => NetworkStatus::Drop, 5 => NetworkStatus::KickedOffline, 6 => NetworkStatus::MsfOffline, _ => NetworkStatus::Unknown, }; DisconnectReason::Actively(network) }, })) .await; } } } pub fn stop(&self, status: NetworkStatus) { self.disconnect(); self.status.store(status as u8, Ordering::Relaxed); self.online.store(false, Ordering::Relaxed); } fn disconnect(&self) { // TODO dispatch disconnect event // don't unwrap (Err means there is no receiver.) self.disconnect_signal.send(()).ok(); } async fn net_loop(self: &Arc, stream: impl AsyncRead + AsyncWrite) { let (mut write_half, mut read_half) = LengthDelimitedCodec::builder() .length_field_length(4) .length_adjustment(-4) .new_framed(stream) .split(); // 外发包 Channel Receiver let mut rx = self.out_pkt_sender.subscribe(); let mut disconnect_signal = self.disconnect_signal.subscribe(); loop { tokio::select! { input = read_half.next() => { if let Some(Ok(mut input)) = input { if let Ok(pkt) = self.engine.read().await.transport.decode_packet(&mut input) { self.process_income_packet(pkt).await; } else { self.status.store(NetworkStatus::MsfOffline as u8, Ordering::Relaxed); break; } } else { break; } } output = rx.recv() => { if let Ok(output) = output && write_half.send(output).await.is_err() { break; } } _ = disconnect_signal.recv() => { break; } } } } } ================================================ FILE: ricq/src/client/processor/c2c/friend_msg.rs ================================================ use cached::Cached; use std::sync::Arc; use ricq_core::msg::MessageChain; use ricq_core::structs::{FriendAudio, FriendAudioMessage, FriendMessage}; use ricq_core::{pb, RQResult}; use crate::client::event::{FriendAudioMessageEvent, FriendMessageEvent}; use crate::handler::QEvent; use crate::Client; impl Client { pub(crate) async fn process_friend_message( self: &Arc, mut msg: pb::msg::Message, ) -> RQResult<()> { fn take_ptt(msg: &mut pb::msg::Message) -> Option { msg.body.as_mut()?.rich_text.as_mut()?.ptt.take() } if let Some(ptt) = take_ptt(&mut msg) { // TODO self friend audio self.handler .handle(QEvent::FriendAudioMessage(FriendAudioMessageEvent { client: self.clone(), inner: parse_friend_audio_message(msg, ptt)?, })) .await; return Ok(()); } let message = parse_friend_message(msg)?; if message.from_uin == self.uin().await { if let Some(tx) = self .receipt_waiters .lock() .await .cache_remove(&message.rands.first().cloned().unwrap_or_default()) { let _ = tx.send(message.seqs.first().cloned().unwrap_or_default()); return Ok(()); } } self.handler .handle(QEvent::FriendMessage(FriendMessageEvent { client: self.clone(), inner: message, })) .await; Ok(()) } } pub fn parse_friend_message(msg: pb::msg::Message) -> RQResult { let head = msg.head.unwrap(); Ok(FriendMessage { seqs: vec![head.msg_seq()], target: head.to_uin.unwrap(), time: head.msg_time.unwrap(), from_uin: head.from_uin.unwrap_or_default(), from_nick: head.from_nick.unwrap_or_default(), rands: vec![ if let Some(attr) = &msg.body.as_ref().unwrap().rich_text.as_ref().unwrap().attr { attr.random() } else { 0 }, ], elements: MessageChain::from(msg.body.unwrap().rich_text.unwrap().elems), // todo ptt_store }) } pub fn parse_friend_audio_message( msg: pb::msg::Message, ptt: pb::msg::Ptt, ) -> RQResult { let head = msg.head.unwrap(); Ok(FriendAudioMessage { seqs: vec![head.msg_seq()], target: head.to_uin.unwrap(), time: head.msg_time.unwrap(), from_uin: head.from_uin.unwrap_or_default(), from_nick: head.from_nick.unwrap_or_default(), rands: vec![ if let Some(attr) = &msg.body.as_ref().unwrap().rich_text.as_ref().unwrap().attr { attr.random() } else { 0 }, ], audio: FriendAudio(ptt), }) } ================================================ FILE: ricq/src/client/processor/c2c/friend_system_msg.rs ================================================ use crate::client::event::NewFriendRequestEvent; use crate::handler::QEvent; use crate::Client; use ricq_core::command::profile_service::FriendSystemMessages; use std::sync::Arc; impl Client { pub(crate) async fn process_friend_system_messages( self: &Arc, msgs: FriendSystemMessages, ) { for request in msgs.requests { self.handler .handle(QEvent::NewFriendRequest(NewFriendRequestEvent { client: self.clone(), inner: request, })) .await; } } } ================================================ FILE: ricq/src/client/processor/c2c/group_system_msg.rs ================================================ use std::sync::Arc; use ricq_core::command::profile_service::GroupSystemMessages; use crate::client::event::{JoinGroupRequestEvent, SelfInvitedEvent}; use crate::handler::QEvent; use crate::Client; impl Client { pub(crate) async fn process_group_system_messages(self: &Arc, msgs: GroupSystemMessages) { for request in msgs.self_invited.clone() { if self .self_invited_exists(request.msg_seq, request.msg_time) .await { continue; } self.handler .handle(QEvent::SelfInvited(SelfInvitedEvent { client: self.clone(), inner: request, })) .await; } for request in msgs.join_group_requests.clone() { if self .join_group_request_exists(request.msg_seq, request.msg_time) .await { continue; } self.handler .handle(QEvent::GroupRequest(JoinGroupRequestEvent { client: self.clone(), inner: request, })) .await; } let mut cache = self.group_sys_message_cache.write().await; *cache = msgs } async fn self_invited_exists(&self, msg_seq: i64, msg_time: i64) -> bool { if self.start_time > msg_time as i32 { return true; } self.group_sys_message_cache .read() .await .self_invited .iter() .any(|m| m.msg_seq == msg_seq) } async fn join_group_request_exists(&self, msg_seq: i64, msg_time: i64) -> bool { if self.start_time > msg_time as i32 { return true; } self.group_sys_message_cache .read() .await .join_group_requests .iter() .any(|m| m.msg_seq == msg_seq) } } ================================================ FILE: ricq/src/client/processor/c2c/mod.rs ================================================ pub mod friend_msg; pub mod friend_system_msg; pub mod group_system_msg; pub mod new_member; pub mod temp_session; ================================================ FILE: ricq/src/client/processor/c2c/new_member.rs ================================================ use std::sync::Arc; use ricq_core::common::group_uin2code; use ricq_core::structs::NewMember; use ricq_core::{pb, RQError, RQResult}; use crate::client::event::NewMemberEvent; use crate::handler::QEvent; use crate::Client; impl Client { pub(crate) async fn process_join_group( self: &Arc, msg: pb::msg::Message, ) -> RQResult<()> { let head = msg.head.ok_or(RQError::EmptyField("msg.head"))?; let group_code = group_uin2code(head.from_uin()); let member_uin = head.auth_uin(); self.handler .handle(QEvent::NewMember(NewMemberEvent { client: self.clone(), inner: NewMember { group_code, member_uin, }, })) .await; Ok(()) } } ================================================ FILE: ricq/src/client/processor/c2c/temp_session.rs ================================================ use std::sync::Arc; use ricq_core::msg::MessageChain; use ricq_core::structs::GroupTempMessage; use ricq_core::{pb, RQError, RQResult}; use crate::client::event::GroupTempMessageEvent; use crate::handler::QEvent; use crate::Client; impl Client { pub(crate) async fn process_temp_message( self: &Arc, msg: pb::msg::Message, ) -> RQResult<()> { let message = parse_temp_message(msg)?; self.handler .handle(QEvent::GroupTempMessage(GroupTempMessageEvent { client: self.clone(), inner: message, })) .await; Ok(()) } } pub fn parse_temp_message(msg: pb::msg::Message) -> RQResult { let head = msg.head.unwrap(); let tmp_head = head .c2c_tmp_msg_head .ok_or(RQError::EmptyField("c2c_tmp_msg_head"))?; Ok(GroupTempMessage { seqs: vec![head.msg_seq.unwrap_or_default()], rands: vec![ if let Some(attr) = &msg.body.as_ref().unwrap().rich_text.as_ref().unwrap().attr { attr.random() } else { 0 }, ], time: head.msg_time.unwrap(), from_uin: head.from_uin.unwrap_or_default(), from_nick: head.from_nick.unwrap_or_default(), elements: MessageChain::from(msg.body.unwrap().rich_text.unwrap().elems), // todo ptt_store group_code: tmp_head.group_code.unwrap_or_default(), }) } ================================================ FILE: ricq/src/client/processor/config_push_svc.rs ================================================ use std::time::Duration; use bytes::Bytes; use ricq_core::command::config_push_svc::ConfigPushBody; use ricq_core::command::config_push_svc::ConfigPushReq; use ricq_core::common::RQAddr; use crate::client::tcp::sort_addrs; use crate::client::Client; use crate::RQError; impl Client { pub(crate) async fn process_config_push_req( &self, config_push_req: ConfigPushReq, ) -> Result<(), RQError> { // send response to server let resp = config_push_req.resp; let response = self.engine.read().await.build_conf_push_resp_packet( resp.t, resp.pkt_seq, resp.jce_buf, ); self.send(response).await?; match config_push_req.body { ConfigPushBody::Unknown => {} ConfigPushBody::SsoServers { .. } => {} ConfigPushBody::FileStorageInfo { info: _, rsp_body } => { let mut session = self.highway_session.write().await; if let Some(rsp_body) = rsp_body { session.sig_session = Bytes::from(rsp_body.sig_session.unwrap_or_default()); session.session_key = Bytes::from(rsp_body.session_key.unwrap_or_default()); session.uin = self.uin().await; session.app_id = self.engine.read().await.transport.version.app_id as i32; for addr in rsp_body.addrs.into_iter() { let service_type = addr.service_type.unwrap_or_default(); if service_type == 10 { let addrs: Vec = addr .addrs .into_iter() .map(|addr| { RQAddr( addr.ip.unwrap_or_default(), addr.port.unwrap_or_default() as u16, ) }) .collect(); // 先写入,确保启动后可以快速使用 self.highway_addrs.write().await.extend(addrs); // 去重,排序 { let mut addrs = self.highway_addrs.read().await.clone(); addrs.dedup_by(|a, b| (a.0 == b.0 && a.1 == b.1)); let sorted_addrs = sort_addrs(addrs, Duration::from_secs(5)).await; let mut highway_addrs = self.highway_addrs.write().await; highway_addrs.clear(); highway_addrs.extend(sorted_addrs); } } else if service_type == 11 { // TODO } } } } } // TODO process Ok(()) } } ================================================ FILE: ricq/src/client/processor/message_svc.rs ================================================ use std::sync::Arc; use std::time::UNIX_EPOCH; use cached::Cached; use ricq_core::{jce, pb}; use crate::client::event::KickedOfflineEvent; use crate::client::{Client, NetworkStatus}; use crate::handler::QEvent; impl Client { pub(crate) async fn process_push_notify(self: &Arc, notify: jce::RequestPushNotify) { match notify.msg_type { 35 | 36 | 37 | 45 | 46 | 84 | 85 | 86 | 87 => { // pull group system msg(group request), then process match self.get_all_group_system_messages().await { Ok(msgs) => { self.process_group_system_messages(msgs).await; } Err(err) => { tracing::warn!("failed to get group system message {}", err); } } } 187..=191 => { // pull friend system msg(friend request), then process match self.get_friend_system_messages().await { Ok(msgs) => { self.process_friend_system_messages(msgs).await; } Err(err) => { tracing::warn!("failed to get friend system message {}", err); } } } _ => { // TODO tracing.warn!() } } // pull friend msg and other, then process let all_message = self.sync_all_message().await; match all_message { Ok(msgs) => { self.process_message_sync(msgs).await; } Err(err) => { tracing::warn!("failed to sync message {}", err); } } } pub(crate) async fn process_push_force_offline( self: &Arc, offline: jce::RequestPushForceOffline, ) { self.stop(NetworkStatus::KickedOffline); self.handler .handle(QEvent::KickedOffline(KickedOfflineEvent { client: self.clone(), inner: offline, })) .await; } pub(crate) async fn process_message_sync(self: &Arc, msgs: Vec) { for msg in msgs { let head = msg.head.clone().unwrap(); if self.msg_exists(&head).await { continue; } match msg.head.as_ref().unwrap().msg_type() { 9 | 10 | 31 | 79 | 97 | 120 | 132 | 133 | 166 | 167 => { if let Err(err) = self.process_friend_message(msg).await { tracing::error!("failed to process friend message {err}"); } } 33 => { if let Err(err) = self.process_join_group(msg).await { tracing::error!("failed to process join group {err}"); } } 140 | 141 => { if let Err(err) = self.process_temp_message(msg).await { tracing::error!("failed to process temp message {err}"); } } 208 => { // friend ptt_store } _ => tracing::warn!("unhandled sync message type"), } } } async fn msg_exists(&self, head: &pb::msg::MessageHead) -> bool { let now = UNIX_EPOCH.elapsed().unwrap().as_secs() as i32; let msg_time = head.msg_time.unwrap_or_default(); if now - msg_time > 60 || self.start_time > msg_time { return true; } let mut c2c_cache = self.c2c_cache.write().await; let key = ( head.from_uin(), head.to_uin(), head.msg_seq(), head.msg_uid(), ); if c2c_cache.cache_get(&key).is_some() { return true; } c2c_cache.cache_set(key, ()); if c2c_cache.cache_misses().unwrap_or_default() > 100 { c2c_cache.flush(); c2c_cache.cache_reset_metrics(); } false } } ================================================ FILE: ricq/src/client/processor/mod.rs ================================================ use std::sync::Arc; use bytes::Bytes; use ricq_core::protocol::packet::Packet; pub mod c2c; pub mod config_push_svc; pub mod message_svc; pub mod online_push; pub mod reg_prxy_svc; pub mod stat_svc; pub mod wtlogin; macro_rules! log_error { ($process: expr, $info: expr) => { if let Err(e) = $process { tracing::error!($info, e); } }; } impl super::Client { /// 接收到的 Packet 统一分发 pub async fn process_income_packet(self: &Arc, pkt: Packet) { tracing::trace!("received pkt: {}", &pkt.command_name); // response, send_and_wait 的包将会在此被截流 { if let Some(sender) = self.packet_promises.write().await.remove(&pkt.seq_id) { sender.send(pkt).unwrap(); return; } } tracing::trace!("pkt: {} passed packet_promises", &pkt.command_name); { if let Some(handler) = self.packet_handler.read().await.get(&pkt.command_name) { let _ = handler.send(pkt.clone()); } } let cli = self.clone(); tokio::spawn(async move { match pkt.command_name.as_ref() { "OnlinePush.PbPushGroupMsg" => { let p = cli .engine .read() .await .decode_group_message_packet(pkt.body); match p { Ok(part) => { log_error!( cli.process_group_message_part(part).await, "process_group_message_part error: {:?}" ) } Err(err) => { tracing::warn!("failed to decode [OnlinePush.PbPushGroupMsg]: {}", err); } } } "ConfigPushSvc.PushReq" => { let req = cli.engine.read().await.decode_push_req_packet(pkt.body); match req { Ok(req) => { log_error!( cli.process_config_push_req(req).await, "process_config_push_req error: {:?}" ) } Err(err) => { tracing::warn!("failed to decode [ConfigPushSvc.PushReq]: {}", err); } } } "RegPrxySvc.PushParam" => { let other_clients = cli.engine.read().await.decode_push_param_packet(&pkt.body); match other_clients { Ok(other_clients) => { log_error!( cli.process_push_param(other_clients).await, "process_push_param error: {:?}" ) } Err(err) => { tracing::warn!("failed to decode [RegPrxySvc.PushParam]: {}", err); } } } "MessageSvc.PushNotify" => { // c2c流程: // 1. Server 发送 PushNotify 到 Client, 表示有通知需要 Client 拉取 (不带具体内容) // 2. Client 根据 msg_type 发送请求拉取具体通知内容 // 类型:好友申请、群申请、私聊消息、其他? let resp = cli.engine.read().await.decode_svc_notify(pkt.body); match resp { Ok(notify) => { cli.process_push_notify(notify).await; } Err(err) => { tracing::warn!("failed to decode [MessageSvc.PushNotify]: {}", err); } } } "OnlinePush.ReqPush" => { let resp = cli .engine .read() .await .decode_online_push_req_packet(pkt.body); match resp { Ok(resp) => { log_error!( cli.delete_online_push( resp.uin, 0, Bytes::new(), pkt.seq_id as u16, resp.msg_infos.clone(), ) .await, "delete_online_push error: {:?}" ); cli.process_push_req(resp.msg_infos).await; } Err(err) => { tracing::warn!("failed to decode [OnlinePush.ReqPush]: {}", err); } } } "OnlinePush.PbPushTransMsg" => { let online_push_trans = cli .engine .read() .await .decode_online_push_trans_packet(pkt.body); match online_push_trans { Ok(online_push_trans) => { cli.process_push_trans(online_push_trans).await; } Err(err) => { tracing::warn!("failed to decode [OnlinePush.PbPushTransMsg]: {}", err); } } } "MessageSvc.PushForceOffline" => { let offline = cli.engine.read().await.decode_force_offline(pkt.body); match offline { Ok(offline) => { cli.process_push_force_offline(offline).await; } Err(err) => { tracing::warn!( "failed to decode [MessageSvc.PushForceOffline]: {}", err ); } } } "StatSvc.ReqMSFOffline" => { let offline = cli.engine.read().await.decode_msf_force_offline(pkt.body); match offline { Ok(offline) => { cli.process_msf_force_offline(offline).await; } Err(err) => { tracing::warn!("failed to decode [StatSvc.ReqMSFOffline]: {}", err); } } } "OnlinePush.PbC2CMsgSync" => { // 其他设备发送消息,同步 let push = cli.engine.read().await.decode_c2c_sync_packet(pkt.body); match push { Ok(push) => { log_error!( cli.process_c2c_sync(pkt.seq_id, push).await, "process_c2c_sync error: {:?}" ) } Err(err) => { tracing::warn!("failed to decode [OnlinePush.PbC2CMsgSync]: {}", err); } } } "OnlinePush.SidTicketExpired" => { log_error!( cli.process_sid_ticket_expired(pkt.seq_id).await, "process_sid_ticket_expired error: {:?}" ) } "RegPrxySvc.GetMsgV2" | "RegPrxySvc.PbGetMsg" | "RegPrxySvc.NoticeEnd" | "MessageSvc.PushReaded" => { tracing::trace!("ignore pkt: {}", &pkt.command_name); } _ => { tracing::debug!("unhandled pkt: {}", &pkt.command_name); } } }); } } ================================================ FILE: ricq/src/client/processor/online_push.rs ================================================ use std::sync::Arc; use std::time::Duration; use bytes::{Buf, Bytes}; use cached::Cached; use prost::Message; use ricq_core::command::online_push::GroupMessagePart; use ricq_core::command::online_push::{OnlinePushTrans, PushTransInfo}; use ricq_core::msg::MessageChain; use ricq_core::structs::{ DeleteFriend, FriendInfo, FriendMessageRecall, FriendPoke, GroupAudio, GroupAudioMessage, GroupLeave, GroupMessage, GroupMessageRecall, GroupMute, GroupNameUpdate, GroupPoke, }; use ricq_core::{jce, pb}; use crate::client::event::{ DeleteFriendEvent, FriendMessageRecallEvent, FriendPokeEvent, GroupAudioMessageEvent, GroupDisbandEvent, GroupLeaveEvent, GroupMessageEvent, GroupMessageRecallEvent, GroupMuteEvent, GroupNameUpdateEvent, GroupPokeEvent, MemberPermissionChangeEvent, NewFriendEvent, }; use crate::client::handler::QEvent; use crate::client::Client; use crate::RQResult; impl Client { pub(crate) async fn process_group_message_part( self: &Arc, group_message_part: GroupMessagePart, ) -> RQResult<()> { // receipt message if group_message_part.from_uin == self.uin().await { if let Some(tx) = self .receipt_waiters .lock() .await .cache_remove(&group_message_part.rand) { let _ = tx.send(group_message_part.seq); return Ok(()); } } if let Some(ptt) = group_message_part.ptt { self.handler .handle(QEvent::GroupAudioMessage(GroupAudioMessageEvent { client: self.clone(), inner: GroupAudioMessage { seqs: vec![group_message_part.seq], rands: vec![group_message_part.rand], group_code: group_message_part.group_code, group_name: group_message_part.group_name, group_card: group_message_part.group_card, from_uin: group_message_part.from_uin, time: group_message_part.time, audio: GroupAudio(ptt), }, })) .await; return Ok(()); } // merge parts let pkg_num = group_message_part.pkg_num; let group_msg = if pkg_num > 1 { let mut builder = self.group_message_builder.write().await; if builder.cache_misses().unwrap_or_default() > 100 { builder.flush(); builder.cache_reset_metrics(); } // muti-part let div_seq = group_message_part.div_seq; let parts = builder.cache_get_or_set_with(div_seq, Vec::new); parts.push(group_message_part); if parts.len() < pkg_num as usize { // wait for more parts None } else { Some(builder.cache_remove(&div_seq).unwrap_or_default()) } } else { // single-part Some(vec![group_message_part]) }; // handle message if let Some(group_msg) = group_msg { // message is finish self.handler .handle(QEvent::GroupMessage(GroupMessageEvent { client: self.clone(), inner: self.parse_group_message(group_msg).await?, })) .await; //todo } Ok(()) } pub(crate) async fn parse_group_message( &self, mut parts: Vec, ) -> RQResult { parts.sort_by(|a, b| a.pkg_index.cmp(&b.pkg_index)); let group_code = parts.first().map(|p| p.group_code).unwrap_or_default(); let group_name = parts .first_mut() .map(|p| std::mem::take(&mut p.group_name)) .unwrap_or_default(); let group_card = parts .first_mut() .map(|p| std::mem::take(&mut p.group_card)) .unwrap_or_default(); let from_uin = parts.first().map(|p| p.from_uin).unwrap_or_default(); let time = parts.first().map(|p| p.time).unwrap_or_default(); let mut seqs = Vec::with_capacity(parts.len()); let mut rands = Vec::with_capacity(parts.len()); let mut elements = Vec::with_capacity(6); // number by experience for p in parts { seqs.push(p.seq); rands.push(p.rand); elements.extend(p.elems.into_iter().filter_map(|e| e.elem)); } // dbg!(elements.len()); // most of message will be 4, complex message like share card is 5 Ok(GroupMessage { seqs, rands, group_code, group_name, group_card, from_uin, time, elements: MessageChain(elements), }) // TODO: extInfo // TODO: group_card_update // TODO: ptt_store } pub(crate) async fn process_push_req(self: &Arc, msg_infos: Vec) { for info in msg_infos { if self.push_req_exists(&info).await { continue; } match info.msg_type { 732 => { let mut r = info.v_msg; let group_code = r.get_u32() as i64; let i_type = r.get_u8(); r.get_u8(); match i_type { 0x0c => { let operator = r.get_u32() as i64; if operator == self.uin().await { continue; } r.advance(6); let target = r.get_u32() as i64; let duration = Duration::from_secs(r.get_u32() as u64); self.handler .handle(QEvent::GroupMute(GroupMuteEvent { client: self.clone(), inner: GroupMute { group_code, operator_uin: operator, target_uin: target, duration, }, })) .await; } 0x10 | 0x11 | 0x14 | 0x15 => { // group notify msg r.advance(1); let b = pb::notify::NotifyMsgBody::decode(&*r).unwrap(); if let Some(opt_msg_recall) = b.opt_msg_recall { let operator_uin = opt_msg_recall.uin; // use map iterator here will produce massive asm code for rm in opt_msg_recall.recalled_msg_list { if rm.msg_type == 2 { continue; } self.handler .handle(QEvent::GroupMessageRecall( GroupMessageRecallEvent { client: self.clone(), inner: GroupMessageRecall { msg_seq: rm.seq, group_code, operator_uin, author_uin: rm.author_uin, time: rm.time, }, }, )) .await; } } if let Some(t) = b.opt_general_gray_tip { let mut sender: i64 = 0; let mut receiver: i64 = 0; for templ in t.msg_templ_param { match &*templ.name { "uin_str1" => { sender = templ.value.parse().unwrap_or_default() } "uin_str2" => { receiver = templ.value.parse().unwrap_or_default() } _ => {} } } if sender != 0 { self.handler .handle(QEvent::GroupPoke(GroupPokeEvent { client: self.clone(), inner: GroupPoke { group_code, sender, receiver, }, })) .await; } } // TODO 一些没什么用的 event 暂时没写 } _ => {} } } 528 => { let mut v_msg = info.v_msg; let msg: jce::MsgType0x210 = jcers::from_buf(&mut v_msg).unwrap(); match msg.sub_msg_type { 0x8A | 0x8B => { let s8a = pb::Sub8A::decode(&*msg.v_protobuf).unwrap(); for m in s8a.msg_info { self.handler .handle(QEvent::FriendMessageRecall(FriendMessageRecallEvent { client: self.clone(), inner: FriendMessageRecall { msg_seq: m.msg_seq, friend_uin: m.from_uin, time: m.msg_time, }, })) .await; } } 0xB3 => { let msg_add_frd_notify = pb::SubB3::decode(&*msg.v_protobuf).unwrap(); if let Some(f) = msg_add_frd_notify.msg_add_frd_notify { self.handler .handle(QEvent::NewFriend(NewFriendEvent { client: self.clone(), inner: FriendInfo { uin: f.uin, nick: f.nick, ..Default::default() }, })) .await; } } 0xD4 => { let d4 = pb::SubD4::decode(&*msg.v_protobuf).unwrap(); self.handler .handle(QEvent::GroupLeave(GroupLeaveEvent { client: self.clone(), inner: GroupLeave { group_code: d4.uin, member_uin: self.uin().await, operator_uin: None, }, })) .await; } 0x122 | 0x123 => { let t = pb::notify::GeneralGrayTipInfo::decode(&*msg.v_protobuf).unwrap(); let mut sender: i64 = 0; let mut receiver: i64 = 0; for templ in t.msg_templ_param { if templ.name == "uin_str1" { sender = templ.value.parse().unwrap_or_default() } else if templ.name == "uin_str2" { receiver = templ.value.parse().unwrap_or_default() } } if sender != 0 { self.handler .handle(QEvent::FriendPoke(FriendPokeEvent { client: self.clone(), inner: FriendPoke { sender, receiver }, })) .await; } } 0x27 => { let s27 = pb::msgtype0x210::SubMsg0x27Body::decode(&*msg.v_protobuf).unwrap(); for mod_info in s27.mod_infos { if let Some(mod_group_profile) = mod_info.mod_group_profile { for profile_info in mod_group_profile.group_profile_infos { if profile_info.field.unwrap_or_default() != 1 { continue; } self.handler .handle(QEvent::GroupNameUpdate(GroupNameUpdateEvent { client: self.clone(), inner: GroupNameUpdate { group_code: mod_group_profile .group_code .unwrap_or_default() as i64, operator_uin: mod_group_profile .cmd_uin .unwrap_or_default() as i64, group_name: String::from_utf8_lossy( profile_info.value(), ) .into_owned(), }, })) .await; } } if let Some(del_friend) = mod_info.del_friend { for uin in del_friend.uins { self.handler .handle(QEvent::DeleteFriend(DeleteFriendEvent { client: self.clone(), inner: DeleteFriend { uin: uin as i64 }, })) .await; } } } } 0x44 => { // group sync // friend sync } _ => {} } } _ => {} } } } async fn push_req_exists(&self, info: &jce::PushMessageInfo) -> bool { let msg_time = info.msg_time as i32; // 可能是0,不过滤 if msg_time != 0 && self.start_time > msg_time { return true; } let mut push_req_cache = self.push_req_cache.write().await; let key = (info.msg_seq, info.msg_uid); if push_req_cache.cache_get(&key).is_some() { return true; } push_req_cache.cache_set(key, ()); if push_req_cache.cache_misses().unwrap_or_default() > 10 { push_req_cache.flush(); push_req_cache.cache_reset_metrics(); } false } pub(crate) async fn process_push_trans(self: &Arc, push_trans: OnlinePushTrans) { if self.push_trans_exists(&push_trans).await { return; } match push_trans.info { PushTransInfo::MemberLeave(leave) => { self.handler .handle(QEvent::GroupLeave(GroupLeaveEvent { client: self.clone(), inner: leave, })) .await; } PushTransInfo::MemberPermissionChange(change) => { self.handler .handle(QEvent::MemberPermissionChange( MemberPermissionChangeEvent { client: self.clone(), inner: change, }, )) .await; } PushTransInfo::GroupDisband(disband) => { self.handler .handle(QEvent::GroupDisband(GroupDisbandEvent { client: self.clone(), inner: disband, })) .await; } } } async fn push_trans_exists(&self, info: &OnlinePushTrans) -> bool { let msg_time = info.msg_time; if self.start_time > msg_time { return true; } let mut push_trans_cache = self.push_trans_cache.write().await; let key = (info.msg_seq, info.msg_uid); if push_trans_cache.cache_get(&key).is_some() { return true; } push_trans_cache.cache_set(key, ()); if push_trans_cache.cache_misses().unwrap_or_default() > 10 { push_trans_cache.flush(); push_trans_cache.cache_reset_metrics(); } false } pub(crate) async fn process_c2c_sync( self: &Arc, pkt_seq: i32, push: pb::msg::PbPushMsg, ) -> RQResult<()> { let req = self.engine.read().await.build_delete_online_push_packet( self.uin().await, push.svrip(), Bytes::from(push.push_token.unwrap_or_default()), pkt_seq as u16, vec![], ); let _ = self.send(req).await?; if let Some(msg) = push.msg { self.process_message_sync(vec![msg]).await; } Ok(()) } pub(crate) async fn process_sid_ticket_expired(self: &Arc, seq: i32) -> RQResult<()> { self.request_change_sig(Some(3554528)).await?; self.register_client().await?; self.send_sid_ticket_expired_response(seq).await?; Ok(()) } } ================================================ FILE: ricq/src/client/processor/reg_prxy_svc.rs ================================================ use crate::client::{Client, OtherClientInfo}; use crate::RQError; // use crate::client::income::decoder::online_push::GroupMessagePart; impl Client { pub(crate) async fn process_push_param( &self, other_clients: Vec, ) -> Result<(), RQError> { tracing::debug!("{:?}", other_clients); // TODO merge part and dispatch to handler Ok(()) } } ================================================ FILE: ricq/src/client/processor/stat_svc.rs ================================================ use std::sync::Arc; use ricq_core::jce; use crate::client::event::MSFOfflineEvent; use crate::client::{Client, NetworkStatus}; use crate::handler::QEvent; impl Client { // TODO 待测试 pub(crate) async fn process_msf_force_offline( self: &Arc, offline: jce::RequestMSFForceOffline, ) { self.send_msg_offline_rsp(offline.uin, offline.seq_no) .await .ok(); self.stop(NetworkStatus::MsfOffline); self.handler .handle(QEvent::MSFOffline(MSFOfflineEvent { client: self.clone(), inner: offline, })) .await; } } ================================================ FILE: ricq/src/client/processor/wtlogin.rs ================================================ use crate::handler::QEvent; use crate::Client; use ricq_core::command::wtlogin::*; impl Client { pub(crate) async fn process_login_response(&self, login_response: &LoginResponse) { if let LoginResponse::Success(ref success) = login_response { if let Some(info) = &success.account_info { let mut account_info = self.account_info.write().await; account_info.nickname = info.nick.clone(); account_info.age = info.age; account_info.gender = info.gender; } } self.engine .write() .await .process_login_response(login_response); self.handler.handle(QEvent::Login(self.uin().await)).await; } pub(crate) async fn process_trans_emp_response(&self, qrcode_state: &QRCodeState) { if let QRCodeState::Confirmed(resp) = qrcode_state { self.engine.write().await.process_qrcode_confirmed(resp); } } } ================================================ FILE: ricq/src/client/qimei.rs ================================================ use rand::{CryptoRng, RngCore}; use ricq_core::protocol::device::Device; use ricq_core::protocol::qimei::{Qimei, QimeiRequest, QimeiResponse}; use ricq_core::protocol::version::Version; use ricq_core::{RQError, RQResult}; pub async fn get_qimei( rng: &mut RNG, device: &Device, version: &Version, ) -> RQResult { let crypt_key = "0123456789abcdef".as_bytes(); let req = QimeiRequest::new(rng, device, version, crypt_key).unwrap(); let resp: QimeiResponse = reqwest::Client::new() .post("https://snowflake.qq.com/ola/android") .json(&req) .send() .await .map_err(|e| RQError::Other(e.to_string()))? .json() .await .map_err(|e| RQError::Other(e.to_string()))?; resp.to_payload(crypt_key).map_err(Into::into) } #[cfg(test)] mod tests { use super::*; use ricq_core::protocol::version::ANDROID_PHONE; #[tokio::test] async fn test_qimei() { use rand::SeedableRng; let mut rng = rand::rngs::StdRng::seed_from_u64(1239123240); let device = Device::random_with_rng(&mut rng); let version = ANDROID_PHONE; let resp = get_qimei(&mut rng, &device, &version).await.unwrap(); println!("{resp:?}") } } ================================================ FILE: ricq/src/client/tcp.rs ================================================ use std::net::SocketAddr; use std::time::Duration; use tokio::net::TcpStream; use tokio::task::JoinSet; pub async fn tcp_connect_timeout( addr: SocketAddr, timeout: Duration, ) -> tokio::io::Result { let conn = tokio::net::TcpStream::connect(addr); tokio::time::timeout(timeout, conn) .await .map_err(tokio::io::Error::from) .flatten() } /// Race the given address, call `join_set.join_next()` to get next fastest `(addr, conn)` pair. async fn race_addrs( addrs: Vec, timeout: Duration, ) -> JoinSet> { let mut join_set = JoinSet::new(); for addr in addrs { join_set.spawn(async move { let a = addr; tcp_connect_timeout(addr, timeout).await.map(|s| (a, s)) }); } join_set } pub async fn sort_addrs(addrs: Vec, timeout: Duration) -> Vec where SocketAddr: From, Addr: From, { let mut join_set = race_addrs(addrs.into_iter().map(Into::into).collect(), timeout).await; let mut ret = Vec::new(); while let Some(result) = join_set.join_next().await { if let Ok(Ok((addr, _))) = result { ret.push(addr.into()); } } ret } pub async fn tcp_connect_fastest( addrs: Vec, timeout: Duration, ) -> tokio::io::Result { let mut join_set = race_addrs(addrs, timeout).await; while let Some(result) = join_set.join_next().await { if let Ok(Ok((_, stream))) = result { return Ok(stream); } } Err(tokio::io::Error::new( tokio::io::ErrorKind::NotConnected, "NotConnected", )) } #[cfg(test)] mod tests { use std::net::Ipv4Addr; use super::*; #[tokio::test] async fn test_sort() { let addrs = vec![ SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), 80), SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), 8000), ]; let out = race_addrs(addrs.clone(), Duration::from_secs(10)).await; println!("{out:?}"); let str = tcp_connect_fastest(addrs, Duration::from_secs(10)).await; println!("{str:?}"); } } ================================================ FILE: ricq/src/config.rs ================================================ use std::fmt::Debug; use ricq_core::protocol::{ device::Device, version::Version, version::{get_version, Protocol}, }; #[derive(Debug)] pub struct Config { pub device: Device, pub version: Version, } impl Default for Config { fn default() -> Self { Self { device: Device::random(), version: get_version(Protocol::IPad), } } } impl Config { pub fn new(device: Device, version: Version) -> Self { Self { device, version } } } ================================================ FILE: ricq/src/ext/common.rs ================================================ use std::sync::atomic::Ordering; use std::sync::Arc; use crate::Client; /// 登录后必须执行的操作 pub async fn after_login(client: &Arc) { if let Err(err) = client.register_client().await { tracing::error!("failed to register client: {}", err) } start_heartbeat(client.clone()).await; if let Err(err) = client.refresh_status().await { tracing::error!("failed to refresh status: {}", err) } } /// 如果当前启动心跳,spawn 开始心跳 pub async fn start_heartbeat(client: Arc) { if !client.heartbeat_enabled.load(Ordering::Relaxed) { tokio::spawn(async move { client.do_heartbeat().await; }); } } ================================================ FILE: ricq/src/ext/image.rs ================================================ use std::future::Future; use std::pin::Pin; use ricq_core::command::img_store::GroupImageStoreResp; use ricq_core::command::long_conn::OffPicUpResp; use ricq_core::highway::BdhInput; use ricq_core::msg::elem::{FriendImage, GroupImage}; use ricq_core::{RQError, RQResult}; use crate::structs::ImageInfo; use crate::Client; pub async fn upload_group_image_ext( cli: &Client, group_code: i64, image_info: ImageInfo, f: F, ) -> RQResult where F: for<'a> FnOnce( &'a ImageInfo, ) -> Pin>> + Send + 'a>>, { let sign = cli.get_highway_session_key().await; let group_image = match cli.get_group_image_store(group_code, &image_info).await? { GroupImageStoreResp::Exist { file_id, addrs } => { image_info.into_group_image(file_id, addrs.first().cloned().unwrap_or_default(), sign) } GroupImageStoreResp::NotExist { file_id, upload_key, mut upload_addrs, } => { let data = f(&image_info).await?; let addr = upload_addrs .pop() .ok_or(RQError::EmptyField("upload_addrs"))?; cli.highway_upload_bdh( addr.into(), BdhInput { command_id: 2, ticket: upload_key, ext: vec![], encrypt: false, chunk_size: 256 * 1024, send_echo: true, }, &data, ) .await .map(|_| image_info.into_group_image(file_id, addr, sign))? } }; Ok(group_image) } pub async fn upload_friend_image_ext( cli: &Client, target: i64, image_info: ImageInfo, f: F, ) -> RQResult where F: for<'a> FnOnce( &'a ImageInfo, ) -> Pin>> + Send + 'a>>, { let friend_image = match cli.get_off_pic_store(target, &image_info).await? { OffPicUpResp::Exist { res_id, uuid } => image_info.into_friend_image(res_id, uuid), OffPicUpResp::UploadRequired { res_id, uuid, upload_key, mut upload_addrs, } => { let data = f(&image_info).await?; let addr = upload_addrs .pop() .ok_or(RQError::EmptyField("upload_addrs"))?; cli.highway_upload_bdh( addr.into(), BdhInput { command_id: 1, ticket: upload_key, ext: vec![], encrypt: false, chunk_size: 256 * 1024, send_echo: true, }, &data, ) .await .map(|_| image_info.into_friend_image(res_id, uuid))? } }; Ok(friend_image) } ================================================ FILE: ricq/src/ext/login.rs ================================================ use std::sync::Arc; use std::time::Duration; use ricq_core::command::wtlogin::{LoginResponse, QRCodeConfirmed, QRCodeState}; use ricq_core::{RQError, RQResult}; use crate::Client; /// 扫码登录:自动查询二维码状态,忽略中间结果,成功或失败返回 pub async fn auto_query_qrcode(client: &Arc, sig: &[u8]) -> RQResult<()> { loop { tokio::time::sleep(Duration::from_secs(1)).await; let qrcode_state = client.query_qrcode_result(sig).await?; match qrcode_state { QRCodeState::Timeout => return Err(RQError::Timeout), QRCodeState::Canceled => return Err(RQError::Other("canceled".into())), QRCodeState::Confirmed(QRCodeConfirmed { ref tmp_pwd, ref tmp_no_pic_sig, ref tgt_qr, .. }) => { let login_resp = client.qrcode_login(tmp_pwd, tmp_no_pic_sig, tgt_qr).await?; return match login_resp { LoginResponse::Success { .. } => Ok(()), LoginResponse::DeviceLockLogin { .. } => { match client.device_lock_login().await? { LoginResponse::Success { .. } => Ok(()), other => Err(RQError::Other(format!( "device_lock_login failed {other:?}" ))), } } other => Err(RQError::Other(format!("invalid login resp: {other:?}"))), }; } _ => { // do nothing } } tokio::time::sleep(Duration::from_secs(4)).await; } } ================================================ FILE: ricq/src/ext/mod.rs ================================================ pub mod common; pub mod image; pub mod login; pub mod reconnect; ================================================ FILE: ricq/src/ext/reconnect.rs ================================================ use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; use tokio::io::{AsyncRead, AsyncWrite}; use ricq_core::command::wtlogin::LoginResponse; use crate::client::net::Connector; use crate::client::NetworkStatus; use crate::ext::common::after_login; use crate::{Client, RQError, RQResult}; /// 自动重连,在掉线后使用,会阻塞到重连结束 pub async fn auto_reconnect( client: Arc, credential: Credential, interval: Duration, max: usize, connector: impl Connector, ) { let mut count = 0; loop { // 如果不是网络原因掉线,不重连(服务端强制下线/被踢下线/用户手动停止) if client.get_status() != (NetworkStatus::NetworkOffline as u8) { tracing::warn!( "client status: {}, auto_reconnect break", client.get_status() ); break; } client.stop(NetworkStatus::NetworkOffline); tracing::error!("client will reconnect after {} seconds", interval.as_secs()); tokio::time::sleep(interval).await; let stream = if let Ok(stream) = connector.connect(&client).await { count = 0; stream } else { count += 1; if count > max { tracing::error!("reconnect_count: {}, break!", count); break; } continue; }; let c = client.clone(); let handle = tokio::spawn(async move { c.start(stream).await }); tokio::task::yield_now().await; // 等一下,确保连上了 if let Err(err) = fast_login(&client, &credential).await { // token 可能过期了 tracing::error!("failed to fast_login: {}", err); client.stop(NetworkStatus::NetworkOffline); count += 1; if count > max { tracing::error!("reconnect_count: {}, break!", count); break; } continue; } tracing::info!("succeed to reconnect"); after_login(&client).await; handle.await.ok(); } } pub struct Password { pub uin: i64, pub password: String, } pub enum Credential { Token(ricq_core::Token), Password(Password), } /// 用于重连 #[async_trait] pub trait FastLogin { async fn fast_login(&self, client: &Arc) -> RQResult<()>; } #[async_trait] impl FastLogin for ricq_core::Token { async fn fast_login(&self, client: &Arc) -> RQResult<()> { match client.token_login(self.clone()).await? { LoginResponse::Success(_) => Ok(()), other => Err(RQError::Other(format!("failed to token_login, {other:?}"))), } } } #[async_trait] impl FastLogin for Password { async fn fast_login(&self, client: &Arc) -> RQResult<()> { let resp = client.password_login(self.uin, &self.password).await?; match resp { LoginResponse::Success { .. } => return Ok(()), LoginResponse::DeviceLockLogin { .. } => { return if let LoginResponse::Success { .. } = client.device_lock_login().await? { Ok(()) } else { Err(RQError::Other("failed to login".into())) }; } other => Err(RQError::Other(format!("failed to login, {other:?}"))), } } } /// 如果你非常确定登录过程中不会遇到验证码,可以用 fast_login pub async fn fast_login(client: &Arc, credential: &Credential) -> RQResult<()> { match credential { Credential::Token(token) => token.fast_login(client).await, Credential::Password(password) => password.fast_login(client).await, } } ================================================ FILE: ricq/src/lib.rs ================================================ #![feature(async_closure)] #![feature(let_chains)] #![feature(result_flattening)] pub mod client; mod config; pub mod ext; pub mod qsign; pub mod structs; pub use client::handler; pub use client::Client; pub use config::Config; pub use device::Device; pub use version::Protocol; pub use ricq_core::command::wtlogin::{ LoginDeviceLockLogin, LoginDeviceLocked, LoginNeedCaptcha, LoginResponse, LoginSuccess, LoginUnknownStatus, QRCodeConfirmed, QRCodeImageFetch, QRCodeState, }; pub use ricq_core::error::{RQError, RQResult}; use ricq_core::jce; pub use ricq_core::msg; pub use ricq_core::protocol::device; pub use ricq_core::protocol::version; ================================================ FILE: ricq/src/qsign.rs ================================================ use bytes::{BufMut, BytesMut}; use ricq_core::binary::packet_writer::WriteLV; use ricq_core::hex::encode_hex; use serde::{Deserialize, Serialize}; use std::time::Duration; pub struct QSignClient { url: String, key: String, client: reqwest::Client, timeout: Duration, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct QSignResponse { pub code: i64, pub msg: String, pub data: T, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SignData { pub token: String, pub extra: String, pub sign: String, #[serde(rename = "o3did")] pub o3_did: String, pub request_callback: Vec, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RequestCallback { pub cmd: String, pub body: String, pub callback_id: i64, } impl QSignClient { pub fn new(mut url: String, key: String, timeout: Duration) -> reqwest::Result { let client = reqwest::ClientBuilder::new().build()?; if url.ends_with('/') { url.pop(); } Ok(QSignClient { url, key, client, timeout, }) } pub async fn register( &self, uin: i64, qimei36: &str, android_id: &str, guid: &[u8], ) -> reqwest::Result> { let url = format!("{}/register", self.url); self.client .get(url) .query(&[ ("uin", uin.to_string().as_str()), ("android_id", android_id), ("guid", encode_hex(guid).as_str()), ("qimei36", qimei36), ("key", self.key.as_str()), ]) .timeout(self.timeout) .send() .await? .json() .await } pub async fn custom_energy( &self, uin: i64, data: &str, salt: &[u8], guid: &[u8], android_id: &str, ) -> reqwest::Result> { let url = format!("{}/custom_energy", self.url); self.client .get(url) .query(&[ ("uin", uin.to_string().as_str()), ("salt", encode_hex(salt).as_str()), ("data", data), ("android_id", android_id), ("guid", encode_hex(guid).as_str()), ]) .timeout(self.timeout) .send() .await? .json() .await } pub fn calc_salt(uin: u64, guid: &[u8], sdk_version: &str, sub_cmd: u32) -> Vec { let mut buf = BytesMut::new(); match sub_cmd { 2 | 7 => buf.put_u64(uin), 9 | 0xa | 0xf => buf.put_u32(0), _ => panic!("sub_cmd not supported"), } buf.write_short_lv(guid); buf.write_short_lv(sdk_version.as_bytes()); buf.put_u32(sub_cmd); match sub_cmd { 9 | 0xa | 0xf => buf.put_u32(0), _ => {} } buf.to_vec() } pub async fn energy( &self, uin: i64, data: &str, version: &str, guid: &[u8], android_id: &str, ) -> reqwest::Result> { let url = format!("{}/energy", self.url); self.client .get(url) .query(&[ ("version", version), ("uin", uin.to_string().as_str()), ("data", data), ("guid", encode_hex(guid).as_str()), ("android_id", android_id), ]) .timeout(self.timeout) .send() .await? .json() .await } // TODO test sign #[allow(clippy::too_many_arguments)] pub async fn sign( &self, uin: i64, qua: &str, cmd: &str, seq: i32, buffer: &[u8], qimei36: &str, android_id: &str, guid: &[u8], ) -> reqwest::Result> { let url = format!("{}/sign", self.url); self.client .post(url) .form(&[ ("uin", uin.to_string().as_str()), ("qua", qua), ("cmd", cmd), ("seq", seq.to_string().as_str()), ("buffer", encode_hex(buffer).as_str()), ("qimei36", qimei36), ("android_id", android_id), ("guid", encode_hex(guid).as_str()), ]) .timeout(self.timeout) .send() .await? .json() .await } pub async fn submit( &self, uin: i64, cmd: &str, callback_id: i64, buffer: &[u8], ) -> reqwest::Result<()> { let url = format!("{}/submit", self.url); self.client .get(url) .query(&[ ("uin", uin.to_string().as_str()), ("cmd", cmd), ("callback_id", callback_id.to_string().as_str()), ("buffer", encode_hex(buffer).as_str()), ]) .timeout(self.timeout) .send() .await?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use crate::client::qimei::get_qimei; use rand::SeedableRng; use ricq_core::protocol::{device::Device, sig::Sig, version::ANDROID_PHONE}; async fn get_device() -> Device { let mut rng = rand::prelude::StdRng::seed_from_u64(10); let version = ANDROID_PHONE; let mut device = Device::random_with_rng(&mut rng); let qimei = get_qimei(&mut rng, &device, &version).await.unwrap(); device.set_qimei(qimei); device } #[tokio::test] async fn test_custom_energy() { let uin = 12345; let device = get_device().await; let sig = Sig::new(&device); let cli = QSignClient::new( String::from("http://localhost:8080"), String::from("114514"), Duration::from_secs(60), ) .unwrap(); let resp = cli .custom_energy( uin, "810_9", &[ 0, 0, 0, 0, 0, 16, 64, 70, 49, 11, 87, 2, 23, 195, 124, 180, 140, 194, 140, 180, 113, 103, 0, 10, 54, 46, 48, 46, 48, 46, 50, 53, 52, 54, 0, 0, 0, 9, 0, 0, 0, 0, ], &sig.guid, &device.android_id, ) .await; println!("{resp:?}"); } #[tokio::test] async fn test_energy() { let uin = 12345; let device = get_device().await; let sig = Sig::new(&device); let cli = QSignClient::new( String::from("http://localhost:8080"), String::from("114514"), Duration::from_secs(60), ) .unwrap(); let resp = cli .energy(uin, "810_9", "6.0.0.2546", &sig.guid, &device.android_id) .await; println!("{resp:?}"); } } ================================================ FILE: ricq/src/structs/image_info.rs ================================================ use serde::{Deserialize, Serialize}; use ricq_core::common::RQAddr; use ricq_core::hex::encode_hex; use ricq_core::msg::elem::{FriendImage, GroupImage}; use ricq_core::RQResult; // 仅用于上传图片,一些临时变量,太多了放一起 #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ImageInfo { pub md5: Vec, pub width: u32, pub height: u32, pub image_type: i32, pub size: u32, pub filename: String, } impl ImageInfo { pub fn try_new(data: &[u8]) -> RQResult { let md5 = md5::compute(data).to_vec(); #[cfg(feature = "image-detail")] let (width, height, format, ext_name) = { let img_reader = image::io::Reader::new(std::io::Cursor::new(data)) .with_guessed_format() .map_err(ricq_core::RQError::IO)?; let format = img_reader.format().unwrap_or(image::ImageFormat::Png); let (width, height) = img_reader.into_dimensions().unwrap_or((720, 480)); let ext_name = format.extensions_str().first().expect("image_format error"); (width, height, format, ext_name) }; #[cfg(not(feature = "image-detail"))] let (width, height, ext_name) = (1280, 720, "png"); Ok(ImageInfo { filename: format!("{}.{}", encode_hex(&md5), ext_name), md5, width, height, #[cfg(feature = "image-detail")] image_type: match format { image::ImageFormat::Jpeg => 1000, image::ImageFormat::Png => 1001, image::ImageFormat::WebP => 1002, image::ImageFormat::Bmp => 1005, image::ImageFormat::Gif => 2000, _ => 1000, }, #[cfg(not(feature = "image-detail"))] image_type: 1001, // PNG size: data.len() as u32, }) } // download path: "/{to_uin}-{unknown?}-{md5}" pub fn into_friend_image(self, res_id: String, download_path: String) -> FriendImage { FriendImage { res_id, file_path: format!("{}.png", encode_hex(&self.md5)), md5: self.md5, size: self.size, width: self.width, height: self.height, image_type: self.image_type, download_path, ..Default::default() } } pub fn into_group_image(self, file_id: u64, addr: RQAddr, signature: Vec) -> GroupImage { GroupImage { file_path: format!("{}.png", encode_hex(&self.md5)), file_id: file_id as i64, size: self.size, width: self.width, height: self.height, md5: self.md5, image_type: self.image_type, signature, server_ip: addr.0, server_port: addr.1 as u32, orig_url: None, } } } ================================================ FILE: ricq/src/structs/mod.rs ================================================ pub use image_info::*; pub use ricq_core::structs::*; mod image_info; ================================================ FILE: ricq-core/Cargo.toml ================================================ [package] name = "ricq-core" version = "0.1.20" edition = "2021" description = "Packet encoders and decoders for ricq" license = "MPL-2.0" homepage = "https://github.com/lz1998/ricq" repository = "https://github.com/lz1998/ricq" keywords = ["qq", "protocol", "android", "mirai"] [dependencies] byteorder.workspace = true bytes.workspace = true derivative.workspace = true flate2.workspace = true generic-array.workspace = true jcers = { workspace = true, features = ["derive"] } md5.workspace = true p256 = { workspace = true, features = ["ecdh"], default-features = false } prost = { workspace = true, features = ["std"], default-features = false } prost-types.workspace = true rand.workspace = true serde = { workspace = true, features = ["derive"] } thiserror.workspace = true crypto-common.workspace = true serde_json.workspace = true block-padding = { workspace = true, features = ["std"] } spki.workspace = true base64.workspace = true cbc = { workspace = true, features = ["alloc"] } aes.workspace = true rsa.workspace = true x509-cert.workspace = true chrono.workspace = true sha2.workspace = true [build-dependencies] prost-build = "0.9" ================================================ FILE: ricq-core/build.rs ================================================ use std::path::{Path, PathBuf}; fn recurse_dir(v: &mut Vec, dir: impl AsRef) { for entry in std::fs::read_dir(&dir).unwrap_or_else(|_| panic!("Unable to read dir: {:?}", dir.as_ref())) { let path = entry.expect("Unable to get direntry").path(); if path.is_dir() { recurse_dir(v, path); } else if let Some(true) = path.extension().map(|v| v == "proto") { v.push(path); } } } fn main() { let mut files = Vec::new(); recurse_dir(&mut files, "src/pb"); prost_build::compile_protos(&files, &["src/pb"]).unwrap(); } ================================================ FILE: ricq-core/src/binary/binary_reader.rs ================================================ use std::collections::HashMap; use bytes::{Buf, Bytes}; pub trait BinaryReader { fn read_string(&mut self) -> String; fn read_string_short(&mut self) -> String; fn read_bytes_short(&mut self) -> Bytes; fn read_tlv_map(&mut self, tag_size: usize) -> HashMap; fn read_string_limit(&mut self, limit: usize) -> String; } impl BinaryReader for B where B: Buf, { fn read_string(&mut self) -> String { let len = self.get_i32() as usize - 4; String::from_utf8_lossy(&self.copy_to_bytes(len)).into_owned() } fn read_string_short(&mut self) -> String { let len = self.get_u16() as usize; String::from_utf8_lossy(&self.copy_to_bytes(len)).into_owned() } fn read_bytes_short(&mut self) -> Bytes { let len = self.get_u16() as usize; self.copy_to_bytes(len) } fn read_tlv_map(&mut self, tag_size: usize) -> HashMap { let mut m = HashMap::new(); loop { if self.remaining() < tag_size { return m; } let mut k = 0; if tag_size == 1 { k = self.get_u8() as u16; } else if tag_size == 2 { k = self.get_u16(); } else if tag_size == 4 { k = self.get_i32() as u16; } if k == 255 { return m; } if self.remaining() < 2 { return m; } let len = self.get_u16() as usize; if self.remaining() < len { return m; } m.insert(k, self.copy_to_bytes(len)); } } fn read_string_limit(&mut self, limit: usize) -> String { String::from_utf8_lossy(&self.copy_to_bytes(limit)).into_owned() } } ================================================ FILE: ricq-core/src/binary/binary_writer.rs ================================================ use crate::crypto::qqtea_encrypt; use crate::hex::decode_hex; use bytes::{Buf, BufMut, BytesMut}; pub trait BinaryWriter { fn write_bytes_short(&mut self, data: &[u8]); fn encrypt_and_write(&mut self, key: &[u8], data: &[u8]); fn write_hex(&mut self, h: &str); fn write_int_lv_packet(&mut self, offset: usize, data: &[u8]); fn write_string(&mut self, v: &str); fn write_uni_packet( &mut self, command_name: &str, session_id: &[u8], extra_data: &[u8], body: &[u8], ); fn write_tlv_limited_size(&mut self, data: &[u8], limit: isize); } impl BinaryWriter for B where B: BufMut, { fn write_bytes_short(&mut self, data: &[u8]) { self.put_u16(data.len() as u16); self.put_slice(data.chunk()) } fn encrypt_and_write(&mut self, key: &[u8], data: &[u8]) { let ed = qqtea_encrypt(data, key); self.put_slice(&ed) } fn write_hex(&mut self, h: &str) { let b = decode_hex(h).expect("write_hex failed"); self.put_slice(&b); } fn write_int_lv_packet(&mut self, offset: usize, data: &[u8]) { self.put_u32((data.len() + offset) as u32); self.put_slice(data); } fn write_string(&mut self, v: &str) { let payload = v.as_bytes(); self.put_u32((payload.len() + 4) as u32); self.put_slice(payload) } fn write_uni_packet( &mut self, command_name: &str, session_id: &[u8], extra_data: &[u8], body: &[u8], ) { let mut w1 = BytesMut::new(); { w1.write_string(command_name); w1.put_u32(8); w1.put_slice(session_id); if extra_data.is_empty() { w1.put_u32(0x04) } else { w1.put_u32((extra_data.len() + 4) as u32); w1.put_slice(extra_data); } } self.put_u32((w1.len() + 4) as u32); self.put_slice(&w1); self.put_u32((body.len() + 4) as u32); self.put_slice(body); } fn write_tlv_limited_size(&mut self, data: &[u8], limit: isize) { if data.len() <= limit as usize { self.write_bytes_short(data); return; } self.write_bytes_short(&data[..(limit as usize)]) } } ================================================ FILE: ricq-core/src/binary/mod.rs ================================================ mod binary_reader; mod binary_writer; pub mod packet_writer; pub use binary_reader::BinaryReader; pub use binary_writer::BinaryWriter; ================================================ FILE: ricq-core/src/binary/packet_writer.rs ================================================ use bytes::*; use std::marker::PhantomData; pub trait PacketWriter { fn write(self, buf: &mut B); } pub trait PacketAppender: PacketWriter + Sized { fn append>(self, w: W) -> impl PacketWriter { |buf: &mut B| { self.write(buf); w.write(buf); } } } impl PacketWriter for F where B: BufMut, F: FnOnce(&mut B), { fn write(self, buf: &mut B) { self(buf) } } impl PacketWriter for &[u8] where B: BufMut, { fn write(self, buf: &mut B) { buf.put_slice(self); } } pub enum Either { Left(L), Right(R), } impl PacketWriter for Either where B: BufMut, L: PacketWriter, R: PacketWriter, { fn write(self, buf: &mut B) { match self { Either::Left(l) => l.write(buf), Either::Right(r) => r.write(buf), } } } pub struct CounterWriter where B: BufMut, W: PacketWriter, { pub count: usize, pub writer: W, pub _mark: PhantomData, } impl PacketWriter for CounterWriter where B: BufMut, T: PacketWriter, { fn write(self, buf: &mut B) { self.writer.write(buf) } } impl PacketAppender for CounterWriter where B: BufMut, T: PacketWriter, { fn append(self, w: W) -> CounterWriter> where W: PacketWriter, { CounterWriter { count: self.count + 1, writer: |buf: &mut B| { self.writer.write(buf); w.write(buf); }, _mark: PhantomData, } } } impl Default for CounterWriter { fn default() -> Self { CounterWriter { count: 0, writer: |_| {}, _mark: PhantomData, } } } impl CounterWriter where B: BufMut, T: PacketWriter, { pub fn append_option(self, w: Option) -> CounterWriter> where W: PacketWriter, { CounterWriter { count: self.count + if w.is_some() { 1 } else { 0 }, writer: |buf: &mut B| { self.writer.write(buf); if let Some(w) = w { w.write(buf) } }, _mark: PhantomData, } } } // write length-value pub trait WriteLV: BufMut { fn write_short_lv(&mut self, w: W) where W: PacketWriter, Self: Sized; } macro_rules! impl_write_lv { () => { fn write_short_lv(&mut self, w: W) where W: PacketWriter, Self: Sized, { let len_start = self.len(); self.put_u16(0); let body_start = self.len(); w.write(self); let body_len = (self.len() - body_start) as u16; (&mut self[len_start..len_start + 2]).put_u16(body_len); } }; } impl WriteLV for Vec { impl_write_lv!(); } impl WriteLV for BytesMut { impl_write_lv!(); } ================================================ FILE: ricq-core/src/command/common.rs ================================================ use bytes::{BufMut, Bytes, BytesMut}; use prost::Message; use crate::protocol::oicq; use crate::protocol::packet::*; use crate::Engine; impl Engine { pub fn build_oicq_request_packet(&self, uin: i64, command_id: u16, body: &[u8]) -> Bytes { let req = oicq::Message { uin: uin as u32, command: command_id, body: Bytes::from(body.to_vec()), encryption_method: oicq::EncryptionMethod::ECDH, }; self.transport.oicq_codec.encode(req) } pub fn uni_packet_with_seq(&self, seq: i32, command: &str, body: Bytes) -> Packet { Packet { packet_type: PacketType::Simple, encrypt_type: EncryptType::D2Key, seq_id: seq, body, command_name: command.to_owned(), uin: self.uin(), ..Default::default() } } pub fn uni_packet(&self, command: &str, body: Bytes) -> Packet { let seq = self.next_seq(); self.uni_packet_with_seq(seq as i32, command, body) } } pub fn pack_uni_request_data(data: &[u8]) -> Bytes { let mut r = BytesMut::new(); r.put_slice(&[0x0A]); r.put_slice(data); r.put_slice(&[0x0B]); Bytes::from(r) } pub trait PbToBytes { fn to_bytes(&self) -> Bytes; } impl PbToBytes for B { fn to_bytes(&self) -> Bytes { let mut buf = BytesMut::new(); prost::Message::encode(self, &mut buf).expect("prost encode failed"); buf.freeze() } } ================================================ FILE: ricq-core/src/command/config_push_svc/builder.rs ================================================ use std::collections::HashMap; use bytes::Bytes; use jcers::JcePut; use crate::command::common::pack_uni_request_data; use crate::jce; use crate::protocol::packet::Packet; impl super::super::super::Engine { // ConfigPushSvc.PushResp pub fn build_conf_push_resp_packet(&self, t: i32, pkt_seq: i64, jce_buf: Bytes) -> Packet { let mut req = jcers::JceMut::new(); req.put_i32(t, 1); req.put_i64(pkt_seq, 2); req.put_bytes(jce_buf, 3); let buf = jce::RequestDataVersion3 { map: HashMap::from([("PushResp".to_string(), pack_uni_request_data(&req.freeze()))]), }; let pkt = jce::RequestPacket { i_version: 3, s_servant_name: "QQService.ConfigPushSvc.MainServant".to_string(), s_func_name: "PushResp".to_string(), s_buffer: buf.freeze(), context: Default::default(), status: Default::default(), ..Default::default() }; self.uni_packet("ConfigPushSvc.PushResp", pkt.freeze()) } } ================================================ FILE: ricq-core/src/command/config_push_svc/decoder.rs ================================================ use bytes::Bytes; use crate::command::config_push_svc::*; use crate::{jce, pb, RQError, RQResult}; use prost::Message; // TODO 还没测试 impl super::super::super::Engine { // ConfigPushSvc.PushReq pub fn decode_push_req_packet(&self, mut payload: Bytes) -> RQResult { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut request.s_buffer)?; let mut a = data .map .remove("PushReq") .ok_or_else(|| RQError::Decode("missing PushReq".into()))?; let mut b = a .remove("ConfigPush.PushReq") .ok_or_else(|| RQError::Decode("missing ConfigPush.PushReq".into()))?; let _ = b.split_to(1); let mut r = jcers::Jce::new(&mut b); let t: i32 = r.get_by_tag(1)?; let mut jce_buf: Bytes = r.get_by_tag(2)?; let seq: i64 = r.get_by_tag(3)?; let mut body = ConfigPushBody::Unknown; if !jce_buf.is_empty() { body = match t { 1 => { let mut sso_pkt = jcers::Jce::new(&mut jce_buf); let servers: Vec = sso_pkt.get_by_tag(1)?; ConfigPushBody::SsoServers { servers } } 2 => { let mut info: jce::FileStoragePushFSSvcList = jcers::from_buf(&mut jce_buf)?; let rsp_body = match pb::cmd0x6ff::C501RspBody::decode( &mut info.big_data_channel.pb_buf, ) { Ok(c501_rsp_body) => c501_rsp_body.rsp_body, _ => None, }; ConfigPushBody::FileStorageInfo { info, rsp_body } } _ => ConfigPushBody::Unknown, } } Ok(ConfigPushReq { resp: ConfigPushResp { t, pkt_seq: seq, jce_buf, }, body, }) } } ================================================ FILE: ricq-core/src/command/config_push_svc/mod.rs ================================================ #![allow(clippy::large_enum_variant)] use bytes::Bytes; use crate::{jce, pb}; pub mod builder; pub mod decoder; #[derive(Default, Debug)] pub struct ConfigPushReq { pub resp: ConfigPushResp, pub body: ConfigPushBody, } #[derive(Debug, derivative::Derivative)] #[derivative(Default)] pub enum ConfigPushBody { #[derivative(Default)] Unknown, SsoServers { servers: Vec, }, FileStorageInfo { info: jce::FileStoragePushFSSvcList, rsp_body: Option, }, } #[derive(Default, Debug)] pub struct ConfigPushResp { pub t: i32, pub pkt_seq: i64, pub jce_buf: Bytes, } ================================================ FILE: ricq-core/src/command/friendlist/builder.rs ================================================ use std::collections::HashMap; use bytes::{BufMut, Bytes, BytesMut}; use jcers::JcePut; use crate::command::common::pack_uni_request_data; use crate::common::group_code2uin; use crate::protocol::packet::*; use crate::{jce, pb}; impl super::super::super::Engine { // friendlist.getFriendGroupList pub fn build_friend_group_list_request_packet( &self, friend_start_index: i16, friend_list_count: i16, group_start_index: i16, group_list_count: i16, ) -> Packet { let mut d50 = BytesMut::new(); prost::Message::encode( &pb::D50ReqBody { appid: 1002, req_music_switch: 1, req_mutualmark_alienation: 1, req_ksing_switch: 1, req_mutualmark_lbsshare: 1, ..Default::default() }, &mut d50, ) .expect("failed to encode pb"); let req = jce::FriendListRequest { reqtype: 3, if_reflush: if friend_start_index <= 0 { 0 } else { 1 }, uin: self.uin(), start_index: friend_start_index, friend_count: friend_list_count, group_id: 0, if_get_group_info: 1, group_start_index: group_start_index as u8, group_count: group_list_count as u8, if_get_msf_group: 0, if_show_term_type: 1, version: 27, uin_list: vec![], app_type: 0, if_get_dov_id: 0, if_get_both_flag: 0, d50: Bytes::from(d50), d6b: Bytes::new(), sns_type_list: vec![13580, 13581, 13582], }; let buf = jce::RequestDataVersion3 { map: HashMap::from([("FL".to_string(), pack_uni_request_data(&req.freeze()))]), }; let pkt = jce::RequestPacket { i_version: 3, c_packet_type: 0x003, i_request_id: 1921334514, s_servant_name: "mqq.IMService.FriendListServiceServantObj".to_string(), s_func_name: "GetFriendListReq".to_string(), s_buffer: buf.freeze(), context: Default::default(), status: Default::default(), ..Default::default() }; self.uni_packet("friendlist.getFriendGroupList", pkt.freeze()) } // friendlist.GetTroopListReqV2 pub fn build_group_list_request_packet(&self, vec_cookie: &[u8]) -> Packet { let req = jce::TroopListRequest { uin: self.uin(), get_msf_msg_flag: 1, cookies: Bytes::from(vec_cookie.to_vec()), group_info: vec![], group_flag_ext: 1, version: 7, company_id: 0, version_num: 1, get_long_group_name: 1, }; let buf = jce::RequestDataVersion3 { map: HashMap::from([( "GetTroopListReqV2Simplify".to_string(), pack_uni_request_data(&req.freeze()), )]), }; let pkt = jce::RequestPacket { i_version: 3, c_packet_type: 0x00, i_message_type: 0, i_request_id: self.next_packet_seq(), s_servant_name: "mqq.IMService.FriendListServiceServantObj".to_string(), s_func_name: "GetTroopListReqV2Simplify".to_string(), s_buffer: buf.freeze(), context: Default::default(), status: Default::default(), ..Default::default() }; self.uni_packet("friendlist.GetTroopListReqV2", pkt.freeze()) } // friendlist.GetTroopMemberListReq pub fn build_group_member_list_request_packet(&self, group_code: i64, next_uin: i64) -> Packet { let payload = jce::TroopMemberListRequest { uin: self.uin(), group_code, next_uin, group_uin: group_code2uin(group_code), version: 2, ..Default::default() }; let mut b = BytesMut::new(); b.put_slice(&[0x0A]); b.put_slice(&payload.freeze()); b.put_slice(&[0x0B]); let buf = jce::RequestDataVersion3 { map: HashMap::from([("GTML".to_string(), b.into())]), }; let pkt = jce::RequestPacket { i_version: 3, i_request_id: self.next_packet_seq(), s_servant_name: "mqq.IMService.FriendListServiceServantObj".to_string(), s_func_name: "GetTroopMemberListReq".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("friendlist.GetTroopMemberListReq", pkt.freeze()) } // friendlist.ModifyGroupCardReq pub fn build_edit_group_tag_packet( &self, group_code: i64, member_uin: i64, new_tag: String, ) -> Packet { let payload = jce::ModifyGroupCardRequest { group_code, uin_info: vec![jce::UinInfo { uin: member_uin, flag: 31, name: new_tag, ..Default::default() }], ..Default::default() }; let buf = jce::RequestDataVersion3 { map: HashMap::from([( "MGCREQ".to_string(), pack_uni_request_data(&payload.freeze()), )]), }; let pkt = jce::RequestPacket { i_version: 3, i_request_id: self.next_packet_seq(), s_servant_name: "mqq.IMService.FriendListServiceServantObj".to_string(), s_func_name: "ModifyGroupCardReq".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("friendlist.ModifyGroupCardReq", pkt.freeze()) } // friendlist.DelFriend pub fn build_delete_friend_packet(&self, del_uin: i64) -> Packet { let payload = jce::DelFriendReq { uin: self.uin(), del_uin, del_type: 2, version: 1, }; let buf = jce::RequestDataVersion3 { map: HashMap::from([("DF".to_string(), pack_uni_request_data(&payload.freeze()))]), }; let pkt = jce::RequestPacket { i_version: 3, i_request_id: self.next_packet_seq(), s_servant_name: "mqq.IMService.FriendListServiceServantObj".to_string(), s_func_name: "DelFriendReq".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("friendlist.delFriend", pkt.freeze()) } /// 好友分组操作 fn build_friend_list_set_group_req_packet(&self, req_type: i32, body: Bytes) -> Packet { let payload = jce::FriendListSetGroupReq { req_type, uin: self.uin(), body, }; let buf = jce::RequestDataVersion3 { map: HashMap::from([( "SetGroupReq".to_string(), pack_uni_request_data(&payload.freeze()), )]), }; let pkt = jce::RequestPacket { i_version: 3, i_request_id: self.next_packet_seq(), s_servant_name: "mqq.IMService.FriendListServiceServantObj".to_string(), s_func_name: "SetGroupReq".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("friendlist.SetGroupReq", pkt.freeze()) } /// 添加好友分组 // friendlist.SetGroupReq pub fn build_friend_list_add_group_req_packet(&self, sort_id: u8, group_name: &str) -> Packet { let mut body = BytesMut::new(); body.put_u8(sort_id); body.put_u8(group_name.len() as u8); body.put_slice(group_name.as_bytes()); self.build_friend_list_set_group_req_packet(0, body.freeze()) } /// 重命名好友分组 // friendlist.SetGroupReq pub fn build_friend_list_rename_group_req_packet( &self, group_id: u8, group_name: &str, ) -> Packet { let mut body = BytesMut::new(); body.put_u8(group_id); body.put_u8(group_name.len() as u8); body.put_slice(group_name.as_bytes()); self.build_friend_list_set_group_req_packet(1, body.freeze()) } /// 删除好友分组 // friendlist.SetGroupReq pub fn build_friend_list_del_group_req_packet(&self, group_id: u8) -> Packet { let mut body = BytesMut::new(); body.put_u8(group_id); self.build_friend_list_set_group_req_packet(2, body.freeze()) } } ================================================ FILE: ricq-core/src/command/friendlist/decoder.rs ================================================ use bytes::{Buf, Bytes}; use jcers::Jce; use crate::command::friendlist::*; use crate::structs::{FriendInfo, GroupInfo, GroupMemberInfo, GroupMemberPermission}; use crate::{jce, RQError, RQResult}; impl super::super::super::Engine { // friendlist.getFriendGroupList pub fn decode_friend_group_list_response( &self, mut payload: Bytes, ) -> RQResult { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload).map_err(RQError::from)?; let mut data: jce::RequestDataVersion3 = jcers::from_buf(&mut request.s_buffer).map_err(RQError::from)?; let mut fl_resp = data.map.remove("FLRESP").ok_or_else(|| { RQError::Decode("decode_friend_group_list_response FLRESP not found".into()) })?; fl_resp.advance(1); let resp: jce::FriendListResponse = jcers::from_buf(&mut fl_resp).map_err(RQError::from)?; Ok(FriendListResponse { total_count: resp.total_friend_count, online_friend_count: resp.online_friend_count, friends: resp .friend_info_list .into_iter() .map(|f| FriendInfo { uin: f.friend_uin, nick: f.nick, remark: f.remark, face_id: f.face_id, group_id: f.group_id, }) .collect(), friend_groups: resp .group_info_list .into_iter() .map(|g| { ( g.group_id, FriendGroupInfo { group_id: g.group_id, group_name: g.group_name, friend_count: g.friend_count, online_friend_count: g.online_friend_count, seq_id: g.seq_id, }, ) }) .collect(), }) } // friendlist.GetTroopListReqV2 pub fn decode_group_list_response(&self, mut payload: Bytes) -> RQResult { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload).map_err(RQError::from)?; let mut data: jce::RequestDataVersion3 = jcers::from_buf(&mut request.s_buffer).map_err(RQError::from)?; let mut fl_resp = data.map.remove("GetTroopListRespV2").ok_or_else(|| { RQError::Decode("decode_group_list_response GetTroopListRespV2 not found".into()) })?; fl_resp.advance(1); let mut r = Jce::new(&mut fl_resp); let vec_cookie: Bytes = r.get_by_tag(4).map_err(RQError::from)?; let groups: Vec = r.get_by_tag(5).map_err(RQError::from)?; let l = groups .into_iter() .map(|g| GroupInfo { uin: g.group_uin, code: g.group_code, name: g.group_name, memo: g.group_memo, owner_uin: g.group_owner_uin, member_count: g.member_num as u16, max_member_count: g.max_group_member_num as u16, shut_up_timestamp: g.shut_up_timestamp, my_shut_up_timestamp: g.my_shut_up_timestamp, ..Default::default() }) .collect(); Ok(GroupListResponse { groups: l, vec_cookie, }) } // friendlist.GetTroopMemberListReq pub fn decode_group_member_list_response( &self, mut payload: Bytes, group_owner_uin: i64, ) -> RQResult { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload).map_err(RQError::from)?; let mut data: jce::RequestDataVersion3 = jcers::from_buf(&mut request.s_buffer).map_err(RQError::from)?; let mut fl_resp = data.map.remove("GTMLRESP").ok_or_else(|| { RQError::Decode("decode_group_member_list_response GTMLRESP not found".into()) })?; fl_resp.advance(1); let mut r = Jce::new(&mut fl_resp); let members: Vec = r.get_by_tag(3).map_err(RQError::from)?; let next_uin = r.get_by_tag(4).map_err(RQError::from)?; let mut l: Vec = Vec::new(); for m in members { l.push(GroupMemberInfo { uin: m.member_uin, gender: m.gender, nickname: m.nick, card_name: m.name, level: m.member_level as u16, join_time: m.join_time, last_speak_time: m.last_speak_time, special_title: m.special_title, special_title_expire_time: m.special_title_expire_time, shut_up_timestamp: m.shut_up_timestap, permission: if group_owner_uin == m.member_uin { GroupMemberPermission::Owner } else { match m.flag { 1 => GroupMemberPermission::Administrator, _ => GroupMemberPermission::Member, } }, ..Default::default() }) } Ok(GroupMemberListResponse { next_uin, list: l }) } //friendlist.delFriend pub fn decode_remove_friend(&self, mut payload: Bytes) -> RQResult { let mut req: jce::RequestPacket = jcers::from_buf(&mut payload)?; let mut data: jce::RequestDataVersion3 = jcers::from_buf(&mut req.s_buffer)?; let mut r = data .map .remove("DFRESP") .ok_or_else(|| RQError::Decode("decode_remove_friend `DFRESP` not found".into()))?; jcers::from_buf(&mut r).map_err(Into::into) } } ================================================ FILE: ricq-core/src/command/friendlist/mod.rs ================================================ use std::collections::HashMap; use bytes::Bytes; use crate::structs::*; pub mod builder; pub mod decoder; #[derive(Debug, Default)] pub struct FriendListResponse { /// 好友列表 pub friends: Vec, /// 好友分组 pub friend_groups: HashMap, /// 好友数量 pub total_count: i16, /// 在线好友数量 pub online_friend_count: i16, } #[derive(Debug)] pub struct GroupListResponse { pub groups: Vec, pub vec_cookie: Bytes, } #[derive(Debug)] pub struct GroupMemberListResponse { pub next_uin: i64, pub list: Vec, } ================================================ FILE: ricq-core/src/command/group_anonymous_generate_nick/builder.rs ================================================ use crate::command::common::PbToBytes; use crate::pb; use crate::protocol::packet::Packet; impl super::super::super::Engine { // group_anonymous_generate_nick.group pub fn build_get_anony_info_request(&self, group_code: i64) -> Packet { let req = pb::cmd0x3bb::AnonyMsg { cmd: Some(1), anony_req: Some(pb::cmd0x3bb::C3bbReqBody { uin: Some(self.uin() as u64), group_code: Some(group_code as u64), }), ..Default::default() }; self.uni_packet("group_anonymous_generate_nick.group", req.to_bytes()) } } ================================================ FILE: ricq-core/src/command/group_anonymous_generate_nick/decoder.rs ================================================ use bytes::Bytes; use prost::Message; use crate::msg::elem::Anonymous; use crate::{pb, RQError, RQResult}; impl super::super::super::Engine { // group_member_card.get_group_member_card_info pub fn decode_get_anony_info_response(&self, payload: Bytes) -> RQResult> { let resp = pb::cmd0x3bb::AnonyMsg::decode(&*payload)?; let rsp = resp.anony_rsp.ok_or(RQError::EmptyField("anony_rsp"))?; let enable_anony = rsp .anony_status .map(|s| s.forbid_talking.unwrap_or(1) == 0) .unwrap_or_default(); if !enable_anony { return Ok(None); } Ok(Some(Anonymous { anon_id: vec![], nick: String::from_utf8_lossy(&rsp.anony_name.unwrap_or_default()).into_owned(), portrait_index: rsp.portrait_index.unwrap_or_default() as i32, bubble_index: rsp.bubble_index.unwrap_or_default() as i32, expire_time: rsp.expired_time.unwrap_or_default() as i32, color: rsp.color.unwrap_or_default(), })) } } ================================================ FILE: ricq-core/src/command/group_anonymous_generate_nick/mod.rs ================================================ pub mod builder; pub mod decoder; ================================================ FILE: ricq-core/src/command/group_member_card/builder.rs ================================================ use crate::command::common::PbToBytes; use crate::pb; use crate::protocol::packet::*; impl super::super::super::Engine { // group_member_card.get_group_member_card_info pub fn build_group_member_info_request_packet(&self, group_code: i64, uin: i64) -> Packet { let payload = pb::GroupMemberReqBody { group_code, uin, new_client: true, client_type: 1, rich_card_name_ver: 1, }; self.uni_packet( "group_member_card.get_group_member_card_info", payload.to_bytes(), ) } } ================================================ FILE: ricq-core/src/command/group_member_card/decoder.rs ================================================ use bytes::Bytes; use prost::Message; use crate::structs::{GroupMemberInfo, GroupMemberPermission}; use crate::{pb, RQError, RQResult}; impl super::super::super::Engine { // group_member_card.get_group_member_card_info pub fn decode_group_member_info_response(&self, payload: Bytes) -> RQResult { let resp = pb::GroupMemberRspBody::decode(&*payload)?; let group_code = resp.group_code; let mem_info = resp .mem_info .ok_or_else(|| RQError::Decode("mem_info is none".to_string()))?; Ok(GroupMemberInfo { group_code, uin: mem_info.uin, gender: mem_info.sex as u8, nickname: String::from_utf8_lossy(&mem_info.nick).into_owned(), card_name: String::from_utf8_lossy(&mem_info.card).into_owned(), level: mem_info.level as u16, join_time: mem_info.join, last_speak_time: mem_info.last_speak, special_title: String::from_utf8_lossy(&mem_info.special_title).into_owned(), special_title_expire_time: mem_info.special_title_expire_time as i64, permission: match mem_info.role { 3 => GroupMemberPermission::Owner, 2 => GroupMemberPermission::Administrator, _ => GroupMemberPermission::Member, }, // TODO group owner ..Default::default() }) } } ================================================ FILE: ricq-core/src/command/group_member_card/mod.rs ================================================ pub mod builder; pub mod decoder; ================================================ FILE: ricq-core/src/command/heartbeat/builder.rs ================================================ use crate::protocol::packet::*; impl super::super::super::Engine { // Heartbeat.Alive pub fn build_heartbeat_packet(&self) -> Packet { let seq = self.next_seq(); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::NoEncrypt, seq_id: seq as i32, command_name: "Heartbeat.Alive".into(), uin: self.uin(), ..Default::default() } } } ================================================ FILE: ricq-core/src/command/heartbeat/mod.rs ================================================ pub mod builder; ================================================ FILE: ricq-core/src/command/img_store/builder.rs ================================================ use crate::command::common::PbToBytes; use crate::pb; use crate::protocol::packet::Packet; impl super::super::super::Engine { #[allow(clippy::too_many_arguments)] pub fn build_group_image_store_packet( &self, group_code: i64, file_name: String, md5: Vec, size: u64, width: u32, height: u32, image_type: u32, ) -> Packet { let req = pb::cmd0x388::D388ReqBody { net_type: Some(3), subcmd: Some(1), // TODO 支持多张图片? tryup_img_req: vec![pb::cmd0x388::TryUpImgReq { group_code: Some(group_code as u64), src_uin: Some(self.uin() as u64), file_md5: Some(md5), file_size: Some(size), file_name: Some(file_name.into_bytes()), src_term: Some(5), platform_type: Some(9), bu_type: Some(1), pic_type: Some(image_type), pic_width: Some(width), pic_height: Some(height), build_ver: Some(self.transport.version.build_ver.as_bytes().to_vec()), app_pic_type: Some(1006), // 1052? ..Default::default() }], extension: Some(vec![]), ..Default::default() }; self.uni_packet("ImgStore.GroupPicUp", req.to_bytes()) } } ================================================ FILE: ricq-core/src/command/img_store/decoder.rs ================================================ use bytes::Bytes; use prost::Message; use crate::command::img_store::GroupImageStoreResp; use crate::common::RQAddr; use crate::RQError::EmptyField; use crate::{pb, RQError, RQResult}; impl super::super::super::Engine { pub fn decode_group_image_store_response( &self, payload: Bytes, ) -> RQResult { let mut rsp = pb::cmd0x388::D388RspBody::decode(&*payload)?; let rsp = rsp.tryup_img_rsp.pop().ok_or(EmptyField("tryup_img_rsp"))?; if rsp.result() != 0 { return Err(RQError::Other( String::from_utf8_lossy(&rsp.fail_msg.unwrap_or_default()).into_owned(), )); } Ok(if rsp.file_exit() { GroupImageStoreResp::Exist { file_id: rsp.fileid.unwrap_or_default(), addrs: rsp .up_ip .into_iter() .zip(rsp.up_port) .map(|(ip, port)| RQAddr(ip, port as u16)) .collect(), } } else { GroupImageStoreResp::NotExist { file_id: rsp.fileid.unwrap_or_default(), upload_key: rsp.up_ukey.unwrap_or_default(), upload_addrs: rsp .up_ip .into_iter() .zip(rsp.up_port) .map(|(ip, port)| RQAddr(ip, port as u16)) .collect(), } }) } } ================================================ FILE: ricq-core/src/command/img_store/mod.rs ================================================ use crate::common::RQAddr; pub mod builder; pub mod decoder; #[derive(Debug, Clone)] pub enum GroupImageStoreResp { Exist { file_id: u64, addrs: Vec, }, NotExist { file_id: u64, upload_key: Vec, upload_addrs: Vec, }, } ================================================ FILE: ricq-core/src/command/long_conn/builder.rs ================================================ use crate::command::common::PbToBytes; use crate::protocol::packet::Packet; impl crate::Engine { // LongConn.OffPicUp #[allow(clippy::too_many_arguments)] pub fn build_off_pic_up_packet( &self, target: i64, file_name: String, md5: Vec, size: u64, width: u32, height: u32, image_type: u32, ) -> Packet { let req = crate::pb::cmd0x352::ReqBody { subcmd: Some(1), tryup_img_req: vec![crate::pb::cmd0x352::D352TryUpImgReq { src_uin: Some(self.uin() as u64), dst_uin: Some(target as u64), file_name: Some(file_name.as_bytes().to_vec()), //todo file_md5: Some(md5), file_size: Some(size), pic_width: Some(width), pic_height: Some(height), pic_type: Some(image_type), pic_original: Some(true), build_ver: Some(self.transport.version.build_ver.as_bytes().to_vec()), bu_type: Some(1), src_term: Some(5), platform_type: Some(9), ..Default::default() }], ..Default::default() }; self.uni_packet("LongConn.OffPicUp", req.to_bytes()) } } ================================================ FILE: ricq-core/src/command/long_conn/decoder.rs ================================================ use bytes::Bytes; use crate::common::RQAddr; use crate::{pb, RQError, RQResult}; use prost::Message; use super::OffPicUpResp; impl crate::Engine { // LongConn.OffPicUp pub fn decode_off_pic_up_response(&self, payload: Bytes) -> RQResult { let mut resp = pb::cmd0x352::RspBody::decode(&*payload)?; if let Some(err) = resp.fail_msg { return Err(RQError::Other(String::from_utf8_lossy(&err).into_owned())); } if resp.subcmd() != 1 { return Err(RQError::Other(format!( "subcmd is not 1: {}", resp.subcmd() ))); } let img = resp .tryup_img_rsp .pop() .ok_or(RQError::EmptyField("tryup_img_rsp"))?; if img.result() != 0 { return Err(RQError::Other( String::from_utf8_lossy(&img.fail_msg.unwrap_or_default()).into_owned(), )); } if img.file_exit() { Ok(OffPicUpResp::Exist { uuid: String::from_utf8_lossy(img.up_uuid()).into_owned(), res_id: img.up_resid.unwrap_or_default(), }) } else { Ok(OffPicUpResp::UploadRequired { uuid: String::from_utf8_lossy(img.up_uuid()).into_owned(), res_id: img.up_resid.unwrap_or_default(), upload_key: img.up_ukey.unwrap_or_default(), upload_addrs: img .up_ip .into_iter() .zip(img.up_port) .map(|(ip, port)| RQAddr(ip, port as u16)) .collect(), }) } } } ================================================ FILE: ricq-core/src/command/long_conn/mod.rs ================================================ use crate::common::RQAddr; mod builder; mod decoder; #[derive(Debug, Clone)] pub enum OffPicUpResp { Exist { res_id: String, uuid: String, }, UploadRequired { res_id: String, uuid: String, upload_key: Vec, upload_addrs: Vec, }, } ================================================ FILE: ricq-core/src/command/longmsg/builder.rs ================================================ use crate::pb; use prost::Message; impl super::super::super::Engine { pub fn build_long_req(&self, dst_uin: i64, msg_content: Vec, msg_ukey: Vec) -> Vec { pb::longmsg::LongReqBody { subcmd: 1, term_type: 5, platform_type: 9, msg_up_req: vec![pb::longmsg::LongMsgUpReq { msg_type: 3, // group dst_uin, msg_id: 0, msg_content, store_type: 2, msg_ukey, need_cache: 0, }], ..Default::default() } .encode_to_vec() } } ================================================ FILE: ricq-core/src/command/longmsg/mod.rs ================================================ pub mod builder; ================================================ FILE: ricq-core/src/command/message_svc/builder.rs ================================================ use prost::Message; use crate::command::common::PbToBytes; use crate::pb; use crate::protocol::packet::Packet; impl super::super::super::Engine { // MessageSvc.PbSendMsg #[allow(clippy::too_many_arguments)] pub fn build_group_sending_packet( &self, group_code: i64, elems: Vec, ptt: Option, ran: i32, pkg_num: i32, pkg_index: i32, pkg_div: i32, forward: bool, ) -> Packet { let req = pb::msg::SendMessageRequest { routing_head: Some(pb::msg::RoutingHead { routing_head: Some(pb::msg::routing_head::RoutingHead::Grp(pb::msg::Grp { group_code: Some(group_code), })), }), content_head: Some(pb::msg::ContentHead { pkg_num: Some(pkg_num), pkg_index: Some(pkg_index), div_seq: Some(pkg_div), ..Default::default() }), msg_body: Some(pb::msg::MessageBody { rich_text: Some(pb::msg::RichText { elems, ptt, ..Default::default() }), ..Default::default() }), msg_seq: Some(self.next_group_seq()), msg_rand: Some(ran), // 群消息没有 sync_cookie msg_via: Some(1), // 从哪进入界面(联系人列表/搜索/...) msg_ctrl: if forward { // 合并转发 Some(pb::msg::MsgCtrl { msg_flag: Some(4) }) } else { None }, ..Default::default() }; self.uni_packet("MessageSvc.PbSendMsg", req.to_bytes()) } // build sync_cookie fn sync_cookie(&self, time: i64) -> Vec { if !self.transport.sig.sync_cookie.is_empty() { return self.transport.sig.sync_cookie.to_vec(); } pb::msg::SyncCookie { time1: Some(time), time: Some(time), ran1: Some(rand::random::() as i64), ran2: Some(rand::random::() as i64), const1: Some(self.transport.sig.sync_const1 as i64), const2: Some(self.transport.sig.sync_const2 as i64), const3: Some(self.transport.sig.sync_const3 as i64), last_sync_time: Some(time), const4: Some(0), } .encode_to_vec() } // MessageSvc.PbGetMsg pub fn build_get_message_request_packet(&self, flag: i32, time: i64) -> Packet { // start = 0, continue = 1, stop = 2 let sync_cookie = self.sync_cookie(time); let req = pb::msg::GetMessageRequest { sync_flag: Some(flag), sync_cookie: Some(sync_cookie), latest_ramble_number: Some(20), other_ramble_number: Some(3), online_sync_flag: Some(1), context_flag: Some(1), msg_req_type: Some(1), pubaccount_cookie: Some(vec![]), msg_ctrl_buf: Some(vec![]), server_buf: Some(vec![]), ..Default::default() }; self.uni_packet("MessageSvc.PbGetMsg", req.to_bytes()) } // MessageSvc.PbDeleteMsg pub fn build_delete_message_request_packet(&self, items: Vec) -> Packet { let body = pb::DeleteMessageRequest { items }.to_bytes(); self.uni_packet("MessageSvc.PbDeleteMsg", body) } // MessageSvc.PbSendMsg pub fn build_send_message_packet( &self, routing_head: pb::msg::routing_head::RoutingHead, elems: Vec, ptt: Option, seq: i32, ran: i32, time: i64, ) -> Packet { let sync_cookie = self.sync_cookie(time); let req = pb::msg::SendMessageRequest { routing_head: Some(pb::msg::RoutingHead { routing_head: Some(routing_head), }), content_head: Some(pb::msg::ContentHead { pkg_num: Some(1), pkg_index: Some(0), div_seq: Some(0), ..Default::default() }), msg_body: Some(pb::msg::MessageBody { rich_text: Some(pb::msg::RichText { elems, ptt, ..Default::default() }), ..Default::default() }), msg_seq: Some(seq), msg_rand: Some(ran), sync_cookie: Some(sync_cookie), msg_via: Some(1), ..Default::default() }; self.uni_packet("MessageSvc.PbSendMsg", req.to_bytes()) } // MessageSvc.PbGetGroupMsg pub fn build_get_group_msg_request( &self, group_code: i64, begin_seq: i64, end_seq: i64, ) -> Packet { let req = pb::msg::GetGroupMsgReq { group_code: Some(group_code as u64), begin_seq: Some(begin_seq as u64), end_seq: Some(end_seq as u64), public_group: Some(false), ..Default::default() }; self.uni_packet("MessageSvc.PbGetGroupMsg", req.to_bytes()) } pub fn build_friend_recall_packet( &self, uin: i64, msg_time: i64, seqs: Vec, rands: Vec, ) -> Packet { let req = pb::msg::MsgWithDrawReq { c2c_with_draw: vec![pb::msg::C2cMsgWithDrawReq { msg_info: seqs .into_iter() .zip(rands) .map(|(seq, ran)| pb::msg::C2cMsgInfo { from_uin: Some(self.uin()), to_uin: Some(uin), msg_seq: Some(seq), msg_uid: Some(0x0100_0000_0000_0000 | (ran as i64 & 0xFFFFFFFF)), // TODO msg_time: Some(msg_time), msg_random: Some(ran), routing_head: Some(pb::msg::RoutingHead { routing_head: Some(pb::msg::routing_head::RoutingHead::C2c( pb::msg::C2c { to_uin: Some(uin) }, )), }), ..Default::default() }) .collect(), long_message_flag: Some(0), reserved: Some(vec![0x08, 0x00]), // TODO PB: GrpTmp {1: 1,2: group_uin} sub_cmd: Some(1), }], ..Default::default() }; self.uni_packet("PbMessageSvc.PbMsgWithDraw", req.to_bytes()) } pub fn build_group_recall_packet( &self, group_code: i64, seqs: Vec, rands: Vec, ) -> Packet { let req = pb::msg::MsgWithDrawReq { group_with_draw: vec![pb::msg::GroupMsgWithDrawReq { sub_cmd: Some(1), group_code: Some(group_code), user_def: Some(vec![0x08, 0x00]), msg_list: seqs .into_iter() .zip(rands) .map(|(seq, ran)| pb::msg::GroupMsgInfo { msg_seq: Some(seq), msg_random: Some(ran), msg_type: Some(0), }) .collect(), group_type: None, }], ..Default::default() }; self.uni_packet("PbMessageSvc.PbMsgWithDraw", req.to_bytes()) } } ================================================ FILE: ricq-core/src/command/message_svc/decoder.rs ================================================ use bytes::{Buf, Bytes}; use crate::pb::msg::GetMessageResponse; use crate::{jce, RQError, RQResult}; use prost::Message; impl crate::Engine { // MessageSvc.PushNotify pub fn decode_svc_notify(&self, mut payload: Bytes) -> RQResult { payload.advance(4); let mut req: jce::RequestPacket = jcers::from_buf(&mut payload)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut req.s_buffer)?; let mut notify_data = data .map .remove("req_PushNotify") .ok_or_else(|| RQError::Decode("req_PushNotify".into()))? .remove("PushNotifyPack.RequestPushNotify") .ok_or_else(|| RQError::Decode("PushNotifyPack.RequestPushNotify".into()))?; notify_data.advance(1); let notify: jce::RequestPushNotify = jcers::from_buf(&mut notify_data)?; Ok(notify) } // MessageSvc.PushForceOffline pub fn decode_force_offline( &self, mut payload: Bytes, ) -> RQResult { let mut req: jce::RequestPacket = jcers::from_buf(&mut payload)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut req.s_buffer)?; let mut data = data .map .remove("req_PushForceOffline") .ok_or_else(|| RQError::Decode("req_PushForceOffline".into()))? .remove("PushNotifyPack.RequestPushForceOffline") .ok_or_else(|| RQError::Decode("PushNotifyPack.RequestPushForceOffline".into()))?; data.advance(1); let offline: jce::RequestPushForceOffline = jcers::from_buf(&mut data)?; Ok(offline) } // MessageSvc.PbGetMsg pub fn decode_message_svc_packet( &self, payload: Bytes, ) -> RQResult { let resp = GetMessageResponse::decode(&*payload)?; Ok(super::MessageSyncResponse { msg_rsp_type: resp.msg_rsp_type.unwrap_or_default(), sync_flag: resp.sync_flag.unwrap_or(2), // default stop sync_cookie: resp.sync_cookie, pub_account_cookie: resp.pub_account_cookie, msgs: resp .uin_pair_msgs .into_iter() .flat_map(|x| x.messages) .collect(), }) } } ================================================ FILE: ricq-core/src/command/message_svc/mod.rs ================================================ use crate::pb; pub mod builder; pub mod decoder; pub struct MessageSyncResponse { pub msg_rsp_type: i32, pub sync_flag: i32, pub sync_cookie: Option>, pub pub_account_cookie: Option>, pub msgs: Vec, } ================================================ FILE: ricq-core/src/command/mod.rs ================================================ pub mod common; pub mod config_push_svc; pub mod friendlist; pub mod group_anonymous_generate_nick; pub mod group_member_card; pub mod heartbeat; pub mod img_store; pub mod long_conn; pub mod longmsg; pub mod message_svc; pub mod multi_msg; pub mod oidb_svc; pub mod online_push; pub mod pb_message_svc; pub mod profile_service; pub mod ptt_center_svr; pub mod ptt_store; pub mod reg_prxy_svc; pub mod signature; pub mod stat_svc; pub mod summary_card; pub mod visitor_svc; pub mod wtlogin; ================================================ FILE: ricq-core/src/command/multi_msg/builder.rs ================================================ use std::collections::HashMap; use std::io::Write; use flate2::write::GzEncoder; use flate2::Compression; use crate::command::common::PbToBytes; use crate::command::multi_msg::{ForwardMessage, PackedMessage}; use crate::msg::elem::RichMsg; use crate::msg::MessageChain; use crate::pb; use crate::protocol::device::random_string; use crate::protocol::packet::Packet; impl super::super::super::Engine { pub fn build_multi_msg_apply_down_req(&self, res_id: String) -> Packet { let req = pb::multimsg::MultiReqBody { subcmd: 2, term_type: 5, platform_type: 9, net_type: 3, build_ver: self.transport.version.build_ver.into(), multimsg_applydown_req: vec![pb::multimsg::MultiMsgApplyDownReq { msg_resid: res_id.into_bytes(), msg_type: 3, ..Default::default() }], bu_type: 2, req_channel_type: 2, ..Default::default() }; self.uni_packet("MultiMsg.ApplyDown", req.to_bytes()) } pub fn build_multi_msg_apply_up_req( &self, msg_size: i64, msg_md5: Vec, bu_type: i32, dst_uin: i64, ) -> Packet { let req = pb::multimsg::MultiReqBody { subcmd: 1, term_type: 5, platform_type: 9, net_type: 3, build_ver: self.transport.version.build_ver.into(), req_channel_type: 0, multimsg_applyup_req: vec![pb::multimsg::MultiMsgApplyUpReq { dst_uin, msg_size, msg_md5, msg_type: 3, // group ..Default::default() }], bu_type, ..Default::default() }; self.uni_packet("MultiMsg.ApplyUp", req.to_bytes()) } pub fn calculate_validation_data( &self, messages: Vec, group_code: i64, ) -> Vec { let PackedMessage { mut buffer, filename, } = self.pack_forward_msg(messages, group_code); let msgs = buffer.remove(&filename).expect("msgs not found"); let mut pb_item_list = vec![pb::msg::PbMultiMsgItem { file_name: Some("MultiMsg".into()), buffer: Some(pb::msg::PbMultiMsgNew { msg: msgs.clone() }), }]; for (filename, msg) in buffer { pb_item_list.push(pb::msg::PbMultiMsgItem { file_name: Some(filename), buffer: Some(pb::msg::PbMultiMsgNew { msg }), }); } let trans = pb::msg::PbMultiMsgTransmit { msg: msgs, pb_item_list, }; let mut encoder = GzEncoder::new(vec![], Compression::default()); encoder.write_all(&trans.to_bytes()).ok(); encoder.finish().unwrap_or_default() } fn pack_forward_msg( &self, messages: Vec, group_code: i64, ) -> PackedMessage { let mut packed_buffers = HashMap::default(); let msgs: Vec = messages .into_iter() .map(|m| match m { ForwardMessage::Message(message) => { self.pack_msg(message, group_code) } ForwardMessage::Forward(forward) => { let t_sum = forward.nodes.len(); let preview = super::gen_forward_preview(&forward.nodes); let packed_message = self.pack_forward_msg(forward.nodes, group_code); packed_buffers.extend(packed_message.buffer); self.pack_msg( super::MessageNode { sender_id: forward.sender_id, time: forward.time, sender_name: forward.sender_name, elements: MessageChain( RichMsg { template1: format!( r##"群聊的聊天记录{}查看{}条转发消息"##, packed_message.filename, t_sum, preview, t_sum ), service_id: 35, }.into() ), }, group_code, ) } }) .collect(); let filename = random_string(16); packed_buffers.insert(filename.clone(), msgs); PackedMessage { filename, buffer: packed_buffers, } } fn pack_msg(&self, node: super::MessageNode, group_code: i64) -> pb::msg::Message { pb::msg::Message { head: Some(pb::msg::MessageHead { from_uin: Some(node.sender_id), msg_type: Some(82), // troop msg_seq: Some(self.next_group_seq()), msg_time: Some(node.time), msg_uid: Some(0x01000000000000000 | rand::random::() as i64), // TODO ? group_info: Some(pb::msg::GroupInfo { group_code: Some(group_code), group_card: Some(node.sender_name.into_bytes()), ..Default::default() }), mutiltrans_head: Some(pb::msg::MutilTransHead { status: Some(0), msg_id: Some(1), }), ..Default::default() }), body: Some(pb::msg::MessageBody { rich_text: Some(pb::msg::RichText { elems: node.elements.into(), ..Default::default() }), ..Default::default() }), ..Default::default() } } } ================================================ FILE: ricq-core/src/command/multi_msg/decoder.rs ================================================ use bytes::Bytes; use crate::{pb, RQError, RQResult}; use prost::Message; impl super::super::super::Engine { pub fn decode_multi_msg_apply_down_resp( &self, payload: Bytes, ) -> RQResult { pb::multimsg::MultiRspBody::decode(&*payload)? .multimsg_applydown_rsp .pop() .ok_or(RQError::EmptyField("multimsg_applydown_rsp")) } pub fn decode_multi_msg_apply_up_resp( &self, payload: Bytes, ) -> RQResult { pb::multimsg::MultiRspBody::decode(&*payload)? .multimsg_applyup_rsp .pop() .ok_or(RQError::EmptyField("multimsg_applyup_rsp")) } } ================================================ FILE: ricq-core/src/command/multi_msg/mod.rs ================================================ use std::collections::HashMap; use std::fmt::Write; use crate::msg::MessageChain; use crate::pb; pub mod builder; pub mod decoder; pub enum ForwardMessage { Message(MessageNode), Forward(ForwardNode), } pub fn gen_forward_preview(messages: &[ForwardMessage]) -> String { let mut ret = String::new(); for msg in messages.iter().take(4) { ret.push_str(r##""##); match msg { ForwardMessage::Message(v) => write!(&mut ret, "{}: {}", v.sender_name, v.elements), ForwardMessage::Forward(v) => write!(&mut ret, "{}: [转发消息]", v.sender_name), } .unwrap(); ret.push_str(""); } ret } pub struct MessageNode { pub sender_id: i64, pub time: i32, pub sender_name: String, pub elements: MessageChain, } impl From for ForwardMessage { fn from(n: MessageNode) -> Self { Self::Message(n) } } pub struct ForwardNode { pub sender_id: i64, pub time: i32, pub sender_name: String, pub nodes: Vec, } impl From for ForwardMessage { fn from(f: ForwardNode) -> Self { Self::Forward(f) } } struct PackedMessage { pub filename: String, pub buffer: HashMap>, } ================================================ FILE: ricq-core/src/command/oidb_svc/builder.rs ================================================ use bytes::{BufMut, BytesMut}; use super::*; use crate::command::common::PbToBytes; use crate::pb; use crate::protocol::packet::Packet; impl super::super::super::Engine { // OidbSvc.0x4ff_9_IMCore pub fn build_update_profile_detail_packet(&self, profile: ProfileDetailUpdate) -> Packet { let mut w = BytesMut::new(); w.put_u32(self.uin() as u32); w.put_u8(0); w.put_u16(profile.0.len() as u16); for (tag, value) in profile.0 { w.put_u16(tag); w.put_u16(value.len() as u16); w.put_slice(&value); } let payload = self.transport.encode_oidb_packet(0x4ff, 9, w.freeze()); self.uni_packet("OidbSvc.0x4ff_9_IMCore", payload) } // OidbSvc.0x88d_0 pub fn build_group_info_request_packet(&self, group_codes: Vec) -> Packet { let body = pb::oidb::D88dReqBody { app_id: Some(self.transport.version.app_id), req_group_info: group_codes .into_iter() .map(|group_code| pb::oidb::ReqGroupInfo { group_code: Some(group_code as u64), stgroupinfo: Some(pb::oidb::D88dGroupInfo { group_owner: Some(0), group_uin: Some(0), group_create_time: Some(0), group_flag: Some(0), group_member_max_num: Some(0), group_member_num: Some(0), group_option: Some(0), group_level: Some(0), group_face: Some(0), group_name: Some(vec![]), group_memo: Some(vec![]), group_finger_memo: Some(vec![]), group_last_msg_time: Some(0), group_cur_msg_seq: Some(0), group_question: Some(vec![]), group_answer: Some(vec![]), group_grade: Some(0), active_member_num: Some(0), head_portrait_seq: Some(0), msg_head_portrait: Some(pb::oidb::D88dGroupHeadPortrait::default()), st_group_ex_info: Some(pb::oidb::D88dGroupExInfoOnly::default()), group_sec_level: Some(0), cmduin_privilege: Some(0), no_finger_open_flag: Some(0), no_code_finger_open_flag: Some(0), ..Default::default() }), ..Default::default() }) .collect(), pc_client_version: Some(0), }; let payload = self.transport.encode_oidb_packet(0x88d, 0, body.to_bytes()); self.uni_packet("OidbSvc.0x88d_0", payload) } // OidbSvc.0x570_8 pub fn build_group_mute_packet( &self, group_code: i64, member_uin: i64, duration: u32, ) -> Packet { let mut w = BytesMut::new(); w.put_u32(group_code as u32); w.put_u8(32); w.put_u16(1); w.put_u32(member_uin as u32); w.put_u32(duration); let payload = self.transport.encode_oidb_packet(0x570, 8, w.freeze()); self.uni_packet("OidbSvc.0x570_8", payload) } // OidbSvc.0x89a_0 fn build_group_operation_packet(&self, body: pb::oidb::D89aReqBody) -> Packet { let payload = self.transport.encode_oidb_packet(0x89a, 0, body.to_bytes()); self.uni_packet("OidbSvc.0x89a_0", payload) } // OidbSvc.0x89a_0 pub fn build_group_mute_all_packet(&self, group_code: i64, mute: bool) -> Packet { let shut_up_time: i32 = if mute { 268435455 } else { 0 }; let body = pb::oidb::D89aReqBody { group_code, st_group_info: Some(pb::oidb::D89aGroupinfo { shutup_time: Some(pb::oidb::d89a_groupinfo::ShutupTime::Val(shut_up_time)), ..Default::default() }), ..Default::default() }; self.build_group_operation_packet(body) } // OidbSvc.0x89a_0 pub fn build_group_name_update_packet(&self, group_code: i64, name: String) -> Packet { let body = pb::oidb::D89aReqBody { group_code, st_group_info: Some(pb::oidb::D89aGroupinfo { ing_group_name: name.as_bytes().to_vec(), ..Default::default() }), ..Default::default() }; self.build_group_operation_packet(body) } // OidbSvc.0x89a_0 pub fn build_group_memo_update_packet(&self, group_code: i64, memo: String) -> Packet { let body = pb::oidb::D89aReqBody { group_code, st_group_info: Some(pb::oidb::D89aGroupinfo { ing_group_memo: memo.as_bytes().to_vec(), ..Default::default() }), ..Default::default() }; self.build_group_operation_packet(body) } // OidbSvc.0x8a0_0 pub fn build_group_kick_packet( &self, group_code: i64, member_uins: Vec, kick_msg: &str, block: bool, ) -> Packet { let flag_block = if block { 1 } else { 0 }; let body = pb::oidb::D8a0ReqBody { opt_uint64_group_code: group_code, msg_kick_list: member_uins .into_iter() .map(|member_uin| pb::oidb::D8a0KickMemberInfo { opt_uint32_operate: 5, opt_uint64_member_uin: member_uin, opt_uint32_flag: flag_block, ..Default::default() }) .collect(), kick_msg: kick_msg.as_bytes().to_vec(), ..Default::default() }; let payload = self.transport.encode_oidb_packet(0x8a0, 0, body.to_bytes()); self.uni_packet("OidbSvc.0x8a0_0", payload) } // OidbSvc.0xed3 pub fn build_group_poke_packet(&self, group_code: i64, target: i64) -> Packet { let body = pb::oidb::Ded3ReqBody { to_uin: target, group_code, ..Default::default() }; let payload = self.transport.encode_oidb_packet(0xed3, 1, body.to_bytes()); self.uni_packet("OidbSvc.0xed3", payload) } // OidbSvc.0xed3 pub fn build_friend_poke_packet(&self, target: i64) -> Packet { let body = pb::oidb::Ded3ReqBody { to_uin: target, aio_uin: target, ..Default::default() }; let payload = self.transport.encode_oidb_packet(0xed3, 1, body.to_bytes()); self.uni_packet("OidbSvc.0xed3", payload) } // OidbSvc.0x55c_1 pub fn build_group_admin_set_packet(&self, group_code: i64, member: i64, flag: bool) -> Packet { let mut w = BytesMut::new(); w.put_u32(group_code as u32); w.put_u32(member as u32); w.put_u8(if flag { 0x01 } else { 0x00 }); let payload = self.transport.encode_oidb_packet(0x55c, 1, w.freeze()); self.uni_packet("OidbSvc.0x55c_1", payload) } // OidbSvc.0x758 pub fn build_group_invite_packet(&self, group_code: i64, uin: i64) -> Packet { let body = pb::oidb::D758ReqBody { join_group_code: Some(group_code as u64), be_invited_uin_info: vec![pb::oidb::InviteUinInfo { uin: Some(uin as u64), ..Default::default() }], ..Default::default() }; let payload = self.transport.encode_oidb_packet(0x758, 1, body.to_bytes()); self.uni_packet("OidbSvc.0x758", payload) } // OidbSvc.0x8a7_0 pub fn build_group_at_all_remain_request_packet(&self, group_code: i64) -> Packet { let body = pb::oidb::D8a7ReqBody { sub_cmd: Some(1), limit_interval_type_for_uin: Some(2), limit_interval_type_for_group: Some(1), uin: Some(self.uin() as u64), group_code: Some(group_code as u64), }; let payload = self.transport.encode_oidb_packet(0x8a7, 0, body.to_bytes()); self.uni_packet("OidbSvc.0x8a7_0", payload) } // OidbSvc.0x8fc_2 pub fn build_edit_special_title_packet( &self, group_code: i64, member_uin: i64, new_title: String, ) -> Packet { let body = pb::oidb::D8fcReqBody { group_code: Some(group_code), mem_level_info: vec![pb::oidb::D8fcMemberInfo { uin: Some(member_uin), uin_name: Some(new_title.as_bytes().to_vec()), special_title: Some(new_title.as_bytes().to_vec()), special_title_expire_time: Some(-1), ..Default::default() }], ..Default::default() }; let payload = self.transport.encode_oidb_packet(0x8fc, 2, body.to_bytes()); self.uni_packet("OidbSvc.0x8fc_2", payload) } // OidbSvc.0x990 pub fn build_translate_request_packet( &self, src_language: String, dst_language: String, src_text_list: Vec, ) -> Packet { let body = pb::oidb::TranslateReqBody { batch_translate_req: Some(pb::oidb::BatchTranslateReq { src_language, dst_language, src_text_list, }), }; let payload = self.transport.encode_oidb_packet(0x990, 2, body.to_bytes()); self.uni_packet("OidbSvc.0x990", payload) } // OidbSvc.0xeac pub fn build_essence_msg_operate_packet( &self, group_code: i64, msg_seq: i32, msg_rand: i32, flag: bool, ) -> Packet { let body = pb::oidb::EacReqBody { group_code: Some(group_code as u64), seq: Some(msg_seq as u32), random: Some(msg_rand as u32), }; let payload = self.transport .encode_oidb_packet(0xeac, if flag { 1 } else { 2 }, body.to_bytes()); self.uni_packet("OidbSvc.0xeac", payload) } // OidbSvc.0xe07_0 pub fn build_image_ocr_request_packet( &self, url: String, md5: String, size: i32, wight: i32, height: i32, ) -> Packet { let body = pb::oidb::De07ReqBody { version: 1, entrance: 3, ocr_req_body: Some(pb::oidb::OcrReqBody { image_url: url, origin_md5: md5.clone(), after_compress_md5: md5, after_compress_file_size: size, after_compress_height: height, after_compress_weight: wight, ..Default::default() }), ..Default::default() }; let payload = self.transport.encode_oidb_packet(0xe07, 0, body.to_bytes()); self.uni_packet("OidbSvc.0xe07_0", payload) } pub fn build_share_music_request_packet( &self, share_target: ShareTarget, music_share: MusicShare, music_version: MusicVersion, ) -> Packet { let body = pb::oidb::Db77ReqBody { app_id: music_version.app_id, app_type: music_version.app_type, msg_style: if music_share.music_url.is_empty() { 0 } else { 4 }, client_info: Some(pb::oidb::Db77ClientInfo { platform: music_version.platform, sdk_version: music_version.sdk_version.into(), android_package_name: music_version.package_name.into(), android_signature: music_version.signature.into(), ..Default::default() }), send_type: share_target.send_type(), recv_uin: match share_target { ShareTarget::Friend(uin) => uin as u64, ShareTarget::Group(code) => code as u64, ShareTarget::Guild { channel_id, .. } => channel_id, }, rich_msg_body: Some(pb::oidb::Db77RichMsgBody { title: music_share.title, summary: music_share.summary, brief: music_share.brief, url: music_share.url, picture_url: music_share.picture_url, music_url: music_share.music_url, ..Default::default() }), recv_guild_id: if let ShareTarget::Guild { guild_id, .. } = share_target { guild_id } else { 0 }, ..Default::default() }; let payload = self.transport.encode_oidb_packet(0xb77, 9, body.to_bytes()); self.uni_packet("OidbSvc.0xb77_9", payload) } pub fn build_share_link_request_packet( &self, share_target: ShareTarget, link_share: LinkShare, ) -> Packet { let body = pb::oidb::Db77ReqBody { app_id: 100446242, app_type: 1, msg_style: 0, client_info: Some(pb::oidb::Db77ClientInfo { platform: 1, sdk_version: "0.0.0".into(), android_package_name: "com.tencent.mtt".into(), android_signature: "d8391a394d4a179e6fe7bdb8a301258b".into(), ..Default::default() }), send_type: share_target.send_type(), recv_uin: match share_target { ShareTarget::Friend(uin) => uin as u64, ShareTarget::Group(code) => code as u64, ShareTarget::Guild { channel_id, .. } => channel_id, }, rich_msg_body: Some(pb::oidb::Db77RichMsgBody { summary: link_share.summary.unwrap_or_default(), brief: link_share .brief .unwrap_or_else(|| format!("[分享] {}", link_share.title)), title: link_share.title, url: link_share.url, picture_url: link_share.picture_url.unwrap_or_else(|| "none".into()), // "none" will use default icon music_url: String::new(), action: String::new(), }), recv_guild_id: if let ShareTarget::Guild { guild_id, .. } = share_target { guild_id } else { 0 }, ..Default::default() }; let payload = self.transport.encode_oidb_packet(0xb77, 9, body.to_bytes()); self.uni_packet("OidbSvc.0xb77_9", payload) } // OidbSvc.0x899_0 pub fn build_get_group_admin_list_request_packet(&self, group_code: u64) -> Packet { let body = pb::cmd0x899::ReqBody { group_code: Some(group_code), start_uin: Some(0), identify_flag: Some(2), memberlist_opt: Some(pb::cmd0x899::Memberlist { member_uin: Some(0), privilege: Some(1), ..Default::default() }), ..Default::default() }; let payload = self.transport.encode_oidb_packet(0x899, 0, body.to_bytes()); self.uni_packet("OidbSvc.0x899_0", payload) } // OidbSvc.0xeb7 pub fn build_group_sign_in_packet(&self, group_code: i64) -> Packet { let body = pb::oidb::Deb7ReqBody { sign_in_write_req: Some(pb::oidb::StSignInWriteReq { uid: Some(self.uin().to_string()), group_id: Some(group_code.to_string()), client_version: Some(self.transport.version.sort_version_name.to_string()), }), ..Default::default() }; let payload = self.transport.encode_oidb_packet(0xeb7, 1, body.to_bytes()); self.uni_packet("OidbSvc.0xeb7", payload) } // OidbSvc.0x6d8_1 pub fn build_group_file_list_request_packet( &self, group_code: u64, folder_id: String, start_index: u32, ) -> Packet { let body = pb::oidb::D6d8ReqBody { file_list_info_req: Some(pb::oidb::GetFileListReqBody { group_code: Some(group_code), app_id: Some(3), folder_id: Some(folder_id), file_count: Some(20), all_file_count: Some(0), req_from: Some(3), sort_by: Some(1), filter_code: Some(0), uin: Some(0), start_index: Some(start_index), context: None, ..Default::default() }), ..Default::default() }; let payload = self.transport.encode_oidb_packet(0x6d8, 1, body.to_bytes()); self.uni_packet("OidbSvc.0x6d8_1", payload) } // OidbSvc.0x6d6_2 pub fn build_group_file_download_request_packet( &self, group_code: i64, file_id: String, bus_id: i32, ) -> Packet { let body = pb::oidb::D6d6ReqBody { download_file_req: Some(pb::oidb::DownloadFileReqBody { file_id: Some(file_id), group_code: Some(group_code), app_id: Some(3), bus_id: Some(bus_id), ..Default::default() }), ..Default::default() }; let payload = self.transport.encode_oidb_packet(1750, 2, body.to_bytes()); self.uni_packet("OidbSvc.0x6d6_2", payload) } // OidbSvc.0x6d8_1 pub fn build_group_file_count_request_packet(&self, group_code: u64) -> Packet { let body = pb::oidb::D6d8ReqBody { group_file_count_req: Some(pb::oidb::GetFileCountReqBody { group_code: Some(group_code), app_id: Some(3), bus_id: Some(0), }), ..Default::default() }; let payload = self.transport.encode_oidb_packet(0x6d8, 2, body.to_bytes()); self.uni_packet("OidbSvc.0x6d8_1", payload) } } ================================================ FILE: ricq-core/src/command/oidb_svc/decoder.rs ================================================ use std::collections::HashMap; use bytes::{Bytes, BytesMut}; use crate::command::oidb_svc::GroupAtAllRemainInfo; use crate::structs::{ GroupFileCount, GroupFileInfo, GroupFileItem, GroupFileList, GroupFolderInfo, GroupInfo, GroupMemberPermission, }; use crate::{pb, RQResult}; use prost::Message; use super::OcrResponse; impl super::super::super::Engine { // OidbSvc.0x88d_0 pub fn decode_group_info_response(&self, payload: Bytes) -> RQResult> { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let groups = pb::oidb::D88dRspBody::decode(&*pkg.bodybuffer)?.rsp_group_info; Ok(groups .into_iter() .filter_map(|g| { let code = g.group_code? as i64; let info = g.group_info?; Some(GroupInfo { uin: info.group_uin? as i64, code, name: String::from_utf8_lossy(&info.group_name?).into_owned(), memo: String::from_utf8_lossy(&info.group_memo?).into_owned(), owner_uin: info.group_owner? as i64, group_create_time: info.group_create_time.unwrap_or_default(), group_level: info.group_level.unwrap_or_default(), member_count: info.group_member_num? as u16, max_member_count: info.group_member_max_num? as u16, shut_up_timestamp: info.shutup_timestamp.unwrap_or_default() as i64, my_shut_up_timestamp: info.shutup_timestamp_me.unwrap_or_default() as i64, last_msg_seq: info.group_cur_msg_seq.unwrap_or_default() as i64, }) }) .collect()) } // // OidbSvc.0x8a7_0 pub fn decode_group_at_all_remain_response( &self, payload: Bytes, ) -> RQResult { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let rsp = pb::oidb::D8a7RspBody::decode(&*pkg.bodybuffer)?; Ok(GroupAtAllRemainInfo { can_at_all: rsp.can_at_all(), remain_at_all_count_for_group: rsp.remain_at_all_count_for_group(), remain_at_all_count_for_uin: rsp.remain_at_all_count_for_uin(), }) } // OidbSvc.0x990 pub fn decode_translate_response(&self, payload: Bytes) -> RQResult> { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let rsp = pb::oidb::TranslateRspBody::decode(&*pkg.bodybuffer)?; Ok(rsp.batch_translate_rsp.unwrap_or_default().dst_text_list) } // OidbSvc.0xeac_1/2 pub fn decode_essence_msg_response(&self, payload: Bytes) -> RQResult { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let resp = pb::oidb::EacRspBody::decode(&*pkg.bodybuffer)?; Ok(resp) } // OidbSvc.0xe07_0 pub fn decode_image_ocr_response(&self, payload: Bytes) -> RQResult { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let resp = pb::oidb::De07RspBody::decode(&*pkg.bodybuffer)?; Ok(OcrResponse { texts: resp .ocr_rsp_body .clone() .unwrap_or_default() .text_detections, language: resp.ocr_rsp_body.unwrap_or_default().language, }) } // OidbSvc.0x899_0 pub fn decode_get_group_admin_list_response( &self, payload: Bytes, ) -> RQResult> { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let resp = pb::cmd0x899::RspBody::decode(&*pkg.bodybuffer)?; Ok(resp .memberlist .into_iter() .map(|mem| { ( mem.member_uin.unwrap_or_default() as i64, if mem.privilege == Some(1) { GroupMemberPermission::Owner } else if mem.privilege == Some(2) { GroupMemberPermission::Administrator } else { GroupMemberPermission::Member }, ) }) .collect()) } // OidbSvc.0x6d8_1 pub fn decode_group_file_list_response(&self, payload: Bytes) -> RQResult { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let resp = pb::oidb::D6d8RspBody::decode(&*pkg.bodybuffer)?; let resp = &resp.file_list_info_rsp.unwrap_or_default(); Ok(GroupFileList { all_file_count: resp.all_file_count(), is_end: resp.is_end(), items: resp .item_list .clone() .into_iter() .map(|f| { if let Some(fi) = f.file_info { let folder_info = f.folder_info.unwrap_or_default(); GroupFileItem { file_info: GroupFileInfo { file_id: fi.file_id().to_string(), bus_id: fi.bus_id(), file_name: fi.file_name().to_string(), sha: format!("{:x}", BytesMut::from(fi.sha())), dead_time: fi.dead_time(), file_size: fi.file_size(), upload_time: fi.upload_time(), uploader_uin: fi.uploader_uin(), uploader_name: fi.uploader_name().to_string(), parent_folder_id: fi.parent_folder_id().to_string(), local_path: fi.local_path().to_string(), modify_time: fi.modify_time(), download_times: fi.download_times(), md5: Bytes::from(fi.md5.unwrap_or_default()), sha3: Bytes::from(fi.sha3.unwrap_or_default()), uploaded_size: fi.uploaded_size.unwrap_or_default(), }, folder_info: GroupFolderInfo { create_time: folder_info.create_time(), create_uin: folder_info.create_uin(), creator_name: folder_info.creator_name.unwrap_or_default(), folder_id: folder_info.folder_id.unwrap_or_default(), folder_name: folder_info.folder_name.unwrap_or_default(), modify_time: folder_info.modify_time.unwrap_or_default(), parent_folder_id: folder_info.parent_folder_id.unwrap_or_default(), total_file_count: folder_info.total_file_count.unwrap_or_default(), }, r#type: f.r#type.unwrap_or_default(), } } else { GroupFileItem::default() } }) .collect(), next_index: resp.next_index(), role: resp.role(), }) } // OidbSvc.0x6d6_2 pub fn decode_group_file_download_response( &self, payload: Bytes, filename: &str, ) -> RQResult { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let resp = pb::oidb::D6d6RspBody::decode(&*pkg.bodybuffer)?; let f_rsp = resp.download_file_rsp.unwrap(); Ok(format!( "http://{}/ftn_handler/{:x}/?fname={}", f_rsp.download_ip(), BytesMut::from(f_rsp.download_url()), filename )) } // OidbSvc.0x6d8_1 pub fn decode_group_file_count_response(&self, payload: Bytes) -> RQResult { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let resp = pb::oidb::D6d8RspBody::decode(&*pkg.bodybuffer)?; if let Some(file_count_rsp) = resp.file_count_rsp { Ok(GroupFileCount { is_full: file_count_rsp.is_full.unwrap_or_default(), all_file_count: file_count_rsp.all_file_count.unwrap_or_default(), limit_count: file_count_rsp.limit_count.unwrap_or_default(), file_too_many: file_count_rsp.file_too_many.unwrap_or_default(), }) } else { Err(crate::RQError::GetFileCountFailed) } } } ================================================ FILE: ricq-core/src/command/oidb_svc/mod.rs ================================================ use std::collections::HashMap; use crate::pb; pub mod builder; pub mod decoder; // 群 @全体 剩余次数 #[derive(Default, Debug)] pub struct GroupAtAllRemainInfo { pub can_at_all: bool, pub remain_at_all_count_for_group: u32, pub remain_at_all_count_for_uin: u32, } pub struct OcrResponse { pub texts: Vec, pub language: String, } // 编辑个人资料 #[derive(Default, Debug)] pub struct ProfileDetailUpdate(pub HashMap>); impl ProfileDetailUpdate { pub fn new() -> Self { Self::default() } pub fn name(&mut self, value: String) { self.0.insert(20002, value.into_bytes()); } pub fn email(&mut self, value: String) { self.0.insert(20011, value.into_bytes()); } pub fn personal_note(&mut self, value: String) { self.0.insert(20019, value.into_bytes()); } pub fn company(&mut self, value: String) { self.0.insert(24008, value.into_bytes()); } pub fn college(&mut self, value: String) { self.0.insert(20021, value.into_bytes()); } } pub enum ShareTarget { Friend(i64), Group(i64), Guild { guild_id: u64, channel_id: u64 }, } impl ShareTarget { pub fn send_type(&self) -> u32 { match self { ShareTarget::Friend { .. } => 0, ShareTarget::Group { .. } => 1, ShareTarget::Guild { .. } => 3, } } } #[derive(Debug, Clone, Default)] pub struct MusicShare { pub title: String, pub brief: String, pub summary: String, pub url: String, pub picture_url: String, pub music_url: String, } #[derive(Debug, Clone)] pub struct MusicVersion { pub app_id: u64, pub app_type: u32, pub platform: u32, pub sdk_version: &'static str, pub package_name: &'static str, pub signature: &'static str, } impl MusicVersion { pub const QQ: MusicVersion = MusicVersion { app_id: 100497308, app_type: 1, platform: 1, sdk_version: "0.0.0", package_name: "com.tencent.qqmusic", signature: "cbd27cd7c861227d013a25b2d10f0799", }; pub const NETEASE: MusicVersion = MusicVersion { app_id: 100495085, app_type: 1, platform: 1, sdk_version: "0.0.0", package_name: "com.netease.cloudmusic", signature: "da6b069da1e2982db3e386233f68d76d", }; pub const MIGU: MusicVersion = MusicVersion { app_id: 1101053067, app_type: 1, platform: 1, sdk_version: "0.0.0", package_name: "cmccwm.mobilemusic", signature: "6cdc72a439cef99a3418d2a78aa28c73", }; pub const KUGOU: MusicVersion = MusicVersion { app_id: 205141, app_type: 1, platform: 1, sdk_version: "0.0.0", package_name: "com.kugou.android", signature: "fe4a24d80fcf253a00676a808f62c2c6", }; pub const KUWO: MusicVersion = MusicVersion { app_id: 100243533, app_type: 1, platform: 1, sdk_version: "0.0.0", package_name: "cn.kuwo.player", signature: "bf9ff4ffb4c558a34ee3fd52c223ebf5", }; } #[derive(Debug, Clone, Default)] pub struct LinkShare { pub title: String, pub summary: Option, /// 从消息列表中看到的文字,默认为 "[分享]" + title pub brief: Option, /// 预览图网址, 默认为 QQ 浏览器图标,似乎对域名有限制 pub picture_url: Option, pub url: String, } ================================================ FILE: ricq-core/src/command/online_push/builder.rs ================================================ use std::collections::HashMap; use bytes::Bytes; use jcers::JcePut; use crate::command::common::pack_uni_request_data; use crate::jce; use crate::protocol::packet::Packet; impl super::super::super::Engine { // OnlinePush.RespPush pub fn build_delete_online_push_packet( &self, uin: i64, svrip: i32, push_token: Bytes, seq: u16, del_msg: Vec, ) -> Packet { let req = jce::SvcRespPushMsg { uin, svrip, push_token, del_infos: del_msg .into_iter() .map(|m| jce::DelMsgInfo { from_uin: m.from_uin, msg_time: m.msg_time, msg_seq: m.msg_seq, msg_cookies: m.msg_cookies, ..Default::default() }) .collect(), ..Default::default() }; let b = pack_uni_request_data(&req.freeze()); let buf = jce::RequestDataVersion3 { map: HashMap::from([("resp".to_string(), b)]), }; let pkt = jce::RequestPacket { i_version: 3, i_request_id: seq as i32, s_servant_name: "OnlinePush".to_string(), s_func_name: "SvcRespPushMsg".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("OnlinePush.RespPush", pkt.freeze()) } pub fn build_sid_ticket_expired_response(&self, seq: i32) -> Packet { self.uni_packet_with_seq(seq, "OnlinePush.SidTicketExpired", Bytes::new()) } } ================================================ FILE: ricq-core/src/command/online_push/decoder.rs ================================================ use bytes::{Buf, Bytes}; use jcers::Jce; use super::*; use crate::common::group_uin2code; use crate::structs::{GroupDisband, GroupLeave, GroupMemberPermission, MemberPermissionChange}; use crate::{jce, pb, RQError, RQResult}; use prost::Message; impl super::super::super::Engine { // 解析群消息分片 长消息需要合并 // OnlinePush.PbPushGroupMsg pub fn decode_group_message_packet(&self, payload: Bytes) -> RQResult { let message = pb::msg::PushMessagePacket::decode(&*payload)?; (|| { let msg = message.message.ok_or("message")?; let head = msg.head.ok_or("head")?; let body = msg.body.ok_or("body")?; let content = msg.content.ok_or("content")?; let rich_text = body.rich_text.ok_or("rich_text")?; let group_info = head.group_info.ok_or("group_info")?; Ok(GroupMessagePart { seq: head.msg_seq.ok_or("msg_seq")?, rand: rich_text.attr.ok_or("attr")?.random.ok_or("attr.random")?, group_code: group_info.group_code.ok_or("group_info.group_code")?, group_name: String::from_utf8_lossy( &group_info.group_name.ok_or("group_info.group_name")?, ) .into_owned(), group_card: String::from_utf8_lossy( &group_info.group_card.ok_or("group_info.group_card")?, ) .into_owned(), from_uin: head.from_uin.ok_or("from_uin")?, elems: rich_text.elems, time: head.msg_time.ok_or("msg_time")?, pkg_num: content.pkg_num.ok_or("pkg_num")?, pkg_index: content.pkg_index.ok_or("pkg_index")?, div_seq: content.div_seq.ok_or("div_seq")?, ptt: rich_text.ptt, }) })() .map_err(|e: &'static str| RQError::Decode(format!("{e} is none"))) } // OnlinePush.ReqPush pub fn decode_online_push_req_packet(&self, mut payload: Bytes) -> RQResult { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut request.s_buffer)?; let mut req = data .map .remove("req") .ok_or_else(|| RQError::Decode("req is none".into()))?; let mut msg = req .remove("OnlinePushPack.SvcReqPushMsg") .ok_or_else(|| RQError::Decode("OnlinePushPack.SvcReqPushMsg is none".into()))?; msg.advance(1); let mut jr = Jce::new(&mut msg); let uin: i64 = jr.get_by_tag(0)?; let msg_infos: Vec = jr.get_by_tag(2)?; Ok(ReqPush { uin, msg_infos }) } pub fn decode_online_push_trans_packet(&self, payload: Bytes) -> RQResult { let info = pb::msg::TransMsgInfo::decode(&*payload)?; let msg_seq = info.msg_seq.unwrap_or_default(); let msg_uid = info.msg_uid.unwrap_or_default(); let msg_time = info.msg_time.unwrap_or_default(); let group_uin = info.from_uin.ok_or_else(|| { RQError::Decode("decode_online_push_trans_packet from_uin is 0".to_string()) })?; let mut data = Bytes::from( info.msg_data .ok_or_else(|| RQError::Decode("msg_data is none".into()))?, ); // 去重暂时不做 match info.msg_type { Some(34) => { data.get_i32(); data.get_u8(); let target = data.get_u32() as i64; let typ = data.get_u8() as i32; let operator = data.get_u32() as i64; match typ { 0x01 | 0x81 => { return Ok(OnlinePushTrans { msg_seq, msg_uid, msg_time, info: PushTransInfo::GroupDisband(GroupDisband { group_code: group_uin2code(group_uin), operator_uin: operator, }), }); } 0x02 | 0x82 => { return Ok(OnlinePushTrans { msg_seq, msg_uid, msg_time, info: PushTransInfo::MemberLeave(GroupLeave { group_code: group_uin2code(group_uin), member_uin: target, operator_uin: None, }), }); } 0x03 | 0x83 => { return Ok(OnlinePushTrans { msg_seq, msg_uid, msg_time, info: PushTransInfo::MemberLeave(GroupLeave { group_code: group_uin2code(group_uin), member_uin: target, operator_uin: Some(operator), }), }); } _ => {} } } Some(44) => { data.advance(5); let var4 = data.get_u8() as i32; let mut var5: i64 = 0; let target = data.get_u32() as i64; if var4 != 0 && var4 != 1 { var5 = data.get_u32() as i64; } if var5 == 0 && data.len() == 1 { let new_permission = if data.get_u8() == 1 { GroupMemberPermission::Administrator } else { GroupMemberPermission::Member }; return Ok(OnlinePushTrans { msg_seq, msg_uid, msg_time, info: PushTransInfo::MemberPermissionChange(MemberPermissionChange { group_code: group_uin2code(group_uin), member_uin: target, new_permission, }), }); } } _ => {} } Err(RQError::Decode(format!( "decode_online_push_trans_packet unknown error: {:?}", info.msg_type ))) } // OnlinePush.PbC2CMsgSync pub fn decode_c2c_sync_packet(&self, payload: Bytes) -> RQResult { pb::msg::PbPushMsg::decode(&*payload).map_err(Into::into) } } ================================================ FILE: ricq-core/src/command/online_push/mod.rs ================================================ use crate::structs::{GroupDisband, GroupLeave, MemberPermissionChange}; use crate::{jce, pb}; pub mod builder; pub mod decoder; #[derive(Debug, Default)] pub struct ReqPush { pub uin: i64, pub msg_infos: Vec, } #[derive(Debug, Clone)] pub enum PushTransInfo { MemberLeave(GroupLeave), MemberPermissionChange(MemberPermissionChange), GroupDisband(GroupDisband), // TODO 转让 } #[derive(Debug, Clone)] pub struct OnlinePushTrans { pub msg_seq: i32, pub msg_uid: i64, pub msg_time: i32, pub info: PushTransInfo, } #[derive(Debug, Default, Clone, PartialEq)] pub struct GroupMessagePart { pub seq: i32, pub rand: i32, pub group_code: i64, pub group_name: String, pub group_card: String, pub from_uin: i64, pub elems: Vec, pub time: i32, // 语音消息 pub ptt: Option, // 整个message有多少个part,大于elem.len()时,应等待下一个片段到达后合并 pub pkg_num: i32, // 分片的第几段 pub pkg_index: i32, // 分片id,相同id的应该合并,且根据pkg_index排序 pub div_seq: i32, } ================================================ FILE: ricq-core/src/command/pb_message_svc/builder.rs ================================================ use crate::command::common::PbToBytes; use crate::pb; use crate::protocol::packet::Packet; impl super::super::super::Engine { // PbMessageSvc.PbMsgReadedReport pub fn build_group_msg_readed_packet(&self, group_code: i64, msg_seq: i32) -> Packet { let req = pb::msg::PbMsgReadedReportReq { grp_read_report: vec![pb::msg::PbGroupReadedReportReq { group_code: Some(group_code as u64), last_read_seq: Some(msg_seq as u64), }], ..Default::default() }; self.uni_packet("PbMessageSvc.PbMsgReadedReport", req.to_bytes()) } // PbMessageSvc.PbMsgReadedReport pub fn build_friend_msg_readed_packet(&self, uin: i64, time: i64) -> Packet { let transport = &self.transport; let req = pb::msg::PbMsgReadedReportReq { c2_c_read_report: Some(pb::msg::PbC2cReadedReportReq { pair_info: vec![pb::msg::UinPairReadInfo { peer_uin: Some(uin as u64), last_read_time: Some(time as u32), ..Default::default() }], sync_cookie: Some(transport.sig.sync_cookie.to_vec()), }), ..Default::default() }; self.uni_packet("PbMessageSvc.PbMsgReadedReport", req.to_bytes()) } } ================================================ FILE: ricq-core/src/command/pb_message_svc/mod.rs ================================================ pub mod builder; ================================================ FILE: ricq-core/src/command/profile_service/builder.rs ================================================ use jcers::JcePut; use crate::command::common::PbToBytes; use crate::pb; use crate::protocol::packet::Packet; impl super::super::super::Engine { // ProfileService.Pb.ReqSystemMsgNew.Group pub fn build_system_msg_new_group_packet(&self, suspicious: bool) -> Packet { let req = pb::structmsg::ReqSystemMsgNew { msg_num: 100, version: 1000, checktype: 3, flag: Some(pb::structmsg::FlagInfo { grp_msg_kick_admin: 1, grp_msg_hidden_grp: 1, grp_msg_wording_down: 1, grp_msg_get_official_account: 1, grp_msg_get_pay_in_group: 1, frd_msg_discuss2_many_chat: 1, grp_msg_not_allow_join_grp_invite_not_frd: 1, frd_msg_need_waiting_msg: 1, frd_msg_uint32_need_all_unread_msg: 1, grp_msg_need_auto_admin_wording: 1, grp_msg_get_transfer_group_msg_flag: 1, grp_msg_get_quit_pay_group_msg_flag: 1, grp_msg_support_invite_auto_join: 1, grp_msg_mask_invite_auto_join: 1, grp_msg_get_disbanded_by_admin: 1, grp_msg_get_c2c_invite_join_group: 1, ..Default::default() }), friend_msg_type_flag: 1, req_msg_type: if suspicious { 2 } else { 1 }, ..Default::default() }; let payload = req.to_bytes(); self.uni_packet("ProfileService.Pb.ReqSystemMsgNew.Group", payload) } // ProfileService.Pb.ReqSystemMsgNew.Friend pub fn build_system_msg_new_friend_packet(&self) -> Packet { let req = pb::structmsg::ReqSystemMsgNew { msg_num: 20, version: 1000, checktype: 2, flag: Some(pb::structmsg::FlagInfo { frd_msg_discuss2_many_chat: 1, frd_msg_get_busi_card: 1, frd_msg_need_waiting_msg: 1, frd_msg_uint32_need_all_unread_msg: 1, grp_msg_mask_invite_auto_join: 1, ..Default::default() }), friend_msg_type_flag: 1, ..Default::default() }; let payload = req.to_bytes(); self.uni_packet("ProfileService.Pb.ReqSystemMsgNew.Friend", payload) } // ProfileService.Pb.ReqSystemMsgAction.Group #[allow(clippy::too_many_arguments)] pub fn build_system_msg_group_action_packet( &self, msg_seq: i64, req_uin: i64, group_code: i64, msg_type: i32, is_invite: bool, accept: bool, block: bool, reason: String, ) -> Packet { let req = pb::structmsg::ReqSystemMsgAction { msg_type, msg_seq, req_uin, sub_type: 1, src_id: 3, sub_src_id: if is_invite { 10016 } else { 31 }, group_msg_type: if is_invite { 2 } else { 1 }, action_info: Some(pb::structmsg::SystemMsgActionInfo { r#type: if accept { 11 } else { 12 }, group_code, blacklist: block, msg: reason, sig: vec![], ..Default::default() }), language: 1000, }; let payload = req.to_bytes(); self.uni_packet("ProfileService.Pb.ReqSystemMsgAction.Group", payload) } // ProfileService.Pb.ReqSystemMsgAction.Friend pub fn build_system_msg_friend_action_packet( &self, req_id: i64, req_uin: i64, accept: bool, ) -> Packet { let req = pb::structmsg::ReqSystemMsgAction { msg_type: 1, msg_seq: req_id, req_uin, sub_type: 1, src_id: 6, sub_src_id: 7, action_info: Some(pb::structmsg::SystemMsgActionInfo { r#type: if accept { 2 } else { 3 }, blacklist: false, add_frd_sn_info: Some(pb::structmsg::AddFrdSnInfo::default()), ..Default::default() }), ..Default::default() }; let payload = req.to_bytes(); self.uni_packet("ProfileService.Pb.ReqSystemMsgAction.Friend", payload) } // ProfileService.GroupMngReq pub fn build_quit_group_packet(&self, group_code: i64) -> Packet { let mut jce_mut = jcers::JceMut::new(); jce_mut.put_i32(2, 0); jce_mut.put_i64(self.uin(), 1); jce_mut.put_bytes( bytes::Bytes::from({ let mut v = Vec::with_capacity(8); v.extend((self.uin() as u32).to_be_bytes()); v.extend(group_code.to_be_bytes()); v }), 2, ); let buf = crate::jce::RequestDataVersion3 { map: [( "GroupMngReq".to_owned(), crate::command::common::pack_uni_request_data(&jce_mut.freeze()), )] .into(), }; let pkt = crate::jce::RequestPacket { i_version: 3, i_request_id: self.next_packet_seq(), s_servant_name: "KQQ.ProfileService.ProfileServantObj".to_owned(), s_func_name: "GroupMngReq".to_owned(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("ProfileService.GroupMngReq", pkt.freeze()) } pub fn build_get_rich_sig_request_packet(&self, user_ids: Vec) -> Packet { let payload = crate::jce::GetRichSigReq { req_rich_infos: user_ids .into_iter() .map(|id| crate::jce::ReqRichInfo { uin: id, dw_time: 0, }) .collect(), check_update: false, show_date_sig: false, get_large_tlv: true, }; let buf = crate::jce::RequestDataVersion3 { map: [( "GetRichSigReq".to_owned(), crate::command::common::pack_uni_request_data(&payload.freeze()), )] .into(), }; let pkt = crate::jce::RequestPacket { i_version: 3, i_request_id: self.next_packet_seq(), s_servant_name: "KQQ.ProfileService.ProfileServantObj".to_owned(), s_func_name: "GetRichSig".to_owned(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("ProfileService.GetRichSig", pkt.freeze()) } } ================================================ FILE: ricq-core/src/command/profile_service/decoder.rs ================================================ use std::collections::HashMap; use bytes::{Buf, Bytes}; use prost::Message; use crate::command::profile_service::*; use crate::{jce, RQResult}; use crate::{pb, RQError}; impl super::super::super::Engine { // ProfileService.Pb.ReqSystemMsgNew.Group pub fn decode_system_msg_group_packet(&self, payload: Bytes) -> RQResult { let rsp = pb::structmsg::RspSystemMsgNew::decode(&*payload); let mut join_group_requests = Vec::new(); let mut self_invited = Vec::new(); match rsp { Ok(rsp) => { for st in rsp .groupmsgs .into_iter() .filter_map(|st| st.msg.map(|m| (st.msg_seq, st.msg_time, st.req_uin, m))) { let msg_seq = st.0; let msg_time = st.1; let req_uin = st.2; let msg = st.3; match msg.sub_type { // 1 进群申请 1 => match msg.group_msg_type { 1 => join_group_requests.push(JoinGroupRequest { msg_seq, msg_time, message: msg.msg_additional, req_uin, req_nick: msg.req_uin_nick, group_code: msg.group_code, group_name: msg.group_name, actor_uin: msg.actor_uin, suspicious: !msg.warning_tips.is_empty(), ..Default::default() }), 2 => self_invited.push(SelfInvited { msg_seq, msg_time, invitor_uin: msg.action_uin, invitor_nick: msg.action_uin_nick, group_code: msg.group_code, group_name: msg.group_name, actor_uin: msg.actor_uin, actor_nick: msg.actor_uin_nick, }), 22 => join_group_requests.push(JoinGroupRequest { msg_seq, msg_time, message: msg.msg_additional, req_uin, req_nick: msg.req_uin_nick, group_code: msg.group_code, group_name: msg.group_name, actor_uin: msg.actor_uin, suspicious: !msg.warning_tips.is_empty(), invitor_uin: Some(msg.action_uin), invitor_nick: Some(msg.action_uin_qq_nick), }), _ => {} }, // 2 被邀请,不需要处理 2 => {} // ? 3 => {} // 自身状态变更(管理员/加群退群) 5 => {} _ => {} } } Ok(GroupSystemMessages { self_invited, join_group_requests, }) } Err(_) => Err(RQError::Decode( "failed to decode RspSystemMsgNew".to_string(), )), } } // ProfileService.Pb.ReqSystemMsgNew.Friend pub fn decode_system_msg_friend_packet( &self, payload: Bytes, ) -> RQResult { let rsp = pb::structmsg::RspSystemMsgNew::decode(&*payload) .map_err(|_| RQError::Decode("RspSystemMsgNew".into()))?; Ok(FriendSystemMessages { requests: rsp .friendmsgs .into_iter() .map(|m| { let msg = m.msg.as_ref(); NewFriendRequest { msg_seq: m.msg_seq, message: msg .map(|msg| msg.msg_additional.to_owned()) .unwrap_or_default(), req_uin: m.req_uin, req_nick: msg .map(|msg| msg.req_uin_nick.to_owned()) .unwrap_or_default(), } }) .collect(), }) } pub fn decode_get_rich_sig_response_packet( &self, mut payload: Bytes, ) -> RQResult> { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut request.s_buffer)?; let mut a = data .map .remove("GetRichSigRes") .ok_or_else(|| RQError::Decode("missing GetRichSigRes".into()))?; let mut b = a .remove("KQQ.GetRichSigRes") .ok_or_else(|| RQError::Decode("missing KQQ.GetRichSigRes".into()))?; b.advance(1); let resp: jce::GetRichSigRes = jcers::from_buf(&mut b)?; Ok(resp .sig_infos .into_iter() .map(|mut info| RichSigInfo { status: info.status, uin: info.uin, dw_time: info.dw_time, infos: { let mut infos = HashMap::new(); while info.sig_info.remaining() > 2 { let tag = info.sig_info.get_u8(); let len = info.sig_info.get_u8(); if info.sig_info.len() < len as usize { break; } let value = info.sig_info.copy_to_bytes(len as usize); infos.insert(tag, value); } infos }, }) .collect()) } } ================================================ FILE: ricq-core/src/command/profile_service/mod.rs ================================================ use std::collections::HashMap; use bytes::Bytes; pub mod builder; pub mod decoder; #[derive(Debug, Default, Clone)] pub struct GroupSystemMessages { pub self_invited: Vec, pub join_group_requests: Vec, } // 自己被邀请 #[derive(Debug, Default, Clone)] pub struct SelfInvited { pub msg_seq: i64, pub msg_time: i64, pub invitor_uin: i64, pub invitor_nick: String, pub group_code: i64, pub group_name: String, pub actor_uin: i64, pub actor_nick: String, } // 用户申请进群 #[derive(Debug, Default, Clone)] pub struct JoinGroupRequest { pub msg_seq: i64, pub msg_time: i64, pub message: String, pub req_uin: i64, pub req_nick: String, pub group_code: i64, pub group_name: String, pub actor_uin: i64, pub suspicious: bool, pub invitor_uin: Option, pub invitor_nick: Option, } #[derive(Debug, Default, Clone)] pub struct FriendSystemMessages { pub requests: Vec, } #[derive(Debug, Default, Clone)] pub struct NewFriendRequest { pub msg_seq: i64, pub message: String, pub req_uin: i64, pub req_nick: String, } #[derive(Debug, Default, Clone)] pub struct RichSigInfo { pub status: u8, pub uin: i64, pub dw_time: i64, /// 1-actionText /// 2-dataText /// 4-locationText /// 130-经纬度 /// 129-actionId+dataId /// 猜测A:[1,128) 范围内,且不是1,2,4,字符串拼一起作为签名 /// 猜测B:3是签名 pub infos: HashMap, } impl RichSigInfo { pub fn get_signature(&self) -> String { String::from_utf8_lossy(&self.infos.get(&3).cloned().unwrap_or_default()).into_owned() } } ================================================ FILE: ricq-core/src/command/ptt_center_svr/builder.rs ================================================ use crate::command::common::PbToBytes; use crate::hex::encode_hex; use crate::pb; use crate::protocol::packet::Packet; impl super::super::super::Engine { pub fn build_group_video_store_packet( &self, short_video_up_req: pb::short_video::ShortVideoUploadReq, ) -> Packet { let seq = self.next_seq(); let req = pb::short_video::ShortVideoReqBody { seq: seq as i32, cmd: 300, ptt_short_video_upload_req: Some(short_video_up_req), extension_req: vec![pb::short_video::ShortVideoExtensionReq { sub_busi_type: 0, user_cnt: 1, }], ..Default::default() }; self.uni_packet_with_seq( seq as i32, "PttCenterSvr.GroupShortVideoUpReq", req.to_bytes(), ) } pub fn build_short_video_up_req( &self, to_uin: i64, file_md5: Vec, thumb_file_md5: Vec, file_size: i64, thumb_file_size: i64, ) -> pb::short_video::ShortVideoUploadReq { pb::short_video::ShortVideoUploadReq { from_uin: self.uin(), to_uin, chat_type: 1, // 私聊 0 client_type: 2, info: Some(pb::short_video::ShortVideoFileInfo { file_name: format!("{}.mp4", encode_hex(&file_md5)), file_md5, thumb_file_md5, file_size, file_res_length: 1280, file_res_width: 720, file_format: 3, file_time: 120, // 视频时长 秒 thumb_file_size, }), group_code: to_uin, agent_type: 0, business_type: 0, support_large_size: 1, } } // PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_DOWNLOAD-1200 pub fn build_c2c_ptt_down_req(&self, sender_uin: i64, file_uuid: Vec) -> Packet { let req = pb::cmd0x346::C346ReqBody { client_type: 104, cmd: 1200, business_id: 17, // 3? apply_download_req: Some(pb::cmd0x346::ApplyDownloadReq { uin: sender_uin, uuid: file_uuid, need_https_url: 1, ..Default::default() }), ..Default::default() }; self.uni_packet( "PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_DOWNLOAD-1200", req.to_bytes(), ) } } ================================================ FILE: ricq-core/src/command/ptt_center_svr/decoder.rs ================================================ use bytes::Bytes; use crate::pb::short_video::{ShortVideoRspBody, ShortVideoUploadRsp}; use crate::{pb, RQError, RQResult}; use prost::Message; impl super::super::super::Engine { pub fn decode_group_video_store_response( &self, payload: Bytes, ) -> RQResult { ShortVideoRspBody::decode(&*payload)? .ptt_short_video_upload_rsp .ok_or(RQError::EmptyField("ptt_short_video_upload_rsp")) } // PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_DOWNLOAD-1200 pub fn decode_c2c_ptt_down(&self, payload: Bytes) -> RQResult { pb::cmd0x346::C346RspBody::decode(&*payload)? .apply_download_rsp .ok_or(RQError::EmptyField("apply_download_rsp"))? .download_info .ok_or(RQError::EmptyField("download_info")) .map(|info| info.download_url) } } ================================================ FILE: ricq-core/src/command/ptt_center_svr/mod.rs ================================================ pub mod builder; pub mod decoder; ================================================ FILE: ricq-core/src/command/ptt_store/builder.rs ================================================ use bytes::Bytes; use crate::command::common::PbToBytes; use crate::hex::encode_hex; use crate::pb; use crate::protocol::packet::Packet; impl super::super::super::Engine { pub fn build_group_try_up_ptt_req( &self, group_code: i64, file_md5: Vec, file_size: u64, codec: u32, voice_length: u32, ) -> Bytes { let req = pb::cmd0x388::D388ReqBody { net_type: Some(3), subcmd: Some(3), tryup_ptt_req: vec![pb::cmd0x388::TryUpPttReq { group_code: Some(group_code as u64), src_uin: Some(self.uin() as u64), file_md5: Some(file_md5.clone()), file_size: Some(file_size), file_name: Some(file_md5), src_term: Some(5), platform_type: Some(9), bu_type: Some(4), build_ver: Some(self.transport.version.build_ver.into()), inner_ip: Some(0), // TODO ? voice_length: Some(voice_length), new_up_chan: Some(true), codec: Some(codec), // 2021/1/26 因为 #577 修改为 resource.voiceCodec voice_type: Some(1), ..Default::default() }], ..Default::default() }; req.to_bytes() } pub fn build_friend_try_up_ptt_req( &self, target: i64, file_md5: Vec, file_size: i64, voice_length: i32, ) -> Bytes { let req = pb::cmd0x346::C346ReqBody { cmd: 500, seq: self.next_seq() as i32, business_id: 17, client_type: 104, apply_upload_req: Some(pb::cmd0x346::ApplyUploadReq { sender_uin: self.uin(), recver_uin: target, file_type: 2, file_size, file_name: encode_hex(&file_md5), bytes_10m_md5: file_md5, ..Default::default() }), extension_req: Some(pb::cmd0x346::ExtensionReq { id: 3, ptt_format: 1, net_type: 3, voice_type: 2, ptt_time: voice_length, ..Default::default() }), ..Default::default() }; req.to_bytes() } pub fn build_group_ptt_down_req(&self, group_code: i64, file_md5: Vec) -> Packet { let req = pb::cmd0x388::D388ReqBody { net_type: Some(3), subcmd: Some(4), getptt_url_req: vec![pb::cmd0x388::GetPttUrlReq { group_code: Some(group_code as u64), dst_uin: Some(self.uin() as u64), fileid: None, file_md5: Some(file_md5), req_term: Some(5), req_platform_type: Some(9), inner_ip: Some(0), bu_type: Some(4), // 3? build_ver: Some(self.transport.version.build_ver.into()), codec: Some(0), // 11=file_key, 14=2, 15=1 ? ..Default::default() }], ..Default::default() }; self.uni_packet("PttStore.GroupPttDown", req.to_bytes()) } } ================================================ FILE: ricq-core/src/command/ptt_store/decoder.rs ================================================ use bytes::Bytes; use crate::RQResult; use crate::{pb, RQError}; use prost::Message; impl super::super::super::Engine { pub fn decode_group_try_up_ptt_resp(&self, payload: Bytes) -> RQResult> { let mut rsp = pb::cmd0x388::D388RspBody::decode(&*payload)?; let ptt = rsp .tryup_ptt_rsp .pop() .ok_or(RQError::EmptyField("tryup_ptt_rsp"))?; ptt.file_key.ok_or(RQError::EmptyField("file_key")) } pub fn decode_friend_try_up_ptt_resp(&self, payload: Bytes) -> RQResult> { pb::cmd0x346::C346RspBody::decode(&*payload)? .apply_upload_rsp .map(|r| r.uuid) .ok_or(RQError::EmptyField("apply_upload_rsp")) } pub fn decode_group_ptt_down(&self, payload: Bytes) -> RQResult { let mut rsp = pb::cmd0x388::D388RspBody::decode(&*payload)?; let ptt = rsp .getptt_url_rsp .pop() .ok_or(RQError::EmptyField("getptt_url_rsp"))?; Ok(format!( "http://{}{}", ptt.domain.ok_or(RQError::EmptyField("ptt_domain"))?, String::from_utf8_lossy(&ptt.down_para.ok_or(RQError::EmptyField("ptt_down_para"))?) )) } } ================================================ FILE: ricq-core/src/command/ptt_store/mod.rs ================================================ pub mod builder; pub mod decoder; ================================================ FILE: ricq-core/src/command/reg_prxy_svc/builder.rs ================================================ use std::collections::HashMap; use std::time::UNIX_EPOCH; use bytes::{BufMut, BytesMut}; use jcers::JcePut; use crate::command::common::pack_uni_request_data; use crate::command::common::PbToBytes; use crate::protocol::packet::Packet; use crate::{jce, pb}; impl super::super::super::Engine { // RegPrxySvc.getOffMsg pub fn build_get_offline_msg_request_packet(&self, last_message_time: i64) -> Packet { let transport = &self.transport; let reg_req = jce::SvcReqRegisterNew { request_optional: 0x101C2 | 32, c2c_msg: jce::SvcReqGetMsgV2 { uin: self.uin(), date_time: match last_message_time { 0 => 1, _ => last_message_time as i32, }, recive_pic: 1, ability: 15, channel: 4, inst: 1, channel_ex: 1, sync_cookie: transport.sig.sync_cookie.to_owned(), sync_flag: 0, ramble_flag: 0, general_abi: 1, pub_account_cookie: transport.sig.pub_account_cookie.to_owned(), }, group_msg: jce::SvcReqPullGroupMsgSeq { verify_type: 0, filter: 1, ..Default::default() }, end_seq: UNIX_EPOCH.elapsed().unwrap().as_secs() as i64, ..Default::default() }; let flag = 0; // flag := msg.SyncFlag_START let msg_req = pb::msg::GetMessageRequest { sync_flag: Some(flag), sync_cookie: Some(transport.sig.sync_cookie.to_vec()), ramble_flag: Some(0), context_flag: Some(1), online_sync_flag: Some(0), latest_ramble_number: Some(20), other_ramble_number: Some(3), ..Default::default() } .to_bytes(); let mut buf = BytesMut::new(); buf.put_slice(&[0, 0, 0, 0]); buf.put_slice(&msg_req); let buf = buf.freeze(); let mut req = jcers::JceMut::new(); req.put_bytes(buf, 0); let buf = jce::RequestDataVersion3 { map: HashMap::from([ ("req_PbOffMsg".to_string(), req.freeze()), ( "req_OffMsg".to_string(), pack_uni_request_data(®_req.freeze()), ), ]), }; let pkt = jce::RequestPacket { i_version: 3, s_servant_name: "RegPrxySvc".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("RegPrxySvc.getOffMsg", pkt.freeze()) } // RegPrxySvc.infoSync pub fn build_sync_msg_request_packet(&self, last_message_time: i64) -> Packet { let transport = &self.transport; let oidb_req = pb::oidb::D769RspBody { config_list: vec![ pb::oidb::D769ConfigSeq { r#type: Some(46), version: Some(0), }, pb::oidb::D769ConfigSeq { r#type: Some(283), version: Some(0), }, ], ..Default::default() } .to_bytes(); let reg_req = jce::SvcReqRegisterNew { request_optional: 128 | 64 | 256 | 2 | 8192 | 16384 | 65536, dis_group_msg_filter: 1, c2c_msg: jce::SvcReqGetMsgV2 { uin: self.uin(), date_time: match last_message_time { 0 => 1, _ => last_message_time as i32, }, recive_pic: 1, ability: 15, channel: 4, inst: 1, channel_ex: 1, sync_cookie: transport.sig.sync_cookie.to_owned(), sync_flag: 0, // START ramble_flag: 0, general_abi: 1, pub_account_cookie: transport.sig.pub_account_cookie.to_owned(), }, group_mask: 2, end_seq: rand::random::() as i64, _0769_body: oidb_req, ..Default::default() }; let flag = 0; // flag := msg.SyncFlag_START let mut msg_req = pb::msg::GetMessageRequest { sync_flag: Some(flag), sync_cookie: Some(transport.sig.sync_cookie.to_vec()), ramble_flag: Some(0), context_flag: Some(1), online_sync_flag: Some(0), latest_ramble_number: Some(20), other_ramble_number: Some(3), msg_req_type: Some(1), ..Default::default() }; let off_msg = msg_req.to_bytes(); msg_req.msg_req_type = Some(2); msg_req.sync_cookie = None; msg_req.pubaccount_cookie = Some(transport.sig.pub_account_cookie.to_vec()); let pub_msg = msg_req.to_bytes(); let buf = jce::RequestDataVersion3 { map: HashMap::from([ ("req_PbOffMsg".to_string(), { let mut w = jcers::JceMut::new(); w.put_bytes( { let mut b = BytesMut::new(); b.put_slice(&[0; 4]); b.put_slice(&off_msg); b.freeze() }, 0, ); w.freeze() }), ("req_PbPubMsg".to_string(), { let mut w = jcers::JceMut::new(); w.put_bytes( { let mut b = BytesMut::new(); b.put_slice(&[0; 4]); b.put_slice(&pub_msg); b.freeze() }, 0, ); w.freeze() }), ( "req_OffMsg".to_string(), pack_uni_request_data(®_req.freeze()), ), ]), }; let pkt = jce::RequestPacket { i_version: 3, s_servant_name: "RegPrxySvc".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("RegPrxySvc.infoSync", pkt.freeze()) } } ================================================ FILE: ricq-core/src/command/reg_prxy_svc/decoder.rs ================================================ use bytes::{Buf, Bytes}; use crate::structs::OtherClientInfo; use crate::{jce, RQError, RQResult}; impl super::super::super::Engine { // RegPrxySvc.PushParam pub fn decode_push_param_packet(&self, payload: &[u8]) -> RQResult> { let mut payload = Bytes::from(payload.to_owned()); let mut request: jce::RequestPacket = jcers::from_buf(&mut payload).map_err(RQError::from)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut request.s_buffer).map_err(RQError::from)?; let mut req = data .map .remove("SvcRespParam") .ok_or_else(|| RQError::Decode("SvcRespParam is none".to_string()))?; let mut reader = req .remove("RegisterProxySvcPack.SvcRespParam") .ok_or_else(|| { RQError::Decode("RegisterProxySvcPack.SvcRespParam is none".to_string()) })?; reader.advance(1); let rsp: jce::SvcRespParam = jcers::from_buf(&mut reader).map_err(RQError::from)?; Ok(rsp .online_infos .iter() .map(|i| OtherClientInfo { app_id: i.instance_id as i64, instance_id: i.instance_id, sub_platform: String::from_utf8_lossy(&i.sub_platform).into_owned(), device_kind: match i.u_client_type { 65793 => "Windows".to_string(), 65805 | 68104 => "aPad".to_string(), 66818 | 66831 | 81154 => "Mac".to_string(), 68361 | 72194 => "iPad".to_string(), 75023 | 78082 | 78096 => "Watch".to_string(), 77313 => "Windows TIM".to_string(), _ => String::from_utf8_lossy(&i.sub_platform).into_owned(), }, }) .collect()) } } ================================================ FILE: ricq-core/src/command/reg_prxy_svc/mod.rs ================================================ pub mod builder; pub mod decoder; ================================================ FILE: ricq-core/src/command/signature/builder.rs ================================================ use crate::command::common::PbToBytes; use crate::pb::sig_act; use crate::protocol::packet::Packet; use std::time::UNIX_EPOCH; impl super::super::super::Engine { pub fn build_update_signature_packet(&self, signature: String) -> Packet { let req = sig_act::ReqBody { cmd: Some(2), seq: Some(UNIX_EPOCH.elapsed().unwrap().as_millis() as u64), plf: Some(sig_act::Platform { platform: Some(109), osver: Some(self.transport.device.version.release.to_owned()), mqqver: Some(self.transport.version.sort_version_name.into()), }), auth_req: Some(sig_act::SigauthReq { uin_disable: Some(self.uin() as u64), itemid: Some(0), len: Some(signature.len() as i32 + 27), data: Some({ let mut buf = vec![0x3, signature.as_bytes().len() as u8 + 1, 0x20]; buf.extend(signature.into_bytes()); buf.extend([ 0x91, 0x04, 0x00, 0x00, 0x00, 0x00, 0x92, 0x04, 0x00, 0x00, 0x00, 0x00, 0xA2, 0x04, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x04, 0x00, 0x00, 0x00, 0x00, ]); buf }), fontid: Some(0), }), source: Some(1), ..Default::default() }; self.uni_packet("Signature.auth", req.to_bytes()) } } ================================================ FILE: ricq-core/src/command/signature/mod.rs ================================================ pub mod builder; ================================================ FILE: ricq-core/src/command/stat_svc/builder.rs ================================================ use std::collections::HashMap; use std::time::UNIX_EPOCH; use bytes::{BufMut, Bytes, BytesMut}; use jcers::JcePut; use crate::command::common::pack_uni_request_data; use crate::command::common::PbToBytes; use crate::jce; use crate::protocol::packet::*; use crate::structs::CustomOnlineStatus; impl super::super::super::Engine { // StatSvc.SetStatusFromClient pub fn build_set_online_status_packet( &self, online_status: i32, ext_online_status: i64, custom_status: Option, ) -> Packet { let transport = &self.transport; let svc = jce::SvcReqRegister { uin: self.uin(), bid: 1 | 2 | 4, conn_type: 0, status: online_status, kick_pc: 0, kick_weak: 0, ios_version: transport.device.version.sdk as i64, net_type: 1, // 0-移动网络 1-wifi reg_type: 0, guid: transport.sig.guid.to_owned(), is_set_status: 1, locale_id: 2052, dev_name: transport.device.model.to_owned(), dev_type: transport.device.model.to_owned(), os_ver: transport.device.version.release.to_owned(), open_push: 1, large_seq: 1551, vendor_name: transport.device.vendor_name.to_owned(), vendor_os_name: transport.device.vendor_os_name.to_owned(), ext_online_status, timestamp: UNIX_EPOCH.elapsed().unwrap().as_secs() as i64, custom_status: custom_status .map(|custom_status| { crate::pb::online_status::CustomStatus { face_index: Some(custom_status.face_index), wording: Some(custom_status.wording), face_type: Some(1), } .to_bytes() }) .unwrap_or_default(), ..Default::default() }; let pkt = self.svc_req_register_pkt(svc); self.uni_packet("StatSvc.SetStatusFromClient", pkt.freeze()) } // StatSvc.register pub fn build_client_register_packet(&self) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let svc = jce::SvcReqRegister { uin: self.uin(), bid: 1 | 2 | 4, conn_type: 0, status: 11, kick_pc: 0, kick_weak: 0, ios_version: transport.device.version.sdk as i64, net_type: 1, // 0-移动网络 1-wifi reg_type: 0, guid: transport.sig.guid.to_owned(), is_set_status: 0, locale_id: 2052, dev_name: transport.device.model.to_owned(), dev_type: transport.device.model.to_owned(), os_ver: transport.device.version.release.to_owned(), open_push: 1, large_seq: 1551, old_sso_ip: 0, new_sso_ip: 31806887127679168, channel_no: "".to_string(), cpid: 0, vendor_name: transport.device.vendor_name.to_owned(), vendor_os_name: transport.device.vendor_os_name.to_owned(), b769: Bytes::from_static(&[ 0x0A, 0x04, 0x08, 0x2E, 0x10, 0x00, 0x0A, 0x05, 0x08, 0x9B, 0x02, 0x10, 0x00, ]), set_mute: 0, ..Default::default() }; let pkt = self.svc_req_register_pkt(svc); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::D2Key, seq_id: seq as i32, body: pkt.freeze(), command_name: "StatSvc.register".into(), uin: self.uin(), ..Default::default() } } fn svc_req_register_pkt(&self, svc: jce::SvcReqRegister) -> jce::RequestPacket { let mut b = BytesMut::new(); b.put_slice(&[0x0A]); b.put_slice(&svc.freeze()); b.put_slice(&[0x0B]); let buf = jce::RequestDataVersion3 { map: HashMap::from([("SvcReqRegister".to_string(), b.into())]), }; jce::RequestPacket { i_version: 3, s_servant_name: "PushService".to_string(), s_func_name: "SvcReqRegister".to_string(), s_buffer: buf.freeze(), context: Default::default(), status: Default::default(), ..Default::default() } } // StatSvc.GetDevLoginInfo pub fn build_device_list_request_packet(&self) -> Packet { let transport = &self.transport; let req = jce::SvcReqGetDevLoginInfo { guid: transport.sig.guid.to_owned(), login_type: 1, app_name: "com.tencent.mobileqq".into(), require_max: 20, get_dev_list_type: 20, ..Default::default() }; let buf = jce::RequestDataVersion3 { map: HashMap::from([( "SvcReqGetDevLoginInfo".to_string(), pack_uni_request_data(&req.freeze()), )]), }; let pkt = jce::RequestPacket { i_version: 3, s_servant_name: "StatSvc".to_string(), s_func_name: "SvcReqGetDevLoginInfo".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("StatSvc.GetDevLoginInfo", pkt.freeze()) } // StatSvc.RspMSFForceOffline pub fn build_msf_force_offline_rsp(&self, uin: i64, seq_no: i64) -> Packet { let rsp = jce::RspMSFForceOffline { uin, seq_no, const_zero: 0, }; let buf = jce::RequestDataVersion3 { map: HashMap::from([( "RspMSFForceOffline".to_string(), pack_uni_request_data(&rsp.freeze()), )]), }; let pkt = jce::RequestPacket { i_version: 3, s_servant_name: "StatSvc".to_string(), s_func_name: "RspMSFForceOffline".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("StatSvc.RspMSFForceOffline", pkt.freeze()) } } ================================================ FILE: ricq-core/src/command/stat_svc/decoder.rs ================================================ use bytes::{Buf, Bytes}; use jcers::Jce; use crate::{jce, RQError, RQResult}; impl super::super::super::Engine { // StatSvc.register pub fn decode_client_register_response( &self, mut payload: Bytes, ) -> RQResult { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload).map_err(RQError::from)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut request.s_buffer).map_err(RQError::from)?; let mut a = data .map .remove("SvcRespRegister") .ok_or_else(|| RQError::Decode("missing SvcRespRegister".into()))?; let mut b = a .remove("QQService.SvcRespRegister") .ok_or_else(|| RQError::Decode("missing QQService.SvcRespRegister".into()))?; b.advance(1); jcers::from_buf(&mut b).map_err(RQError::from) } // StatSvc.GetDevLoginInfo pub fn decode_dev_list_response( &self, mut payload: Bytes, ) -> RQResult> { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload).map_err(RQError::from)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut request.s_buffer).map_err(RQError::from)?; let mut req = data .map .remove("SvcRspGetDevLoginInfo") .ok_or_else(|| RQError::Decode("missing SvcRspGetDevLoginInfo".into()))?; let mut msg = req .remove("QQService.SvcRspGetDevLoginInfo") .ok_or_else(|| RQError::Decode("missing QQService.SvcRspGetDevLoginInfo".into()))?; msg.advance(1); let mut rsp = Jce::new(&mut msg); let d: Vec = rsp.get_by_tag(4).map_err(RQError::from)?; if !d.is_empty() { return Ok(d); } let d: Vec = rsp.get_by_tag(5).map_err(RQError::from)?; if !d.is_empty() { return Ok(d); } let d: Vec = rsp.get_by_tag(6).map_err(RQError::from)?; if !d.is_empty() { return Ok(d); } Err(RQError::Decode("decode_dev_list_response".into())) } // StatSvc.ReqMSFOffline pub fn decode_msf_force_offline( &self, mut payload: Bytes, ) -> RQResult { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload).map_err(RQError::from)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut request.s_buffer).map_err(RQError::from)?; let mut data = data .map .remove("RequestMSFForceOffline") .ok_or_else(|| RQError::Decode("missing RequestMSFForceOffline".into()))? .remove("QQService.RequestMSFForceOffline") .ok_or_else(|| RQError::Decode("missing QQService.RequestMSFForceOffline".into()))?; jcers::from_buf(&mut data).map_err(RQError::from) } } ================================================ FILE: ricq-core/src/command/stat_svc/mod.rs ================================================ pub mod builder; pub mod decoder; #[derive(Debug, Clone)] pub struct Status { pub online_status: i32, pub ext_online_status: i64, pub custom_status: Option, } #[derive(Debug, Copy, Clone)] pub enum OnlineStatus { Online = 11, // 在线 Offline = 21, // 离线 Away = 31, // 离开 Invisible = 41, // 隐身 Busy = 50, // 忙 Qme = 60, // Q我吧 Dnd = 70, // 请勿打扰 } impl From for Status { fn from(s: OnlineStatus) -> Self { Self { online_status: s as i32, ext_online_status: 0, custom_status: None, } } } #[derive(Debug, Copy, Clone)] pub enum ExtOnlineStatus { Battery = 1000, // 当前电量 Listening = 1028, // 听歌中 Constellation = 1040, // 星座运势 Weather = 1030, // 今日天气 MeetSpring = 1069, // 遇见春天 Timi = 1027, // Timi中 EatChicken = 1064, // 吃鸡中 Loving = 1051, // 恋爱中 WangWang = 1053, // 汪汪汪 CookedRice = 1019, // 干饭中 Study = 1018, // 学习中 StayUp = 1032, // 熬夜中 PlayBall = 1050, // 打球中 Signal = 1011, // 信号弱 StudyOnline = 1024, // 在线学习 Gaming = 1017, // 游戏中 Vacationing = 1022, // 度假中 WatchingTV = 1021, // 追剧中 Fitness = 1020, // 健身中 } impl From for Status { fn from(s: ExtOnlineStatus) -> Self { Self { online_status: 11, ext_online_status: s as i64, custom_status: None, } } } #[derive(Debug, Clone)] pub struct CustomOnlineStatus { pub face_index: u64, pub wording: String, } impl From for Status { fn from(s: CustomOnlineStatus) -> Self { Self { online_status: 11, ext_online_status: 2000, custom_status: Some(s), } } } ================================================ FILE: ricq-core/src/command/summary_card/builder.rs ================================================ use std::collections::HashMap; use bytes::{BufMut, Bytes, BytesMut}; use jcers::JcePut; use crate::command::common::{pack_uni_request_data, PbToBytes}; use crate::protocol::packet::*; use crate::{jce, pb}; impl super::super::super::Engine { // SummaryCard.ReqSummaryCard pub fn build_summary_card_request_packet(&self, target: i64) -> Packet { let seq = self.next_seq(); let gate = pb::profilecard::GateVaProfileGateReq { u_cmd: Some(3), st_privilege_req: Some(pb::profilecard::GatePrivilegeBaseInfoReq { u_req_uin: Some(target), }), st_gift_req: Some(pb::profilecard::GateGetGiftListReq { uin: Some(target as i32), }), st_vip_care: Some(pb::profilecard::GateGetVipCareReq { uin: Some(target) }), oidb_flag: vec![ pb::profilecard::GateOidbFlagInfo { fieled: Some(42334), byets_value: None, }, pb::profilecard::GateOidbFlagInfo { fieled: Some(42340), byets_value: None, }, pb::profilecard::GateOidbFlagInfo { fieled: Some(42344), byets_value: None, }, pb::profilecard::GateOidbFlagInfo { fieled: Some(42354), byets_value: None, }, ], ..Default::default() } .to_bytes(); let business_buf = { let mut w = BytesMut::new(); let comm = pb::profilecard::BusiComm { ver: Some(1), seq: Some(seq as i32), fromuin: Some(self.uin()), touin: Some(target), service: Some(16), platform: Some(2), qqver: Some(self.transport.version.build_ver.into()), build: Some(4945), ..Default::default() } .to_bytes(); w.put_u8(40); w.put_u32(comm.len() as u32); w.put_u32(gate.len() as u32); w.put_slice(&comm); w.put_slice(&gate); w.put_u8(42); w.freeze() }; let req = jce::SummaryCardReq { uin: target, come_from: 31, get_control: 69181, add_friend_source: 3001, secure_sig: Bytes::from(vec![0]), req_medal_wall_info: 0, req_0x5eb_field_id: vec![ 27225, 27224, 42122, 42121, 27236, 27238, 42167, 42172, 40324, 42284, 42326, 42325, 42356, 42363, 42361, 42367, 42377, 42425, 42505, 42488, ], req_services: vec![business_buf], req_nearby_god_info: 1, req_extend_card: 1, ..Default::default() }; let mut head = jcers::JceMut::new(); head.put_i32(2, 0); let buf = jce::RequestDataVersion3 { map: HashMap::from([ ("ReqHead".to_string(), pack_uni_request_data(&head.freeze())), ( "ReqSummaryCard".to_string(), pack_uni_request_data(&req.freeze()), ), ]), }; let pkt = jce::RequestPacket { i_version: 3, s_servant_name: "SummaryCardServantObj".to_string(), s_func_name: "ReqSummaryCard".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("SummaryCard.ReqSummaryCard", pkt.freeze()) } } ================================================ FILE: ricq-core/src/command/summary_card/decoder.rs ================================================ use bytes::{Buf, Bytes}; use crate::jce::{RespSummaryCard, RespSummaryCardHead}; use crate::structs::SummaryCardInfo; use crate::{jce, RQError, RQResult}; impl super::super::super::Engine { // SummaryCard.ReqSummaryCard pub fn decode_summary_card_response(&self, mut payload: Bytes) -> RQResult { let mut request: jce::RequestPacket = jcers::from_buf(&mut payload).map_err(RQError::from)?; let mut data: jce::RequestDataVersion2 = jcers::from_buf(&mut request.s_buffer).map_err(RQError::from)?; let mut head = data .map .remove("RespHead") .ok_or_else(|| RQError::Decode("missing RespHead".into()))? .remove("SummaryCard.RespHead") .ok_or_else(|| RQError::Decode("missing SummaryCard.RespHead".into()))?; head.advance(1); let head: RespSummaryCardHead = jcers::from_buf(&mut head)?; let mut rsp = data .map .remove("RespSummaryCard") .ok_or_else(|| RQError::Decode("missing RespSummaryCard".into()))? .remove("SummaryCard_Old.RespSummaryCard") .ok_or_else(|| RQError::Decode("missing SummaryCard_Old.RespSummaryCard".into()))?; rsp.advance(1); let rsp: RespSummaryCard = jcers::from_buf(&mut rsp)?; let info = SummaryCardInfo { sex: rsp.sex, age: rsp.age, nickname: rsp.nickname, level: rsp.level, city: rsp.city, sign: rsp.sign, mobile: rsp.mobile, uin: rsp.uin, login_days: rsp.login_days, cookie: head.cookie, }; // TODO more info Ok(info) } } ================================================ FILE: ricq-core/src/command/summary_card/mod.rs ================================================ pub mod builder; pub mod decoder; ================================================ FILE: ricq-core/src/command/visitor_svc/builder.rs ================================================ use std::collections::HashMap; use bytes::Bytes; use jcers::JcePut; use crate::command::common::pack_uni_request_data; use crate::jce; use crate::protocol::packet::Packet; impl super::super::super::Engine { // VisitorSvc.ReqFavorite pub fn build_send_like_packet( &self, uin: i64, count: i32, source: i32, cookies: Bytes, ) -> Packet { let seq = self.next_seq(); let req = jce::ReqFavorite { header: jce::QQServiceReqHead { uin: self.uin(), sh_version: 1, seq: seq as i32, req_type: 1, triggered: 0, cookies, }, mid: uin, op_type: 0, source, count, }; let buf = jce::RequestDataVersion3 { map: HashMap::from([( "ReqFavorite".to_string(), pack_uni_request_data(&req.freeze()), )]), }; let pkt = jce::RequestPacket { i_version: 3, s_servant_name: "VisitorSvc".to_string(), s_func_name: "ReqFavorite".to_string(), s_buffer: buf.freeze(), ..Default::default() }; self.uni_packet("VisitorSvc.ReqFavorite", pkt.freeze()) } } ================================================ FILE: ricq-core/src/command/visitor_svc/mod.rs ================================================ pub mod builder; ================================================ FILE: ricq-core/src/command/wtlogin/builder.rs ================================================ use bytes::{BufMut, BytesMut}; use crate::binary::packet_writer::{CounterWriter, Either, PacketAppender, PacketWriter, WriteLV}; use crate::binary::BinaryWriter; use crate::command::wtlogin::builder::utils::*; use crate::command::wtlogin::tlv_writer::*; use crate::protocol::version::Protocol; use crate::protocol::{ oicq::{self, EncryptionMethod}, packet::{EncryptType, Packet, PacketType}, }; impl super::super::super::Engine { // wtlogin.trans_emp pub fn build_qrcode_fetch_request_packet(&self) -> Packet { let transport = &self.transport; let seq = self.next_seq(); let req = self.build_oicq_request_packet(0, 0x812, &{ let mut w = BytesMut::new(); let req_body = build_code2d_request_packet(0, 0, 0x31, &{ let mut w = BytesMut::new(); w.put_u16(0); // const w.put_u32(16); // app id w.put_u64(0); // const long user w.put_u8(8); // const w.write_short_lv([].as_slice()); let tlv_writer = CounterWriter::default() .append(t16( transport.version.sso_version, 16, // app id ? transport.version.sub_app_id, &transport.sig.guid, transport.version.apk_id, transport.version.sort_version_name, transport.version.apk_sign, )) .append(t1b(0, 0, 3, 4, 72, 2, 2)) .append(t1d(transport.version.misc_bitmap)) .append(if matches!(transport.version.protocol, Protocol::MacOS) { t1f( false, "Mac OSX", "10", "mac carrier", &transport.device.apn, 2, // wifi ) } else { t1f( false, &transport.device.os_type, "7.1.2", &transport.device.sim_info, &transport.device.apn, 2, // wifi ) }) .append(t33(&transport.sig.guid)) .append(t35( if matches!(transport.version.protocol, Protocol::MacOS) { 5 } else { 8 }, )); w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); w }); w.put_u8(0); w.put_u16(req_body.len() as u16); w.put_u32(transport.version.app_id); w.put_u32(114); // const role w.write_hex("000000"); w.put_slice(&req_body); w }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.trans_emp".into(), ..Default::default() } } // wtlogin.trans_emp pub fn build_qrcode_result_query_request_packet(&self, sig: &[u8]) -> Packet { let seq = self.next_seq(); let req = self.build_oicq_request_packet(0, 0x812, &{ let mut w = BytesMut::new(); let req_body = build_code2d_request_packet(1, 0, 0x12, &{ let mut w = Vec::new(); w.put_u16(5); w.put_u8(1); w.put_u32(8); // 0x68 ? w.put_u32(16); w.write_short_lv(sig); w.put_u64(0); w.put_u8(8); w.write_short_lv([].as_slice()); w.put_u16(0); w }); w.put_u8(0); w.put_u16(req_body.len() as u16); w.put_u32(self.transport.version.app_id); w.put_u32(114); // const role w.write_hex("000000"); w.put_slice(&req_body); w }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.trans_emp".into(), ..Default::default() } } // wtlogin.login pub fn build_qrcode_login_packet( &self, tmp_pwd: &[u8], tmp_no_pic_sig: &[u8], tgt_qr: &[u8], ) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let req = self.build_oicq_request_packet(self.uin(), 0x0810, &{ let mut w = BytesMut::new(); w.put_u16(9); let dev_info = transport.device.gen_pb_data(); let tlv_writer = CounterWriter::default() .append(t18(16, self.uin() as u32)) .append(t1(self.uin() as u32, &transport.device.ip_address)) .append(tlv(0x106, tmp_pwd)) .append(t116( transport.version.misc_bitmap, transport.version.sub_sig_map, )) .append(t100( transport.version.sso_version, transport.version.sub_app_id, transport.version.main_sig_map, )) .append(t107(0)) .append(t142(transport.version.apk_id)) .append(t144( &transport.device.imei, &dev_info, &transport.device.os_type, &transport.device.version.release, &transport.device.sim_info, &transport.device.apn, false, true, false, guid_flag(), &transport.device.model, &transport.sig.guid, &transport.device.brand, &transport.sig.tgtgt_key, )) .append(t145(&transport.sig.guid)) .append(t147( 16, transport.version.sort_version_name, transport.version.apk_sign, )) .append(t16a(tmp_no_pic_sig)) .append(t154(seq)) .append(t141(&transport.device.sim_info, &transport.device.apn)) .append(t8(2052)) .append(t511(vec![ "tenpay.com", "openmobile.qq.com", "docs.qq.com", "connect.qq.com", "qzone.qq.com", "vip.qq.com", "gamecenter.qq.com", "qun.qq.com", "game.qq.com", "qqweb.qq.com", "office.qq.com", "ti.qq.com", "mail.qq.com", "mma.qq.com", ])) .append(t187(&transport.device.mac_address)) .append(t188(&transport.device.android_id)) .append_option(if !transport.device.imsi_md5.is_empty() { Some(t194(transport.device.imsi_md5.as_slice())) } else { None }) .append(t191(0x00)) .append_option( if !transport.device.wifi_bssid.is_empty() && !transport.device.wifi_ssid.is_empty() { Some(t202( &transport.device.wifi_bssid, &transport.device.wifi_ssid, )) } else { None }, ) .append(t177( transport.version.build_time, transport.version.sdk_version, )) .append(t516()) .append(t521(8)) .append(t318(tgt_qr)); w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); // let v:Vec = vec![0x01, 0x00]; // w.put_slice(&t525(&t536(&v))); w }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.login".to_string(), uin: self.uin(), ..Default::default() } } // wtlogin.login pub fn build_device_lock_login_packet(&self) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let req = self.build_oicq_request_packet(self.uin(), 0x0810, &{ let mut w = BytesMut::new(); w.put_u16(20); let tlv_writer = CounterWriter::default() .append(t8(2052)) .append(t104(&transport.sig.t104)) .append(t116( transport.version.misc_bitmap, transport.version.sub_sig_map, )) .append(t401(&transport.sig.g)); w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); w }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.login".into(), uin: self.uin(), ..Default::default() } } // wtlogin.login pub fn build_captcha_packet(&self, result: String, sign: &[u8]) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let req = self.build_oicq_request_packet(self.uin(), 0x810, &{ let mut w = BytesMut::new(); w.put_u16(2); // sub command let tlv_writer = CounterWriter::default() .append(t2(result, sign)) .append(t8(2052)) .append(t104(&transport.sig.t104)) .append(t116( transport.version.misc_bitmap, transport.version.sub_sig_map, )); w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); w }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.login".into(), uin: self.uin(), ..Default::default() } } // wtlogin.login pub fn build_sms_request_packet(&self) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let req = self.build_oicq_request_packet(self.uin(), 0x810, &{ let mut w = BytesMut::new(); w.put_u16(8); let tlv_writer = CounterWriter::default() .append(t8(2052)) .append(t104(&transport.sig.t104)) .append(t116( transport.version.misc_bitmap, transport.version.sub_sig_map, )) .append(t174(&transport.sig.t174)) .append(t17a(9)) .append(t197()); w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); w }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.login".into(), uin: self.uin(), ..Default::default() } } // wtlogin.login pub fn build_sms_code_submit_packet(&self, code: &str, sign: &[u8]) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let req = self.build_oicq_request_packet(self.uin(), 0x810, &{ let mut w = BytesMut::new(); w.put_u16(7); let tlv_writer = CounterWriter::default() .append(t8(2052)) .append(t104(&transport.sig.t104)) .append(t116( transport.version.misc_bitmap, transport.version.sub_sig_map, )) .append(t174(&transport.sig.t174)) .append(t17c(code)) .append(t401(&transport.sig.g)) .append(t198()) .append(tlv(0x544, sign)); w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); w }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.login".into(), uin: self.uin(), ..Default::default() } } // wtlogin.login pub fn build_ticket_submit_packet(&self, ticket: &str, sign: &[u8]) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let req = self.build_oicq_request_packet(self.uin(), 0x810, &{ let mut w = BytesMut::new(); w.put_u16(2); let tlv_writer = CounterWriter::default() .append(t193(ticket)) .append(t8(2052)) .append(t104(&transport.sig.t104)) .append(t116( transport.version.misc_bitmap, transport.version.sub_sig_map, )) .append(tlv(0x544, sign)) .append(tlv(0x547, &*transport.sig.t547)); w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); w }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.login".into(), uin: self.uin(), ..Default::default() } } // wtlogin.exchange_emp pub fn build_request_tgtgt_no_pic_sig_packet(&self) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let codec = &self.transport.oicq_codec; let req = { let mut w = BytesMut::new(); w.put_u16(15); let dev_info = transport.device.gen_pb_data(); let tlv_writer = CounterWriter::default() .append(t18(16, self.uin() as u32)) .append(t1(self.uin() as u32, &transport.device.ip_address)) .append(tlv(0x106, &*transport.sig.encrypted_a1)) .append(t116( transport.version.misc_bitmap, transport.version.sub_sig_map, )) .append(t100( transport.version.sso_version, 2, transport.version.main_sig_map, )) .append(t107(0)) .append(t108(&transport.sig.ksid)) .append(t144( &transport.device.android_id, &dev_info, &transport.device.os_type, &transport.device.version.release, &transport.device.sim_info, &transport.device.apn, false, true, false, guid_flag(), &transport.device.model, &transport.sig.guid, &transport.device.brand, &transport.sig.tgtgt_key, )) .append(t142(transport.version.apk_id)) .append(t145(&transport.sig.guid)) .append(t16a(&transport.sig.srm_token)) .append(t154(seq)) .append(t141(&transport.device.sim_info, &transport.device.apn)) .append(t8(2052)) .append(t511(vec![ "tenpay.com", "openmobile.qq.com", "docs.qq.com", "connect.qq.com", "qzone.qq.com", "vip.qq.com", "gamecenter.qq.com", "qun.qq.com", "game.qq.com", "qqweb.qq.com", "office.qq.com", "ti.qq.com", "mail.qq.com", "mma.qq.com", ])) .append(t147( 16, transport.version.sort_version_name, transport.version.apk_sign, )) .append(t177( transport.version.build_time, transport.version.sdk_version, )) .append(t400( &transport.sig.g, self.uin(), &transport.sig.guid, &transport.sig.dpwd, 1, 16, &transport.sig.rand_seed, )) .append(t187(&transport.device.mac_address)) .append(t188(&transport.device.android_id)) .append(t194(&transport.device.imsi_md5)) .append(t202( &transport.device.wifi_bssid, &transport.device.wifi_ssid, )) .append(t516()) .append(t521(0)) .append(t525(t536(&[0x01, 0x00]))) .append(if let Some(ref qimei) = transport.device.qimei { Either::Left(tlv(0x545, qimei.q16.as_bytes())) } else { Either::Right(tlv(0x545, transport.device.imei.as_bytes())) }); // TODO 544 w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); w.freeze() }; let m = oicq::Message { uin: self.uin() as u32, command: 0x810, body: req, encryption_method: EncryptionMethod::ST, }; Packet { packet_type: PacketType::Simple, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: codec.encode(m), command_name: "wtlogin.exchange_emp".into(), uin: self.uin(), ..Default::default() } } // wtlogin.exchange_emp TODO change d2 pub fn build_request_change_sig_packet(&self, main_sig_map: Option) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let req = self.build_oicq_request_packet(self.uin(), 0x810, &{ let mut w = BytesMut::new(); w.put_u16(11); let dev_info = transport.device.gen_pb_data(); let tgtgt_key = md5::compute(&transport.sig.d2key).to_vec(); let tlv_writer = CounterWriter::default() .append(t100( transport.version.sso_version, 100, main_sig_map.unwrap_or(transport.version.main_sig_map), )) .append(t10a(&transport.sig.tgt)) .append(t116( transport.version.misc_bitmap, transport.version.sub_sig_map, )) .append(t108(&transport.sig.ksid)) .append(t144( &transport.device.android_id, &dev_info, &transport.device.os_type, &transport.device.version.release, &transport.device.sim_info, &transport.device.apn, false, true, false, guid_flag(), &transport.device.model, &transport.sig.guid, &transport.device.brand, &tgtgt_key, )) .append(t112(self.uin())) .append(t143(&transport.sig.d2)) // TODO change d2 145 .append(t142(transport.version.apk_id)) .append(t154(seq)) .append(t18(16, self.uin() as u32)) .append(t141(&transport.device.sim_info, &transport.device.apn)) .append(t8(2052)) .append(t147( 16, transport.version.sort_version_name, transport.version.apk_sign, )) .append(t177( transport.version.build_time, transport.version.sdk_version, )) .append(t187(&transport.device.mac_address)) .append(t188(&transport.device.android_id)) .append(t194(&transport.device.imsi_md5)) .append(t511(vec![ "tenpay.com", "openmobile.qq.com", "docs.qq.com", "connect.qq.com", "qzone.qq.com", "vip.qq.com", "gamecenter.qq.com", "qun.qq.com", "game.qq.com", "qqweb.qq.com", "office.qq.com", "ti.qq.com", "mail.qq.com", "mma.qq.com", ])) .append(t202( &transport.device.wifi_bssid, &transport.device.wifi_ssid, )); // TODO 544 w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); // w.put_slice(&t202(self.device_info.wifi_bssid.as_bytes(), self.device_info.wifi_ssid.as_bytes())); w }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.exchange_emp".into(), uin: self.uin(), ..Default::default() } } // wtlogin.login pub fn build_login_packet( &self, password_md5: &[u8], sign: &[u8], allow_slider: bool, ) -> Packet { let seq = self.next_seq(); let transport = &self.transport; let req = self.build_oicq_request_packet(self.uin(), 0x0810, &{ let mut w = BytesMut::new(); w.put_u16(9); let dev_info = transport.device.gen_pb_data(); let tlv_writer = CounterWriter::default() .append(t18(16, self.uin() as u32)) .append(t1(self.uin() as u32, &transport.device.ip_address)) .append(t106( self.uin() as u32, 0, transport.version.app_id, transport.version.sso_version, password_md5, true, &transport.sig.guid, &transport.sig.tgtgt_key, 0, )) .append(t116( transport.version.misc_bitmap, transport.version.sub_sig_map, )) .append(t100( transport.version.sso_version, transport.version.sub_app_id, transport.version.main_sig_map, )) .append(t107(0)) .append(t142(transport.version.apk_id)) .append(t144( &transport.device.imei, &dev_info, &transport.device.os_type, &transport.device.version.release, &transport.device.sim_info, &transport.device.apn, false, true, false, guid_flag(), &transport.device.model, &transport.sig.guid, &transport.device.brand, &transport.sig.tgtgt_key, )) .append(t145(&transport.sig.guid)) .append(t147( 16, transport.version.sort_version_name, transport.version.apk_sign, )) .append(t154(seq)) .append(t141(&transport.device.sim_info, &transport.device.apn)) .append(t8(2052)) .append(t511(vec![ "tenpay.com", "openmobile.qq.com", "docs.qq.com", "connect.qq.com", "qzone.qq.com", "vip.qq.com", "gamecenter.qq.com", "qun.qq.com", "game.qq.com", "qqweb.qq.com", "office.qq.com", "ti.qq.com", "mail.qq.com", "mma.qq.com", ])) .append(t187(&transport.device.mac_address)) .append(t188(&transport.device.android_id)) .append(t194(&transport.device.imsi_md5)) .append_option(if allow_slider { Some(t191(0x82)) } else { None }) .append(t202( &transport.device.wifi_bssid, &transport.device.wifi_ssid, )) .append(t177( transport.version.build_time, transport.version.sdk_version, )) .append(t516()) .append(t521(0)) .append(t525(t536(&[0x01, 0x00]))) .append(tlv(0x544, sign)) .append(if let Some(ref qimei) = transport.device.qimei { Either::Left(tlv(0x545, qimei.q16.as_bytes())) } else { Either::Right(tlv(0x545, transport.device.imei.as_bytes())) }); w.put_u16(tlv_writer.count as u16); tlv_writer.write(&mut w); w.freeze() }); Packet { packet_type: PacketType::Login, encrypt_type: EncryptType::EmptyKey, seq_id: seq as i32, body: req, command_name: "wtlogin.login".into(), uin: self.uin(), ..Default::default() } } } mod utils { use bytes::{BufMut, Bytes, BytesMut}; use std::time::UNIX_EPOCH; use crate::command::common::PbToBytes; use crate::pb; use crate::protocol::device::Device; pub fn build_code2d_request_packet(seq: u32, j: u64, cmd: u16, body: &[u8]) -> Bytes { let mut w = BytesMut::new(); w.put_u32(UNIX_EPOCH.elapsed().unwrap().as_secs() as u32); w.put_u8(2); w.put_u16((43 + body.len() + 1) as u16); w.put_u16(cmd); w.put_slice(&[0; 21]); w.put_u8(3); w.put_u16(0); w.put_u16(50); w.put_u32(seq); w.put_u64(j); w.put_slice(body); w.put_u8(3); w.into() } pub trait DeviceToPb { fn gen_pb_data(&self) -> Bytes; } impl DeviceToPb for Device { fn gen_pb_data(&self) -> Bytes { pb::DeviceInfo { bootloader: self.bootloader.to_owned(), proc_version: self.proc_version.to_owned(), codename: self.version.codename.to_owned(), incremental: self.version.incremental.to_owned(), fingerprint: self.finger_print.to_owned(), boot_id: self.boot_id.to_owned(), android_id: self.android_id.to_owned(), base_band: self.base_band.to_owned(), inner_version: self.version.incremental.to_owned(), } .to_bytes() } } } ================================================ FILE: ricq-core/src/command/wtlogin/decoder.rs ================================================ use bytes::{Buf, Bytes}; use crate::binary::BinaryReader; use crate::command::wtlogin::{LoginResponse, QRCodeConfirmed, QRCodeImageFetch, QRCodeState}; use crate::{RQError, RQResult}; impl super::super::super::Engine { pub fn decode_trans_emp_response(&self, mut payload: Bytes) -> RQResult { if payload.len() < 48 { return Err(RQError::Decode("invalid payload length".into())); } payload.advance(5); // trans req head payload.get_u8(); payload.get_u16(); let cmd = payload.get_u16(); payload.advance(21); payload.get_u8(); payload.get_u16(); payload.get_u16(); payload.get_i32(); payload.get_i64(); let len = payload.remaining() - 1; let mut body = payload.copy_to_bytes(len); if cmd == 0x31 { body.get_u16(); body.get_i32(); let code = body.get_u8(); if code != 0 { return Err(RQError::Decode(format!("body code: {code}"))); } let sig = body.read_bytes_short(); body.get_u16(); let mut m = body.read_tlv_map(2); if m.contains_key(&0x17) { return Ok(QRCodeState::ImageFetch(QRCodeImageFetch { image_data: m .remove(&0x17) .ok_or_else(|| RQError::Decode("missing 0x17".into()))?, sig, })); } } if cmd == 0x12 { let mut a_var_len = body.get_u16(); if a_var_len != 0 { a_var_len -= 1; // 阴间的位移操作 if body.get_u8() == 2 { body.get_i64(); //uin? a_var_len -= 8; } } if a_var_len > 0 { body.advance(a_var_len as usize); } body.get_i32(); let code = body.get_u8(); if code != 0 { return match code { 0x30 => Ok(QRCodeState::WaitingForScan), 0x35 => Ok(QRCodeState::WaitingForConfirm), 0x36 => Ok(QRCodeState::Canceled), 0x11 => Ok(QRCodeState::Timeout), _ => Err(RQError::Decode("invalid body code".to_string())), }; } let uin = body.get_i64(); body.get_i32(); // sig create time body.get_u16(); let mut m = body.read_tlv_map(2); return Ok(QRCodeState::Confirmed(QRCodeConfirmed { uin, tmp_pwd: m .remove(&0x18) .ok_or_else(|| RQError::Decode("missing 0x18".into()))?, tmp_no_pic_sig: m .remove(&0x19) .ok_or_else(|| RQError::Decode("missing 0x19".into()))?, tgt_qr: m .remove(&0x65) .ok_or_else(|| RQError::Decode("missing 0x65".into()))?, tgtgt_key: m .remove(&0x1e) .ok_or_else(|| RQError::Decode("missing 0x1e".into()))?, })); } Err(RQError::Decode( "decode_trans_emp_response unknown error".to_string(), )) } pub fn decode_login_response(&self, mut reader: Bytes) -> RQResult { let _sub_command = reader.get_u16(); // sub command let status = reader.get_u8(); // TODO status=213 不能执行下面的步骤 panic reader.get_u16(); let tlv_map = reader.read_tlv_map(2); LoginResponse::decode(status, tlv_map, &self.transport.sig.tgtgt_key) } pub fn decode_exchange_emp_response(&self, mut payload: Bytes) -> RQResult { let sub_command = payload.get_u16(); let status = payload.get_u8(); payload.get_u16(); let tlv_map = payload.read_tlv_map(2); if status != 0 { return Err(RQError::Decode(format!( "decode_exchange_emp_response status: {status}" ))); } let encrypt_key = if sub_command == 11 { md5::compute(&self.transport.sig.d2key).to_vec() } else { self.transport.sig.tgtgt_key.to_vec() }; LoginResponse::decode(status, tlv_map, &encrypt_key) } } ================================================ FILE: ricq-core/src/command/wtlogin/mod.rs ================================================ #![allow(clippy::large_enum_variant)] use std::collections::HashMap; use std::time::UNIX_EPOCH; use bytes::{Buf, BufMut, Bytes, BytesMut}; use rsa::BigUint; use crate::binary::{BinaryReader, BinaryWriter}; use crate::command::wtlogin::tlv_reader::*; use crate::{RQError, RQResult}; mod builder; mod decoder; pub mod tlv_reader; pub mod tlv_writer; #[derive(Debug, Clone)] pub enum QRCodeState { ImageFetch(QRCodeImageFetch), WaitingForScan, WaitingForConfirm, Timeout, Confirmed(QRCodeConfirmed), Canceled, } #[derive(Debug, Clone)] pub struct QRCodeImageFetch { pub image_data: Bytes, pub sig: Bytes, } #[derive(Debug, Clone)] pub struct QRCodeConfirmed { pub uin: i64, pub tmp_pwd: Bytes, pub tmp_no_pic_sig: Bytes, pub tgt_qr: Bytes, pub tgtgt_key: Bytes, } #[derive(Debug, Clone)] pub struct ImageCaptcha { pub sign: Bytes, pub image: Bytes, } #[derive(Debug, Clone)] pub enum LoginResponse { Success(LoginSuccess), // slider or image captcha NeedCaptcha(LoginNeedCaptcha), AccountFrozen, // sms or qrcode DeviceLocked(LoginDeviceLocked), TooManySMSRequest, // More login packet needed DeviceLockLogin(LoginDeviceLockLogin), UnknownStatus(LoginUnknownStatus), } #[derive(Debug, Clone)] pub struct LoginSuccess { pub rollback_sig: Option, pub rand_seed: Option, pub ksid: Option, pub account_info: Option, pub t512: Option, // 不知道有没有 t402 pub t402: Option, pub wt_session_ticket_key: Option, pub srm_token: Option, pub t133: Option, pub encrypt_a1: Option, pub tgt: Option, pub tgt_key: Option, pub user_st_key: Option, pub user_st_web_sig: Option, pub s_key: Option, pub s_key_expired_time: i64, pub d2: Option, pub d2key: Option, pub device_token: Option, } #[derive(Debug, Clone)] pub struct LoginNeedCaptcha { pub t104: Option, pub verify_url: Option, pub image_captcha: Option, pub t547: Option, } #[derive(Debug, Clone)] pub struct LoginDeviceLocked { pub t104: Option, pub t174: Option, pub t402: Option, pub sms_phone: Option, pub verify_url: Option, pub message: Option, pub rand_seed: Option, } #[derive(Debug, Clone)] pub struct LoginDeviceLockLogin { pub t104: Option, pub t402: Option, pub rand_seed: Option, } #[derive(Debug, Clone)] pub struct LoginUnknownStatus { pub status: u8, pub tlv_map: HashMap, pub message: String, } impl LoginResponse { pub fn decode( status: u8, mut tlv_map: HashMap, encrypt_key: &[u8], ) -> RQResult { let resp = match status { 0 => { let mut t119 = tlv_map .remove(&0x119) .map(|v| decode_t119(&v, encrypt_key)) .ok_or_else(|| RQError::Decode("missing 0x119".to_string()))?; LoginResponse::Success(LoginSuccess { rollback_sig: tlv_map.remove(&0x161).map(decode_t161), rand_seed: tlv_map.remove(&0x403), ksid: t119.remove(&0x108), account_info: t119.remove(&0x11a).map(read_t11a), t512: t119.remove(&0x512).map(read_t512), t402: tlv_map.remove(&0x402), wt_session_ticket_key: t119.remove(&0x134), srm_token: t119.remove(&0x16a), t133: t119.remove(&0x133), encrypt_a1: t119.remove(&0x106), tgt: t119.remove(&0x10a), tgt_key: t119.remove(&0x10d), user_st_key: t119.remove(&0x10e), user_st_web_sig: t119.remove(&0x103), s_key: t119.remove(&0x120), s_key_expired_time: UNIX_EPOCH.elapsed().unwrap().as_secs() as i64 + 21600, d2: t119.remove(&0x143), d2key: t119.remove(&0x305), device_token: t119.remove(&0x322), }) } 2 => LoginResponse::NeedCaptcha(LoginNeedCaptcha { t104: tlv_map.remove(&0x104), verify_url: tlv_map .remove(&0x192) .map(|v| String::from_utf8_lossy(&v).into_owned()), image_captcha: tlv_map.remove(&0x165).map(|mut img_data| { let sign_len = img_data.get_u16(); img_data.get_u16(); let image_sign = img_data.copy_to_bytes(sign_len as usize); ImageCaptcha { sign: image_sign, image: img_data, } }), t547: tlv_map.remove(&0x546).map(t546_to_t547), }), 40 => LoginResponse::AccountFrozen, 160 | 239 => { let t174 = tlv_map.remove(&0x174); let t178 = tlv_map.remove(&0x178); let sms_phone = if t174.is_some() { t178.map(|mut v| { let country_code = v.read_string_short(); let phone_number = v.read_string_short(); format!("+{} {}", country_code, phone_number) }) } else { None }; LoginResponse::DeviceLocked(LoginDeviceLocked { sms_phone, verify_url: tlv_map .remove(&0x204) .map(|v| String::from_utf8_lossy(&v).into_owned()), message: tlv_map .remove(&0x17e) .map(|v| String::from_utf8_lossy(&v).into_owned()), rand_seed: tlv_map.remove(&0x403), t104: tlv_map.remove(&0x104), t174, t402: tlv_map.remove(&0x402), }) } 162 => LoginResponse::TooManySMSRequest, 204 => LoginResponse::DeviceLockLogin(LoginDeviceLockLogin { t104: tlv_map.remove(&0x104), t402: tlv_map.remove(&0x402), rand_seed: tlv_map.remove(&0x403), }), _ => { // status=1 可能是密码错误 let mut _title = "".into(); let mut message = "".into(); if let Some(mut v) = tlv_map.remove(&0x146) { v.advance(4); _title = v.read_string_short(); message = v.read_string_short(); } LoginResponse::UnknownStatus(LoginUnknownStatus { status, tlv_map, message, }) } }; Ok(resp) } } pub fn t546_to_t547(mut data: Bytes) -> Bytes { let a = data.get_u8(); let typ = data.get_u8(); let c = data.get_u8(); let mut ok = data.get_u8() != 0; let e = data.get_u16(); let f = data.get_u16(); let src = data.read_bytes_short(); let tgt = data.read_bytes_short().to_vec(); let cpy = data.read_bytes_short(); let mut cnt = 0; let mut dst = Vec::new(); let mut elp = 0; if typ == 2 && tgt.len() == 32 { let start = std::time::SystemTime::now(); let mut tmp = BigUint::from_bytes_be(&src); use sha2::{Digest, Sha256}; let mut hash = Sha256::digest(tmp.to_bytes_be()).to_vec(); while hash != tgt { tmp += 1u8; hash = Sha256::digest(tmp.to_bytes_be()).to_vec(); cnt += 1; } ok = true; dst = tmp.to_bytes_be(); elp = start.elapsed().unwrap().as_millis() as u32; } let mut w = BytesMut::new(); w.put_u8(a); w.put_u8(typ); w.put_u8(c); w.put_u8(if ok { 1 } else { 0 }); w.put_u16(e); w.put_u16(f); w.write_bytes_short(&src); w.write_bytes_short(&tgt); w.write_bytes_short(&cpy); if ok { w.write_bytes_short(&dst); w.put_u32(elp); w.put_u32(cnt); } w.freeze() } ================================================ FILE: ricq-core/src/command/wtlogin/tlv_reader.rs ================================================ use std::collections::HashMap; use bytes::{Buf, BufMut, Bytes, BytesMut}; use crate::binary::BinaryReader; use crate::crypto::qqtea_decrypt; #[derive(Debug, Clone)] pub struct T161 { // 172 pub rollback_sig: Option, // 173 // 17f } #[derive(Debug, Clone)] pub struct T113 { pub uin: i32, } #[derive(Debug, Clone)] pub struct T125 { pub open_id: Bytes, pub open_key: Bytes, } #[derive(Debug, Clone, Default)] pub struct T11A { pub face: u16, pub gender: u8, pub age: u8, pub nick: String, } #[derive(Debug, Clone)] pub struct T199 { pub open_id: Bytes, pub pay_token: Bytes, } #[derive(Debug, Clone)] pub struct T200 { pub pf: Bytes, pub pf_key: Bytes, } #[derive(Debug, Clone)] pub struct T512 { pub ps_key_map: HashMap, pub pt4_token_map: HashMap, } #[derive(Debug, Clone)] pub struct T531 { pub a1: Bytes, pub no_pic_sig: Bytes, } pub fn decode_t161(mut data: Bytes) -> T161 { data.advance(2); let mut m = data.read_tlv_map(2); T161 { rollback_sig: m.remove(&0x172), } } pub fn decode_t119(data: &[u8], ek: &[u8]) -> HashMap { let mut reader = Bytes::from(qqtea_decrypt(data, ek)); reader.advance(2); reader.read_tlv_map(2) } pub fn decode_t113(mut data: Bytes) -> T113 { T113 { uin: data.get_i32(), } } pub fn decode_t186(_: &[u8]) {} // not used pub fn read_t125(data: &[u8]) -> T125 { let mut reader = Bytes::from(data.to_owned()); let open_id = reader.read_bytes_short(); let open_key = reader.read_bytes_short(); T125 { open_id, open_key } } pub fn read_t11a(mut data: Bytes) -> T11A { let face = data.get_u16(); let age = data.get_u8(); let gender = data.get_u8(); let limit = data.get_u8() as usize; let nick = data.read_string_limit(limit); T11A { face, age, gender, nick, } } pub fn read_t199(mut data: Bytes) -> T199 { let open_id = data.read_bytes_short(); let pay_token = data.read_bytes_short(); T199 { open_id, pay_token } } pub fn read_t200(mut data: Bytes) -> T200 { let pf = data.read_bytes_short(); let pf_key = data.read_bytes_short(); T200 { pf, pf_key } } pub fn read_t512(mut reader: Bytes) -> T512 { let length = reader.get_u16() as usize; let mut ps_key_map: HashMap = HashMap::with_capacity(length); let mut pt4_token_map: HashMap = HashMap::with_capacity(length); for _ in 0..length { let domain = reader.read_string_short(); let ps_key = reader.read_bytes_short(); let ps4_token = reader.read_bytes_short(); if !ps_key.is_empty() { ps_key_map.insert(domain.clone(), ps_key); } if !ps4_token.is_empty() { pt4_token_map.insert(domain, ps4_token); } } T512 { ps_key_map, pt4_token_map, } } pub fn read_t531(mut data: Bytes) -> T531 { let mut m = data.read_tlv_map(2); let mut a1 = BytesMut::new(); let mut no_pic_sig = Bytes::new(); if [0x16a, 0x16a, 0x10c].iter().all(|v| m.contains_key(v)) { a1.put_slice(&m.remove(&0x106).expect("0x106 not found")); a1.put_slice(&m.remove(&0x10c).expect("0x10c not found")); no_pic_sig = m.remove(&0x16a).expect("0x16a not found"); } T531 { a1: a1.freeze(), no_pic_sig, } } pub fn select(a: Option<&Bytes>, b: &[u8]) -> Bytes { match a { None => Bytes::from(b.to_owned()), Some(a) => Bytes::from(a.to_vec()), } } ================================================ FILE: ricq-core/src/command/wtlogin/tlv_writer.rs ================================================ #![allow(clippy::too_many_arguments)] use bytes::{BufMut, BytesMut}; use std::time::UNIX_EPOCH; use crate::binary::packet_writer::{PacketWriter, WriteLV}; use crate::binary::BinaryWriter; pub fn tlv<'a, B: BufMut + WriteLV, W: PacketWriter + 'a>( tag: u16, body_writer: W, ) -> impl PacketWriter + 'a { move |buf: &mut B| { buf.put_u16(tag); buf.write_short_lv(body_writer); } } pub fn t1(uin: u32, ip: &[u8]) -> impl PacketWriter + '_ { if ip.len() != 4 { panic!("invalid ip") } tlv(0x01, move |w: &mut B| { w.put_u16(1); w.put_u32(rand::random()); w.put_u32(uin); w.put_u32(UNIX_EPOCH.elapsed().unwrap().as_secs() as u32); w.put_slice(ip); w.put_u16(0); }) } pub fn t1b( micro: u32, version: u32, size: u32, margin: u32, dpi: u32, ec_level: u32, hint: u32, ) -> impl PacketWriter { tlv(0x1b, move |w: &mut B| { w.put_u32(micro); w.put_u32(version); w.put_u32(size); w.put_u32(margin); w.put_u32(dpi); w.put_u32(ec_level); w.put_u32(hint); w.put_u16(0); }) } pub fn t1d(misc_bitmap: u32) -> impl PacketWriter { tlv(0x1d, move |w: &mut B| { w.put_u8(1); w.put_u32(misc_bitmap); w.put_u32(0); w.put_u8(0); w.put_u32(0); }) } pub fn t1f<'a, B: BufMut + WriteLV>( is_root: bool, os_name: &'a str, os_version: &'a str, sim_operator_name: &'a str, apn: &'a str, network_type: u16, ) -> impl PacketWriter + 'a { tlv(0x1f, move |w: &mut B| { w.put_u8(if is_root { 1 } else { 0 }); w.write_bytes_short(os_name.as_bytes()); w.write_bytes_short(os_version.as_bytes()); w.put_u16(network_type); w.write_bytes_short(sim_operator_name.as_bytes()); w.write_bytes_short(&[]); w.write_bytes_short(apn.as_bytes()); }) } pub fn t2(result: String, sign: &[u8]) -> impl PacketWriter + '_ { tlv(0x02, move |w: &mut B| { w.put_u16(0); w.write_bytes_short(result.as_bytes()); w.write_bytes_short(sign); }) } pub fn t8(local_id: u32) -> impl PacketWriter { tlv(0x08, move |w: &mut B| { w.put_u16(0); w.put_u32(local_id); w.put_u16(0); }) } pub fn t10a(arr: &[u8]) -> impl PacketWriter + '_ { tlv(0x10A, arr) } pub fn t16<'a, B: BufMut + WriteLV>( sso_version: u32, app_id: u32, sub_app_id: u32, guid: &'a [u8], apk_id: &'a str, apk_version_name: &'a str, apk_sign: &'a [u8], ) -> impl PacketWriter + 'a { tlv(0x16, move |w: &mut B| { w.put_u32(sso_version); w.put_u32(app_id); w.put_u32(sub_app_id); w.put_slice(guid); w.write_bytes_short(apk_id.as_bytes()); w.write_bytes_short(apk_version_name.as_bytes()); w.write_bytes_short(apk_sign); }) } pub fn t16a(arr: &[u8]) -> impl PacketWriter + '_ { tlv(0x16A, arr) } pub fn t16e(build_model: &[u8]) -> impl PacketWriter + '_ { tlv(0x16E, build_model) } pub fn t17a(value: i32) -> impl PacketWriter { tlv(0x17a, move |buf: &mut B| buf.put_u32(value as u32)) } pub fn t17c(code: &str) -> impl PacketWriter + '_ { tlv(0x17c, move |w: &mut B| { w.write_short_lv(code.as_bytes()); }) } pub fn t18(app_id: u32, uin: u32) -> impl PacketWriter { tlv(0x18, move |w: &mut B| { w.put_u16(1); w.put_u32(1536); w.put_u32(app_id); w.put_u32(0); w.put_u32(uin); w.put_u16(0); w.put_u16(0); }) } pub fn t33(guid: &[u8]) -> impl PacketWriter + '_ { tlv(0x33, guid) } pub fn t35(product_type: u32) -> impl PacketWriter { tlv(0x35, move |buf: &mut B| buf.put_u32(product_type)) } pub fn t52d(dev_info: &[u8]) -> impl PacketWriter + '_ { tlv(0x52d, dev_info) } pub fn t100( sso_version: u32, protocol: u32, main_sig_map: u32, ) -> impl PacketWriter { tlv(0x100, move |w: &mut B| { w.put_u16(1); w.put_u32(sso_version); w.put_u32(16); w.put_u32(protocol); w.put_u32(0); // App client version w.put_u32(main_sig_map); // 34869472 }) } pub fn t104(data: &[u8]) -> impl PacketWriter + '_ { tlv(0x104, data) } pub fn t106<'a, B: BufMut + WriteLV>( uin: u32, salt: u32, app_id: u32, sso_ver: u32, password_md5: &'a [u8], guid_available: bool, guid: &'a [u8], tgtgt_key: &'a [u8], wtf: u32, ) -> impl PacketWriter + 'a { tlv(0x106, move |w: &mut B| { let key = md5::compute(&{ let mut v = BytesMut::new(); v.put_slice(password_md5); v.put_slice(&[0; 4]); v.put_u32(if salt != 0 { salt } else { uin }); v }) .to_vec(); w.encrypt_and_write(&key, &{ let mut w = BytesMut::new(); w.put_u16(4); w.put_u32(rand::random::()); w.put_u32(sso_ver); w.put_u32(16); // appId w.put_u32(0); // app client version w.put_u64(if uin == 0 { salt as u64 } else { uin as u64 }); w.put_u32(UNIX_EPOCH.elapsed().unwrap().as_secs() as u32); w.put_slice(&[0x00, 0x00, 0x00, 0x00]); // fake ip w.put_u8(0x01); w.put_slice(password_md5); w.put_slice(tgtgt_key); w.put_u32(wtf); w.put_u8(if guid_available { 1 } else { 0 }); if guid.is_empty() { for _ in 0..4 { w.put_u32(rand::random::()); } } else { w.put_slice(guid) } w.put_u32(app_id); w.put_u32(1); // password login w.write_short_lv((uin as i64).to_string().as_bytes()); w.put_u16(0); w.freeze() }); }) } pub fn t107(pic_type: u16) -> impl PacketWriter { tlv(0x107, move |w: &mut B| { w.put_u16(pic_type); w.put_u8(0x00); w.put_u16(0); w.put_u8(0x01); }) } pub fn t108(ksid: &[u8]) -> impl PacketWriter + '_ { tlv(0x108, ksid) } pub fn t109(android_id: &str) -> impl PacketWriter + '_ { tlv(0x109, move |buf: &mut B| { buf.put_slice(md5::compute(android_id.as_bytes()).as_ref()); }) } pub fn t112(uin: i64) -> impl PacketWriter { tlv(0x112, move |w: &mut B| { w.put_slice(uin.to_string().as_bytes()) }) } pub fn t116(misc_bitmap: u32, sub_sig_map: u32) -> impl PacketWriter { tlv(0x116, move |w: &mut B| { w.put_u8(0x00); w.put_u32(misc_bitmap); w.put_u32(sub_sig_map); w.put_u8(0x01); w.put_u32(1600000226); // app id list }) } pub fn t124<'a, B: BufMut + WriteLV>( os_type: &'a str, os_version: &'a str, sim_info: &'a str, apn: &'a str, ) -> impl PacketWriter + 'a { tlv(0x124, move |w: &mut B| { w.write_tlv_limited_size(os_type.as_bytes(), 16); w.write_tlv_limited_size(os_version.as_bytes(), 16); w.put_u16(2); w.write_tlv_limited_size(sim_info.as_bytes(), 16); w.write_tlv_limited_size(&[], 16); w.write_tlv_limited_size(apn.as_bytes(), 16); }) } pub fn t128<'a, B: BufMut + WriteLV>( is_guid_from_file_null: bool, is_guid_available: bool, is_guid_changed: bool, guid_flag: u32, build_model: &'a str, guid: &'a [u8], build_brand: &'a str, ) -> impl PacketWriter + 'a { tlv(0x128, move |w: &mut B| { w.put_u16(0); w.put_u8(if is_guid_from_file_null { 1 } else { 0 }); w.put_u8(if is_guid_available { 1 } else { 0 }); w.put_u8(if is_guid_changed { 1 } else { 0 }); w.put_u32(guid_flag); w.write_tlv_limited_size(build_model.as_bytes(), 32); w.write_tlv_limited_size(guid, 16); w.write_tlv_limited_size(build_brand.as_bytes(), 16); // app id list }) } pub fn t141<'a, B: BufMut + WriteLV>(sim_info: &'a str, apn: &'a str) -> impl PacketWriter + 'a { tlv(0x141, move |w: &mut B| { w.put_u16(1); w.write_bytes_short(sim_info.as_bytes()); w.put_u16(2); w.write_bytes_short(apn.as_bytes()); }) } pub fn t142(apk_id: &str) -> impl PacketWriter + '_ { tlv(0x142, move |w: &mut B| { w.put_u16(0); w.write_tlv_limited_size(apk_id.as_bytes(), 32); }) } pub fn t143(arr: &[u8]) -> impl PacketWriter + '_ { tlv(0x143, arr) } pub fn t144<'a, B: BufMut + WriteLV>( imei: &'a str, dev_info: &'a [u8], os_type: &'a str, os_version: &'a str, sim_info: &'a str, apn: &'a str, is_guid_from_file_null: bool, is_guid_available: bool, is_guid_changed: bool, guid_flag: u32, build_model: &'a str, guid: &'a [u8], build_brand: &'a str, tgtgt_key: &'a [u8], ) -> impl PacketWriter + 'a { tlv(0x144, move |w: &mut B| { w.encrypt_and_write(tgtgt_key, &{ let mut w = Vec::new(); w.put_u16(5); t109(imei).write(&mut w); t52d(dev_info).write(&mut w); t124(os_type, os_version, sim_info, apn).write(&mut w); t128( is_guid_from_file_null, is_guid_available, is_guid_changed, guid_flag, build_model, guid, build_brand, ) .write(&mut w); t16e(build_model.as_bytes()).write(&mut w); w }); }) } pub fn t145(guid: &[u8]) -> impl PacketWriter + '_ { tlv(0x145, guid) } pub fn t147<'a, B: BufMut + WriteLV>( app_id: u32, apk_version_name: &'a str, apk_signature_md5: &'a [u8], ) -> impl PacketWriter + 'a { tlv(0x147, move |w: &mut B| { w.put_u32(app_id); w.write_tlv_limited_size(apk_version_name.as_bytes(), 32); w.write_tlv_limited_size(apk_signature_md5, 32); }) } pub fn t154(seq: u16) -> impl PacketWriter { tlv(0x154, move |buf: &mut B| buf.put_u32(seq as u32)) } pub fn t166(image_type: u8) -> impl PacketWriter { tlv(0x166, move |buf: &mut B| buf.put_u8(image_type)) } pub fn t174(data: &[u8]) -> impl PacketWriter + '_ { tlv(0x174, data) } pub fn t177(build_time: u32, sdk_version: &str) -> impl PacketWriter + '_ { tlv(0x177, move |w: &mut B| { w.put_u8(0x01); w.put_u32(build_time); w.write_short_lv(sdk_version.as_bytes()); }) } pub fn t187(mac_address: &str) -> impl PacketWriter + '_ { tlv(0x187, move |buf: &mut B| { buf.put_slice(md5::compute(mac_address.as_bytes()).as_ref()) }) } pub fn t188(android_id: &str) -> impl PacketWriter + '_ { tlv(0x188, move |buf: &mut B| { buf.put_slice(md5::compute(android_id.as_bytes()).as_ref()) }) } pub fn t191(k: u8) -> impl PacketWriter { tlv(0x191, move |buf: &mut B| buf.put_u8(k)) } pub fn t193(ticket: &str) -> impl PacketWriter + '_ { tlv(0x193, ticket.as_bytes()) } pub fn t194(imsi_md5: &[u8]) -> impl PacketWriter + '_ { tlv(0x194, imsi_md5) } pub fn t197() -> impl PacketWriter { tlv(0x197, [0u8].as_slice()) } pub fn t198() -> impl PacketWriter { tlv(0x198, [0u8].as_slice()) } pub fn t202<'a, B: BufMut + WriteLV>( wifi_bssid: &'a str, wifi_ssid: &'a str, ) -> impl PacketWriter + 'a { tlv(0x202, move |w: &mut B| { w.write_tlv_limited_size(wifi_bssid.as_bytes(), 16); w.write_tlv_limited_size(wifi_ssid.as_bytes(), 32); }) } pub fn t318(tgt_qr: &[u8]) -> impl PacketWriter + '_ { tlv(0x318, tgt_qr) } pub fn t400<'a, B: BufMut + WriteLV>( g: &'a [u8], uin: i64, guid: &'a [u8], dpwd: &'a [u8], j2: i64, j3: i64, rand_seed: &'a [u8], ) -> impl PacketWriter + 'a { tlv(0x400, move |w: &mut B| { w.encrypt_and_write(g, &{ let mut ww = Vec::new(); ww.put_u16(1); ww.put_u64(uin as u64); ww.put_slice(guid); ww.put_slice(dpwd); ww.put_u32(j2 as u32); ww.put_u32(j3 as u32); ww.put_u32(UNIX_EPOCH.elapsed().unwrap().as_millis() as u32); ww.put_slice(rand_seed); ww }); }) } pub fn t401(d: &[u8]) -> impl PacketWriter + '_ { tlv(0x401, d) } pub fn t511(domains: Vec<&str>) -> impl PacketWriter + '_ { tlv(0x511, move |w: &mut B| { let mut arr2 = Vec::new(); for d in domains { if !d.is_empty() { arr2.push(d) } } w.put_u16(arr2.len() as u16); for d in arr2 { let index_of = match d.find('(') { None => -1, Some(i) => i as isize, }; let index_of2 = match d.find(')') { None => -1, Some(i) => i as isize, }; if index_of != 0 || index_of2 <= 0 { w.put_u8(0x01); w.write_short_lv(d.as_bytes()) } else { let mut b: u8; let z: bool; if let Ok(i) = d[(index_of + 1) as usize..index_of2 as usize].parse::() { let z2 = (1048576 & i) > 0; z = (i & 134217728) > 0; if z2 { b = 1 } else { b = 0 } if z { b |= 2 } w.put_u8(b); w.write_short_lv(d[(index_of2 + 1) as usize..].as_bytes()); } } } }) } pub fn t516() -> impl PacketWriter { tlv(0x516, |buf: &mut B| buf.put_u32(0)) } pub fn t521(i: u32) -> impl PacketWriter { tlv(0x521, move |w: &mut B| { w.put_u32(i); w.put_u16(0); }) } pub fn t525<'a, B: BufMut + WriteLV, T: PacketWriter + 'a>( t536: T, ) -> impl PacketWriter + 'a { tlv(0x525, move |w: &mut B| { w.put_u16(1); t536.write(w); }) } pub fn t536(login_extra_data: &[u8]) -> impl PacketWriter + '_ { tlv(0x526, login_extra_data) } pub fn guid_flag() -> u32 { let mut flag: u32 = 0; flag |= 1 << 24 & 0xFF000000; flag |= 0; // flag |= 0 << 8 & 0xFF00; flag } #[cfg(test)] mod tests { use crate::command::wtlogin::tlv_writer::*; const GUID: [u8; 16] = [ 142, 27, 163, 177, 172, 31, 181, 137, 118, 115, 8, 126, 24, 49, 54, 169, ]; const TGTGT_KEY: [u8; 16] = [ 199, 12, 183, 107, 3, 28, 81, 148, 116, 20, 229, 112, 0, 64, 152, 255, ]; const UIN: u32 = 349195854; const OS_NAME: &str = "android"; const OS_VERSION: &str = "7.1.2"; const SIM_INFO: &str = "T-Mobile"; const IMEI: &str = "468356291846738"; const IMEI_MD5: &[u8] = "9792b1bba1867318bf782af418306ef8".as_bytes(); const WIFI_BSSID: &str = "00:50:56:C0:00:08"; const WIFI_SSID: &str = ""; const APN: &str = "wifi"; const APK_SIGN: [u8; 16] = [ 0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D, ]; const APK_ID: &str = "com.tencent.mobileqq"; const APP_ID: u32 = 537066738; const SUB_APP_ID: u32 = 537066738; const SSO_VERSION: u32 = 15; const SDK_VERSION: &str = "6.0.0.2454"; const MISC_BITMAP: u32 = 184024956; const SUB_SIG_MAP: u32 = 0x10400; const MAIN_SIG_MAP: u32 = 34869472; const MAC_ADDRESS: &str = "00:50:56:C0:00:08"; const IS_ROOT: bool = false; const ANDROID_ID: &str = "QKQ1.191117.002"; const APK_VERSION_NAME: &str = "2.0.5"; const DEV_INFO: &[u8] = "dev_info_dev_info_dev_info_dev_info_dev_info_".as_bytes(); const BUILD_MODEL: &str = "mirai"; const BUILD_BRAND: &str = "mamoe"; const OS_TYPE: &str = "android"; #[test] fn test_param() { println!("{GUID:?}"); println!("{:?}", "test param"); } fn get_buf>>(w: W) -> Vec { let mut buf = vec![]; w.write(&mut buf); buf } #[test] fn test_t1() { let result = t1(UIN, &[192, 168, 1, 1]); let result = get_buf(result); println!("{:?}", result.len()); println!("{result:?}") } #[test] fn test_t1b() { let result = t1b(0, 0, 3, 4, 72, 2, 2); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t1d() { let result = t1d(MISC_BITMAP); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t1f() { let result = t1f(IS_ROOT, OS_NAME, OS_VERSION, "China Mobile GSM", APN, 2); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t2() { let result = t2("result".to_string(), "sign".as_ref()); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t8() { let result = t8(123456); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t10a() { let result = t10a(IMEI.as_bytes()); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t16() { let result = t16( SSO_VERSION, APP_ID, SUB_APP_ID, &GUID, APK_ID, APK_VERSION_NAME, &APK_SIGN, ); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t16a() { let result = t16a(IMEI.as_bytes()); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t16e() { let result = t16e(IMEI.as_bytes()); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t17a() { let result = t17a(UIN as i32); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t17c() { let result = t17c(IMEI); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t18() { let result = t18(APP_ID, UIN); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t33() { let result = t33(&GUID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t35() { let product_type = 8; let result = t35(product_type); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t52d() { let result = t52d(DEV_INFO); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t100() { let result = t100(SSO_VERSION, 2, MAIN_SIG_MAP); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t104() { let result = t104(&GUID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t106() { let result = t106( UIN, 0, APP_ID, SSO_VERSION, &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], true, &GUID, &TGTGT_KEY, 0, ); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}") } #[test] fn test_t107() { let result = t107(3); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t108() { let result = t108(IMEI.as_bytes()); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t109() { let result = t109(ANDROID_ID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t116() { let result = t116(MAIN_SIG_MAP, SUB_SIG_MAP); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t124() { let result = t124(OS_TYPE, OS_VERSION, SIM_INFO, APN); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t128() { let result = t128(false, true, false, 16, BUILD_MODEL, &GUID, BUILD_BRAND); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t141() { let result = t141(SIM_INFO, APN); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t142() { let result = t142(APK_ID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t143() { let result = t143(&[1, 2, 3]); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t144() { let result = t144( IMEI, DEV_INFO, OS_TYPE, OS_VERSION, SIM_INFO, APN, false, true, false, 16, BUILD_MODEL, &GUID, BUILD_BRAND, &TGTGT_KEY, ); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t145() { let result = t145(&GUID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t147() { let result = t147(16, APK_VERSION_NAME, &APK_SIGN); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t154() { let seq = (0x3635 + 1) & 0x7FFF; println!("{seq}"); let result = t154(seq); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t166() { let result = t166(1); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t174() { let result = t174(&GUID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t177() { let result = t177(MISC_BITMAP, SDK_VERSION); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t187() { let result = t187(MAC_ADDRESS); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t188() { let result = t188(ANDROID_ID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t191() { let result = t191(127_u8); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t193() { let result = t193("some ticket"); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t194() { let result = t194(IMEI_MD5); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t197() { let result = t197(); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t198() { let result = t198(); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t202() { let result = t202(WIFI_BSSID, WIFI_SSID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t400() { let result = t400(&GUID, UIN as i64, &GUID, &GUID, 2, 2, &GUID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t401() { let result = t401(&GUID); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t511() { let result = t511(vec![ "tenpay.com", "openmobile.qq.com", "docs.qq.com", "connect.qq.com", "qzone.qq.com", "vip.qq.com", "gamecenter.qq.com", "qun.qq.com", "game.qq.com", "qqweb.qq.com", "office.qq.com", "ti.qq.com", "mail.qq.com", "mma.qq.com", ]); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t516() { let result = t516(); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t521() { let result = t521(6); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_t525() { let result = t525(t536(&GUID)); let result = get_buf(result); println!("{}", result.len()); println!("{result:?}"); } #[test] fn test_tlv() { let result = guid_flag(); println!("{result:?}"); } } ================================================ FILE: ricq-core/src/common.rs ================================================ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; pub fn group_code2uin(code: i64) -> i64 { let mut left = code / 1000000; if (0..=10).contains(&left) { left += 202 } else if (11..=19).contains(&left) { left += 469 } else if (20..=66).contains(&left) { left += 2080 } else if (67..=156).contains(&left) { left += 1943 } else if (157..=209).contains(&left) { left += 1990 } else if (210..=309).contains(&left) { left += 3890 } else if (310..=335).contains(&left) { left += 3490 } else if (336..=386).contains(&left) { //335 336不确定 left += 2265 } else if (387..=499).contains(&left) { left += 3490 } left * 1000000 + code % 1000000 } pub fn group_uin2code(uin: i64) -> i64 { let mut left = uin / 1000000; if (202..=212).contains(&left) { left -= 202 } else if (480..=488).contains(&left) { left -= 469 } else if (2100..=2146).contains(&left) { left -= 2080 } else if (2010..=2099).contains(&left) { left -= 1943 } else if (2147..=2199).contains(&left) { left -= 1990 } else if (2600..=2651).contains(&left) { left -= 2265 } else if (3800..=3989).contains(&left) { left -= 3490 } else if (4100..=4199).contains(&left) { left -= 3890 } left * 1000000 + uin % 1000000 } #[derive(Debug, Clone, Copy, Default)] pub struct RQAddr(pub u32, pub u16); impl From for SocketAddr { fn from(addr: RQAddr) -> Self { let mut ip: [u8; 4] = addr.0.to_be_bytes(); ip.reverse(); SocketAddr::new(Ipv4Addr::from(ip).into(), addr.1) } } impl From for RQAddr { fn from(addr: SocketAddr) -> Self { let IpAddr::V4(ip) = addr.ip() else { panic!("is not ipv4") }; // ip.octets() returns little-endian Self(u32::from_le_bytes(ip.octets()), addr.port()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_group_code2uin() { let uin = group_code2uin(335783090); assert_eq!(uin, 3825783090); } #[test] fn test_group_uin2code() { let code = group_uin2code(3825783090); assert_eq!(code, 335783090); } } ================================================ FILE: ricq-core/src/crypto/encrypt.rs ================================================ use bytes::{BufMut, Bytes}; // use openssl::bn::{BigNum, BigNumContext}; // use openssl::ec::{EcGroup, EcPoint, EcKey, PointConversionForm}; // use openssl::nid::Nid; use super::qqtea_encrypt; use crate::binary::BinaryWriter; use crate::hex::decode_hex; use p256::{ecdh::EphemeralSecret, EncodedPoint, PublicKey}; pub trait IEncryptMethod { fn id(&self) -> u8; fn do_encrypt(&self, data: &[u8], key: &[u8]) -> Vec; } #[derive(Debug)] pub struct EncryptECDH { pub initial_share_key: Bytes, pub public_key: Bytes, pub public_key_ver: u16, } impl Default for EncryptECDH { fn default() -> Self { let mut ecdh = EncryptECDH { initial_share_key: Bytes::new(), public_key: Bytes::new(), public_key_ver: 1, }; ecdh.generate_key("04EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFBC91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E"); ecdh } } impl EncryptECDH { pub fn generate_key(&mut self, s_pub_key: &str) { let s_pub_key = decode_hex(s_pub_key).expect("failed to decode ecdh hex"); // decode pub key let secret = EphemeralSecret::random(rand::thread_rng()); // gen private key let pub_key = PublicKey::from_sec1_bytes(&s_pub_key).expect("failed to get s_pub_key"); // gen public key let share = secret.diffie_hellman(&pub_key); // count public share let share_x = &share.as_bytes()[0..16]; self.initial_share_key = Bytes::copy_from_slice(&md5::compute(share_x).0); let self_public_key = secret.public_key(); let point = EncodedPoint::from(self_public_key); self.public_key = Bytes::copy_from_slice(point.as_bytes()); } } impl IEncryptMethod for EncryptECDH { fn id(&self) -> u8 { 0x87 } fn do_encrypt(&self, data: &[u8], key: &[u8]) -> Vec { let mut w = Vec::new(); w.put_u8(0x02); w.put_u8(0x01); w.put_slice(key); w.put_u16(0x01_31); w.put_u16(self.public_key_ver); w.put_u16(self.public_key.len() as u16); w.put_slice(&self.public_key); w.encrypt_and_write(&self.initial_share_key, data); w } } pub struct EncryptSession { t133: Vec, } impl EncryptSession { pub fn new(t133: &[u8]) -> EncryptSession { EncryptSession { t133: t133.to_vec(), } } } impl IEncryptMethod for EncryptSession { fn id(&self) -> u8 { 69 } fn do_encrypt(&self, data: &[u8], key: &[u8]) -> Vec { let encrypt = qqtea_encrypt(data, key); let mut w = Vec::new(); w.put_u16(self.t133.len() as u16); w.put_slice(&self.t133); w.put_slice(&encrypt); w } } #[cfg(test)] mod tests { use crate::crypto::EncryptECDH; #[test] fn test_ecdh_generate_key() { let mut e = EncryptECDH::default(); e.generate_key("04EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFBC91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E"); println!("{:?}", e.initial_share_key); println!("{:?}", e.public_key); } } ================================================ FILE: ricq-core/src/crypto/mod.rs ================================================ mod encrypt; mod qqtea; pub use self::encrypt::{EncryptECDH, EncryptSession, IEncryptMethod}; pub use self::qqtea::{qqtea_decrypt, qqtea_encrypt}; ================================================ FILE: ricq-core/src/crypto/qqtea.rs ================================================ // copy from https://github.com/zkonge/rtea use byteorder::{BigEndian, ByteOrder}; use rand::{thread_rng, RngCore}; use tea::{GenericArray, Tea16}; pub fn qqtea_encrypt(text: &[u8], key: &[u8]) -> Vec { let fill_count = 9 - (text.len() + 1) % 8; let mut plaintext = vec![0u8; 1 + fill_count + text.len() + 7]; let plaintext_len = plaintext.len(); plaintext[0] = (fill_count as u8 - 2) | 0xF8; if cfg!(debug_assertions) { //这里是为了和pytea对拍,填充220 plaintext[1..fill_count + 1].fill(220); } else { thread_rng().fill_bytes(&mut plaintext[1..fill_count + 1]); } plaintext[fill_count + 1..plaintext_len - 7].copy_from_slice(text); let mut work_block: Vec = vec![0; plaintext.len() / 8]; BigEndian::read_u64_into(&plaintext, &mut work_block); let mut iv1 = 0u64; let mut iv2 = 0u64; let mut holder: u64; let cipher = Tea16::new(GenericArray::from_slice(key)); for block in work_block.iter_mut() { holder = *block ^ iv1; iv1 = cipher.encrypt(holder); iv1 ^= iv2; iv2 = holder; *block = iv1; } BigEndian::write_u64_into(&work_block, &mut plaintext); plaintext } pub fn qqtea_decrypt(text: &[u8], key: &[u8]) -> Vec { let mut work_block: Vec = vec![0; text.len() / 8]; BigEndian::read_u64_into(text, &mut work_block); let mut iv1 = 0u64; let mut iv2 = 0u64; let mut holder: u64; let mut tmp_block: u64; let cipher = Tea16::new(GenericArray::from_slice(key)); for block in work_block.iter_mut() { tmp_block = *block ^ iv2; tmp_block = cipher.decrypt(tmp_block); iv2 = tmp_block; holder = tmp_block ^ iv1; iv1 = *block; *block = holder; } let mut result = vec![0u8; text.len()]; BigEndian::write_u64_into(&work_block, &mut result); let begin_pos = ((result[0] as usize) & 7) + 3; let end_pos = result.len() - 7; result[begin_pos..end_pos].to_owned() } mod tea { use byteorder::{BigEndian, ByteOrder}; pub use generic_array::{ typenum::{U16, U8}, GenericArray, }; const TEA_DELTA: u32 = 0x9E3779B9; pub struct Tea16 { key: [u32; 4], } impl Tea16 { #[inline] pub fn encrypt(&self, n: u64) -> u64 { let mut sum: u32 = 0; let (mut x, mut y) = ((n >> 32) as u32, n as u32); let k0 = self.key[0]; let k1 = self.key[1]; let k2 = self.key[2]; let k3 = self.key[3]; for _ in 0..16 { sum = sum.wrapping_add(TEA_DELTA); x = x.wrapping_add( k0.wrapping_add(y << 4) ^ y.wrapping_add(sum) ^ k1.wrapping_add(y >> 5), ); y = y.wrapping_add( k2.wrapping_add(x << 4) ^ x.wrapping_add(sum) ^ k3.wrapping_add(x >> 5), ); } ((x as u64) << 32) | y as u64 } #[inline] pub fn decrypt(&self, n: u64) -> u64 { let mut sum: u32 = TEA_DELTA << 4; let (mut x, mut y) = ((n >> 32) as u32, n as u32); let k0 = self.key[0]; let k1 = self.key[1]; let k2 = self.key[2]; let k3 = self.key[3]; for _ in 0..16 { y = y.wrapping_sub( k2.wrapping_add(x << 4) ^ x.wrapping_add(sum) ^ k3.wrapping_add(x >> 5), ); x = x.wrapping_sub( k0.wrapping_add(y << 4) ^ y.wrapping_add(sum) ^ k1.wrapping_add(y >> 5), ); sum = sum.wrapping_sub(TEA_DELTA); } ((x as u64) << 32) | y as u64 } #[inline] pub fn new(key: &GenericArray) -> Self { Self { key: [ BigEndian::read_u32(&key[0..4]), BigEndian::read_u32(&key[4..8]), BigEndian::read_u32(&key[8..12]), BigEndian::read_u32(&key[12..16]), ], } } } #[allow(dead_code)] pub fn tea16_encrypt(text: &mut [u8], key: &[u8]) { let key: &GenericArray = GenericArray::from_slice(key); let mut n = BigEndian::read_u64(text); n = Tea16::new(key).encrypt(n); BigEndian::write_u64(text, n); } #[allow(dead_code)] pub fn tea16_decrypt(text: &mut [u8], key: &[u8]) { let key: &GenericArray = GenericArray::from_slice(key); let mut n = BigEndian::read_u64(text); n = Tea16::new(key).decrypt(n); BigEndian::write_u64(text, n); } } ================================================ FILE: ricq-core/src/error.rs ================================================ use std::io; use thiserror::Error; pub type RQResult = Result; #[derive(Error, Debug)] pub enum RQError { #[error("other error {0}")] Other(String), #[error("failed to decode, {0}")] Decode(String), #[error("failed to decode_prost, {0}")] PbDecode(#[from] prost::DecodeError), #[error("empty field, {0}")] EmptyField(&'static str), #[error("From utf-8 error {0}")] Utf8(#[from] std::string::FromUtf8Error), #[error("command_name mismatch, expected {0} get {1}")] CommandNameMismatch(String, String), #[error("timeout error")] Timeout, #[error("network error")] Network, #[error("jce error, {0}")] Jce(#[from] jcers::JceError), #[error("io error, {0}")] IO(#[from] io::Error), #[error("unknown flag {0}")] UnknownFlag(u8), #[error("unknown encrypt type")] UnknownEncryptType, #[error("invalid packet type")] InvalidPacketType, #[error("invalid encrypt type")] InvalidEncryptType, #[error("packet dropped")] PacketDropped, #[error("session expired")] SessionExpired, #[error("unsuccessful ret code: {0}")] UnsuccessfulRetCode(i32), #[error("Token login failed")] TokenLoginFailed, #[error("failed to get file count")] GetFileCountFailed, #[error("failed to get file list: {0}")] GetFileListFailed(String), #[error("crypto invalid length: {0}")] CryptoInvalidLength(#[from] crypto_common::InvalidLength), #[error("serde_json error: {0}")] Serde(#[from] serde_json::Error), #[error("block unpad error: {0}")] BlockUnpad(#[from] block_padding::UnpadError), #[error("spki error: {0}")] Spki(#[from] spki::Error), #[error("qimei error code: {0}")] QimeiError(i64), #[error("base64 decode error: {0}")] Base64Decode(#[from] base64::DecodeError), #[error("rsa error: {0}")] RSA(#[from] rsa::Error), } ================================================ FILE: ricq-core/src/hex.rs ================================================ use std::fmt::Write; use std::num::ParseIntError; pub fn decode_hex(s: &str) -> Result, ParseIntError> { (0..s.len()) .step_by(2) .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) .collect() } pub fn encode_hex(bytes: &[u8]) -> String { let mut s = String::with_capacity(bytes.len() * 2); for &b in bytes { write!(s, "{b:02x}").expect("failed to encode_hex"); } s } #[cfg(test)] mod tests { use super::*; #[test] fn test_decode() { let h = decode_hex("04EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFBC91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E"); println!("{h:?}") } #[test] fn test_encode() { let h = encode_hex(&[1, 2, 3]); println!("{h}") } } ================================================ FILE: ricq-core/src/highway/mod.rs ================================================ use std::net::IpAddr; use std::sync::atomic::{AtomicI32, Ordering}; use bytes::Bytes; use prost::Message; use crate::command::common::PbToBytes; use crate::{pb, RQResult}; #[derive(Default)] pub struct Session { pub uin: i64, pub app_id: i32, pub sig_session: Bytes, pub session_key: Bytes, pub sso_addr: Vec, pub seq: AtomicI32, } #[derive(Default, Debug, Clone)] pub struct BdhInput { // 1-friend, 2-group, 299-groupPtt pub command_id: i32, pub ticket: Vec, pub ext: Vec, pub encrypt: bool, pub chunk_size: usize, pub send_echo: bool, } impl Session { fn next_seq(&self) -> i32 { self.seq.fetch_add(2, Ordering::Relaxed) } pub fn build_basehead( &self, command: String, dataflag: i32, command_id: i32, locale_id: i32, ) -> pb::DataHighwayHead { pb::DataHighwayHead { version: 1, uin: self.uin.to_string(), command, seq: self.next_seq(), appid: self.app_id, dataflag, command_id, locale_id, ..Default::default() } } pub fn build_seghead( &self, filesize: i64, dataoffset: i64, chunk: &[u8], ticket: Vec, file_md5: Vec, ) -> pb::SegHead { pb::SegHead { filesize, dataoffset, datalength: chunk.len() as i32, serviceticket: ticket, md5: md5::compute(chunk).to_vec(), file_md5, ..Default::default() } } pub fn build_bdh_head( &self, command_id: i32, filesize: i64, chunk: &[u8], dataoffset: i64, ticket: Vec, file_md5: Vec, ) -> Bytes { pb::ReqDataHighwayHead { msg_basehead: Some(self.build_basehead("PicUp.DataUp".into(), 4096, command_id, 2052)), msg_seghead: Some(pb::SegHead { filesize, dataoffset, datalength: chunk.len() as i32, serviceticket: ticket, md5: md5::compute(chunk).to_vec(), file_md5, ..Default::default() }), ..Default::default() } .to_bytes() } pub fn decode_rsp_head(&self, payload: Bytes) -> RQResult { pb::RspDataHighwayHead::decode(&*payload).map_err(Into::into) } pub fn build_heartbreak(&self) -> Bytes { pb::ReqDataHighwayHead { msg_basehead: Some(self.build_basehead("PicUp.Echo".into(), 4096, 0, 2052)), ..Default::default() } .to_bytes() } } ================================================ FILE: ricq-core/src/jce/mod.rs ================================================ use std::collections::HashMap; use bytes::Bytes; use jcers::{JceGet, JcePut}; macro_rules! JceStruct { ($struct_name: ident {$($tag: expr => $field: ident: $field_t: ty,)*}) => { #[derive(Debug, Clone, PartialEq, Eq, JceGet, JcePut, Default)] pub struct $struct_name { $(#[jce($tag)] pub $field: $field_t),* } }; } #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct RequestPacket { #[jce(1)] pub i_version: i16, #[jce(2)] pub c_packet_type: u8, #[jce(3)] pub i_message_type: i32, #[jce(4)] pub i_request_id: i32, #[jce(5)] pub s_servant_name: String, #[jce(6)] pub s_func_name: String, #[jce(7)] pub s_buffer: Bytes, #[jce(8)] pub i_timeout: i32, #[jce(9)] pub context: HashMap, #[jce(10)] pub status: HashMap, } JceStruct!(RequestDataVersion3 { 0 => map: HashMap, }); JceStruct!(RequestDataVersion2 { 0 => map: HashMap>, }); JceStruct!(HttpServerListRes { 2 => sso_server_infos: Vec, }); JceStruct!(SsoServerInfo { 1 => server: String, 2 => port: i32, 8 => location: String, }); JceStruct!(FileStoragePushFSSvcList { 0 => upload_list: Vec, 1 => pic_download_list: Vec, 2 => g_pic_download_list: Vec, 3 => q_zone_proxy_service_list: Vec, 4 => url_encode_service_list: Vec, 5 => big_data_channel: BigDataChannel, 6 => vip_emotion_list: Vec, 7 => c2c_pic_down_list: Vec, // 8 => fmt_ip_info: FmtIpInfo, // 9 => domain_ip_channel: DomainIpChannel, 10 => ptt_list: Bytes, }); JceStruct!(FileStorageServerInfo { 1 => server: String, 2 => port: i32, }); JceStruct!(BigDataChannel { 0 => ip_lists: Vec, 1 => sig_session: Bytes, 2 => key_session: Bytes, 3 => sig_uin: i64, 4 => connect_flag: i32, 5 => pb_buf: Bytes, }); JceStruct!(BigDataIPList { 0 => service_type: i64, 1 => ip_list: Vec, 3 => fragment_size: i64, }); JceStruct!(BigDataIPInfo { 0 => r#type: i64, 1 => server: String, 2 => port: i64, }); JceStruct!(SvcReqPullGroupMsgSeq { 0 => group_info: Vec, 1 => verify_type: u8, 2 => filter: i32, }); JceStruct!(SvcReqRegister { 0 => uin: i64, 1 => bid: i64, 2 => conn_type: u8, 3 => other: String, 4 => status: i32, 5 => online_push: u8, 6 => is_online: u8, 7 => is_show_online: u8, 8 => kick_pc: u8, 9 => kick_weak: u8, 10 => timestamp: i64, 11 => ios_version: i64, 12 => net_type: u8, 13 => build_ver: String, 14 => reg_type: u8, 15 => dev_param: Bytes , 16 => guid: Bytes, 17 => locale_id: i32, 18 => silent_push: u8, 19 => dev_name: String, 20 => dev_type: String, 21 => os_ver: String, 22 => open_push: u8, 23 => large_seq: i64, 24 => last_watch_start_time: i64, 26 => old_sso_ip: i64, 27 => new_sso_ip: i64, 28 => channel_no: String, 29 => cpid: i64, 30 => vendor_name: String, 31 => vendor_os_name: String, 32 => ios_idfa: String, 33 => b769: Bytes, 34 => is_set_status: u8, 35 => server_buf: Bytes, 36 => set_mute: u8, 38 => ext_online_status: i64, 39 => battery_status: i32, 40 => tim_active_flag:u8, 41 => bind_uin_notify_switch:u8, // 42 => stVendorPushInfo:struct, 43 => vendor_dev_id:i64, 45 => custom_status: Bytes, // 自定义状态 protobuf }); JceStruct!(SvcRespRegister { 0 => uin: i64, 1 => bid: i64, 2 => reply_code: u8, 3 => result: String, 4 => server_time: i64, 5 => log_qq: u8, 6 => need_kik: u8, 7 => update_flag: u8, 8 => timestamp: i64, 9 => crash_flag: u8, 10 => client_ip: String, 11 => client_port: i32, 12 => hello_interval: i32, 13 => large_seq: i32, 14 => large_seq_update: u8, 15 => d769_rsp_body: Bytes, 16 => status: i32, 17 => ext_online_status: i64, 18 => client_battery_get_interval: i64, 19 => client_auto_status_interval: i64, }); JceStruct!(SvcReqRegisterNew { 0 => request_optional: i64, 1 => c2c_msg: SvcReqGetMsgV2, 2 => group_msg: SvcReqPullGroupMsgSeq, 14 => dis_group_msg_filter: u8, 15 => group_mask: u8, 16 => end_seq: i64, 20 => _0769_body: Bytes, }); JceStruct!(SvcReqGetMsgV2 { 0 => uin: i64, 1 => date_time: i32, 4 => recive_pic: u8, 6 => ability: i16, 9 => channel: u8, 16 => inst: u8, 17 => channel_ex: u8, 18 => sync_cookie: Bytes, 19 => sync_flag: i32, 20 => ramble_flag: u8, 26 => general_abi: i64, 27 => pub_account_cookie: Bytes, }); JceStruct!(PullGroupSeqParam { 0 => group_code: i64, 1 => last_seq_id: i64, }); JceStruct!(SvcRespParam { 0 => pc_stat: i32, 1 => is_support_c2c_roam_msg: i32, 2 => is_support_data_line: i32, 3 => is_support_printable: i32, 4 => is_support_view_p_c_file: i32, 5 => pc_version: i32, 6 => roam_flag: i64, 7 => online_infos: Vec, 8 => pc_client_type: i32, }); JceStruct!(RequestPushNotify { 0 => uin: i64, 1 => r#type: u8, 2 => service: String, 3 => cmd: String, 4 => notify_cookie: Bytes, 5 => msg_type: i32, 6 => user_active: i32, 7 => general_flag: i32, 8 => binded_uin: i64, }); JceStruct!(OnlineInfo { 0 => instance_id: i32, 1 => client_type: i32, 2 => online_status: i32, 3 => platform_id: i32, 4 => sub_platform: Bytes, 5 => u_client_type: i64, }); JceStruct!(SvcReqMSFLoginNotify { 0 => app_id: i64, 1 => status: u8, 2 => tablet: u8, 3 => platform: i64, 4 => title: String, 5 => info: String, 6 => product_type: i64, 7 => client_type: i64, 8 => instance_list: Vec, }); JceStruct!(InstanceInfo { 0 => app_id: i32, 1 => tablet: u8, 2 => platform: i64, 3 => product_type: i64, 4 => client_type: i64, }); JceStruct!(PushMessageInfo { 0 => from_uin: i64, 1 => msg_time: i64, 2 => msg_type: i16, 3 => msg_seq: i16, 4 => msg: String, 5 => real_msg_time: i32, 6 => v_msg: Bytes, 7 => app_share_id: i64, 8 => msg_cookies: Bytes, 9 => app_share_cookie: Bytes, 10 => msg_uid: i64, 11 => last_change_time: i64, 14 => from_inst_id: i64, 15 => remark_of_sender: Bytes, 16 => from_mobile: String, 17 => from_name: String, }); JceStruct!(SvcRespPushMsg { 0 => uin: i64, 1 => del_infos: Vec, 2 => svrip: i32, 3 => push_token: Bytes, 4 => service_type: i32, }); JceStruct!(SvcReqGetDevLoginInfo { 0 => guid: Bytes, 1 => app_name: String, 2 => login_type: i64, 3 => timestamp: i64, 4 => next_item_index: i64, 5 => require_max: i64, 6 => get_dev_list_type: i64, // 1: getLoginDevList 2: getRecentLoginDevList 4: getAuthLoginDevList }); JceStruct!(SvcDevLoginInfo { 0 => app_id: i64, 1 => guid: Bytes, 2 => login_time: i64, 3 => login_platform: i64, 4 => login_location: String, 5 => device_name: String, 6 => device_type_info: String, 8 => ter_type: i64, 9 => product_type: i64, 10 => can_be_kicked: i64, }); JceStruct!(DelMsgInfo { 0 => from_uin: i64, 1 => msg_time: i64, 2 => msg_seq: i16, 3 => msg_cookies: Bytes, 4 => cmd: i16, 5 => msg_type: i64, 6 => app_id: i64, 7 => send_time: i64, 8 => sso_seq: i32, 9 => sso_ip: i32, 10 => client_ip: i32, }); // 下面是生成的 // // JceStruct!(RequestPacket { // 1 => i_version: i16 // 2 => c_packet_type: u8 // 3 => i_message_type: i32 // 4 => i_request_id: i32 // 5 => s_servant_name: String // 6 => s_func_name: String // 7 => s_buffer: Bytes // 8 => i_timeout: i32 // 9 => context: map[String]String // 10 => status: map[String]String // }); // // JceStruct!(RequestDataVersion3 { // 0 => map: map[String]Bytes // }); // // JceStruct!(RequestDataVersion2 { // 0 => map: map[String]map[String]Bytes // }); // // JceStruct!(SsoServerInfo { // 1 => server: String // 2 => port: i32 // 8 => location: String // }); // // JceStruct!(FileStoragePushFSSvcList { // 0 => upload_list: []FileStorageServerInfo // 1 => pic_download_list: []FileStorageServerInfo // 2 => g_pic_download_list: []FileStorageServerInfo // 3 => q_zone_proxy_service_list: []FileStorageServerInfo // 4 => url_encode_service_list: []FileStorageServerInfo // 5 => big_data_channel: BigDataChannel // 6 => vip_emotion_list: []FileStorageServerInfo // 7 => c2CPicDownList: []FileStorageServerInfo // 10 => ptt_list: Bytes // }); // // JceStruct!(FileStorageServerInfo { // 1 => server: String // 2 => port: i32 // }); // // JceStruct!(BigDataChannel { // 0 => i_p_lists: []BigDataIPList // 1 => sig_session: Bytes // 2 => key_session: Bytes // 3 => sig_uin: i64 // 4 => connect_flag: i32 // 5 => pb_buf: Bytes // }); // // JceStruct!(BigDataIPList { // 0 => service_type: i64 // 1 => i_p_list: []BigDataIPInfo // 3 => fragment_size: i64 // }); // // JceStruct!(BigDataIPInfo { // 0 => type: i64 // 1 => server: String // 2 => port: i64 // }); // // JceStruct!(SvcReqRegister { // // 0 => uin: i64 // 1 => bid: i64 // 2 => conn_type: u8 // 3 => other: String // 4 => status: i32 // 5 => online_push: u8 // 6 => is_online: u8 // 7 => is_show_online: u8 // 8 => kick_p_c: u8 // 9 => kick_weak: u8 // 10 => timestamp: i64 // 11 => i_o_s_version: i64 // 12 => net_type: u8 // 13 => build_ver: String // 14 => reg_type: u8 // 15 => dev_param: Bytes // 16 => guid: Bytes // 17 => locale_id: i32 // 18 => silent_push: u8 // 19 => dev_name: String // 20 => dev_type: String // 21 => o_s_ver: String // 22 => open_push: u8 // 23 => large_seq: i64 // 24 => last_watch_start_time: i64 // 26 => old_s_s_o_ip: i64 // 27 => new_s_s_o_ip: i64 // 28 => channel_no: String // 29 => c_p_i_d: i64 // 30 => vendor_name: String // 31 => vendor_o_s_name: String // 32 => i_o_s_idfa: String // 33 => b769: Bytes // 34 => is_set_status: u8 // 35 => server_buf: Bytes // 36 => set_mute: u8 // 38 => ext_online_status: i64 // 39 => battery_status: i32 // }); // // JceStruct!(SvcRespRegister { // 0 => uin: i64 // 1 => bid: i64 // 2 => reply_code: u8 // 3 => result: String // 4 => server_time: i64 // 5 => log_q_q: u8 // 6 => need_kik: u8 // 7 => update_flag: u8 // 8 => timestamp: i64 // 9 => crash_flag: u8 // 10 => client_ip: String // 11 => client_port: i32 // 12 => hello_interval: i32 // 13 => large_seq: i32 // 14 => large_seq_update: u8 // 15 => d769RspBody: Bytes // 16 => status: i32 // 17 => ext_online_status: i64 // 18 => client_battery_get_interval: i64 // 19 => client_auto_status_interval: i64 // }); // // JceStruct!(SvcReqRegisterNew { // // 0 => request_optional: i64 // 1 => c2CMsg: // SvcReqGetMsgV2 // 2 => group_msg: // SvcReqPullGroupMsgSeq // 14 => dis_group_msg_filter: u8 // 15 => group_mask: u8 // 16 => end_seq: i64 // 20 => o769Body: Bytes // }); // // JceStruct!(SvcReqGetMsgV2 { // // 0 => uin: i64 // 1 => date_time: i32 // 4 => recive_pic: u8 // 6 => ability: i16 // 9 => channel: u8 // 16 => inst: u8 // 17 => channel_ex: u8 // 18 => sync_cookie: Bytes // 19 => sync_flag: int // 20 => ramble_flag: u8 // 26 => general_abi: i64 // 27 => pub_account_cookie: Bytes // }); // // JceStruct!(SvcReqPullGroupMsgSeq { // // 0 => group_info: [] // PullGroupSeqParam // 1 => verify_type: u8 // 2 => filter: i32 // }); // // JceStruct!(PullGroupSeqParam { // // 0 => group_code: i64 // 1 => last_seq_id: i64 // }); // // JceStruct!(SvcRespParam { // 0 => p_c_stat: i32 // 1 => is_support_c2CRoamMsg: i32 // 2 => is_support_data_line: i32 // 3 => is_support_printable: i32 // 4 => is_support_view_p_c_file: i32 // 5 => pc_version: i32 // 6 => roam_flag: i64 // 7 => online_infos: []OnlineInfo // 8 => p_c_client_type: i32 // }); // // JceStruct!(RequestPushNotify { // 0 => uin: i64 // 1 => type: u8 // 2 => service: String // 3 => cmd: String // 4 => notify_cookie: Bytes // 5 => msg_type: i32 // 6 => user_active: i32 // 7 => general_flag: i32 // 8 => binded_uin: i64 // }); // // JceStruct!(OnlineInfo { // 0 => instance_id: i32 // 1 => client_type: i32 // 2 => online_status: i32 // 3 => platform_id: i32 // 4 => sub_platform: String // 5 => u_client_type: i64 // }); // // JceStruct!(SvcReqMSFLoginNotify { // 0 => app_id: i64 // 1 => status: u8 // 2 => tablet: u8 // 3 => platform: i64 // 4 => title: String // 5 => info: String // 6 => product_type: i64 // 7 => client_type: i64 // 8 => instance_list: []InstanceInfo // }); // // JceStruct!(InstanceInfo { // 0 => app_id: i32 // 1 => tablet: u8 // 2 => platform: i64 // 3 => product_type: i64 // 4 => client_type: i64 // }); // // JceStruct!(PushMessageInfo { // 0 => from_uin: i64 // 1 => msg_time: i64 // 2 => msg_type: i16 // 3 => msg_seq: i16 // 4 => msg: String // 5 => real_msg_time: i32 // 6 => v_msg: Bytes // 7 => app_share_i_d: i64 // 8 => msg_cookies: Bytes // 9 => app_share_cookie: Bytes // 10 => msg_uid: i64 // 11 => last_change_time: i64 // 14 => from_inst_id: i64 // 15 => remark_of_sender: Bytes // 16 => from_mobile: String // 17 => from_name: String // }); // // JceStruct!(SvcRespPushMsg { // // 0 => uin: i64 // 1 => del_infos: [] // 2 => svrip: i32 // 3 => push_token: Bytes // 4 => service_type: i32 // }); // // JceStruct!(SvcReqGetDevLoginInfo { // // 0 => guid: Bytes // 1 => app_name: String // 2 => login_type: i64 // 3 => timestamp: i64 // 4 => next_item_index: i64 // 5 => require_max: i64 // 6 => get_dev_list_type: i64 // 1: getLoginDevList 2: getRecentLoginDevList 4: getAuthLoginDevList // }); // // JceStruct!(SvcDevLoginInfo { // AppId i64 // Guid Bytes // LoginTime i64 // LoginPlatform i64 // LoginLocation String // DeviceName String // DeviceTypeInfo String // TerType i64 // ProductType i64 // CanBeKicked i64 // }); // // JceStruct!(DelMsgInfo { // // 0 => from_uin: i64 // 1 => msg_time: i64 // 2 => msg_seq: i16 // 3 => msg_cookies: Bytes // 4 => cmd: i16 // 5 => msg_type: i64 // 6 => app_id: i64 // 7 => send_time: i64 // 8 => sso_seq: i32 // 9 => sso_ip: i32 // 10 => client_ip: i32 // }); // JceStruct!(FriendListRequest { 0 => reqtype: i32, 1 => if_reflush: u8, 2 => uin: i64, 3 => start_index: i16, 4 => friend_count: i16, 5 => group_id: u8, 6 => if_get_group_info: u8, 7 => group_start_index: u8, 8 => group_count: u8, 9 => if_get_msf_group: u8, 10 => if_show_term_type: u8, 11 => version: i64, 12 => uin_list: Vec, 13 => app_type: i32, 14 => if_get_dov_id: u8, 15 => if_get_both_flag: u8, 16 => d50: Bytes, 17 => d6b: Bytes, 18 => sns_type_list: Vec, }); /// 获取好友列表 response #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct FriendListResponse { #[jce(5)] pub total_friend_count: i16, #[jce(7)] pub friend_info_list: Vec, #[jce(14)] pub group_info_list: Vec, #[jce(17)] pub online_friend_count: i16, } /// 好友列表分组信息 #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct FriendListGroupInfo { #[jce(0)] pub group_id: u8, #[jce(1)] pub group_name: String, #[jce(2)] pub friend_count: i32, #[jce(3)] pub online_friend_count: i32, #[jce(4)] pub seq_id: u8, } /// 好友列表-修改分组请求 #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct FriendListSetGroupReq { #[jce(0)] pub req_type: i32, #[jce(1)] pub uin: i64, #[jce(2)] pub body: Bytes, } /// 获取签名request #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct GetRichSigReq { #[jce(1)] pub req_rich_infos: Vec, #[jce(2)] pub check_update: bool, // false #[jce(3)] pub show_date_sig: bool, // false #[jce(4)] pub get_large_tlv: bool, // true } #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct ReqRichInfo { #[jce(1)] pub uin: i64, #[jce(2)] pub dw_time: i64, // 0 } /// 获取签名response #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct GetRichSigRes { #[jce(1)] pub result: u8, #[jce(2)] pub sig_infos: Vec, } #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct ResRichSigInfo { #[jce(1)] pub status: u8, #[jce(2)] pub uin: i64, #[jce(3)] pub dw_time: i64, #[jce(4)] pub sig_info: Bytes, } JceStruct!(FriendInfo { 0 => friend_uin: i64, 1 => group_id: u8, 2 => face_id: i16, 3 => remark: String, 4 => qq_type: u8, 5 => status: u8, 6 => member_level: u8, 7 => is_mqq_online: u8, 8 => qq_online_state: u8, 9 => is_iphone_online: u8, 10 => detail_status_flag: u8, 11 => qq_online_state_v2: u8, 12 => show_name: String, 13 => is_remark: u8, 14 => nick: String, 15 => special_flag: u8, 16 => im_group_id: Bytes, 17 => msf_group_id: Bytes, 18 => term_type: i32, 20 => network: u8, 21 => ring: Bytes, 22 => abi_flag: i64, 23 => face_addon_id: i64, 24 => network_type: i32, 25 => vip_font: i64, 26 => icon_type: i32, 27 => term_desc: String, 28 => color_ring: i64, 29 => apollo_flag: u8, 30 => apollo_timestamp: i64, 31 => sex: u8, 32 => founder_font: i64, 33 => eim_id: String, 34 => eim_mobile: String, 35 => olympic_torch: u8, 36 => apollo_sign_time: i64, 37 => lavi_uin: i64, 38 => tag_update_time: i64, 39 => game_last_login_time: i64, 40 => game_app_id: i64, 41 => card_id: Bytes, 42 => bit_set: i64, 43 => king_of_glory_flag: u8, 44 => king_of_glory_rank: i64, 45 => master_uin: String, 46 => last_medal_update_time: i64, 47 => face_store_id: i64, 48 => font_effect: i64, 49 => d_ov_id: String, 50 => both_flag: i64, 51 => centi_show_3d_flag: u8, 52 => intimate_info: Bytes, 53 => show_nameplate: u8, 54 => new_lover_diamond_flag: u8, 55 => ext_sns_frd_data: Bytes, 56 => mutual_mark_data: Bytes, }); JceStruct!(TroopListRequest { 0 => uin: i64, 1 => get_msf_msg_flag: u8, 2 => cookies: Bytes, 3 => group_info: Vec, 4 => group_flag_ext: u8, 5 => version: i32, 6 => company_id: i64, 7 => version_num: i64, 8 => get_long_group_name: u8, }); JceStruct!(TroopNumber { 0 => group_uin: i64, 1 => group_code: i64, 2 => flag: u8, 3 => group_info_seq: i64, 4 => group_name: String, 5 => group_memo: String, 6 => group_flag_ext: i64, 7 => group_rank_seq: i64, 8 => certification_type: i64, 9 => shut_up_timestamp: i64, 10 => my_shut_up_timestamp: i64, 11 => cmd_uin_uin_flag: i64, 12 => additional_flag: i64, 13 => group_type_flag: i64, 14 => group_sec_type: i64, 15 => group_sec_type_info: i64, 16 => group_class_ext: i64, 17 => app_privilege_flag: i64, 18 => subscription_uin: i64, 19 => member_num: i64, 20 => member_num_seq: i64, 21 => member_card_seq: i64, 22 => group_flag_ext3: i64, 23 => group_owner_uin: i64, 24 => is_conf_group: u8, 25 => is_modify_conf_group_face: u8, 26 => is_modify_conf_group_name: u8, 27 => cmd_uin_join_time: i64, 28 => company_id: i64, 29 => max_group_member_num: i64, 30 => cmd_uin_group_mask: i64, 31 => guild_app_id: i64, 32 => guild_sub_type: i64, 33 => cmd_uin_ringtone_i_d: i64, 34 => cmd_uin_flag_ex2: i64, }); JceStruct!(TroopMemberListRequest { 0 => uin: i64, 1 => group_code: i64, 2 => next_uin: i64, 3 => group_uin: i64, 4 => version: i64, 5 => req_type: i64, 6 => get_list_appoint_time: i64, 7 => rich_card_name_ver: u8, }); JceStruct!(TroopMemberInfo { 0 => member_uin: i64, 1 => face_id: i16, 2 => age: u8, 3 => gender: u8, 4 => nick: String, 5 => status: u8, 6 => show_name: String, 8 => name: String, 12 => memo: String, 13 => auto_remark: String, 14 => member_level: i64, 15 => join_time: i64, 16 => last_speak_time: i64, 17 => credit_level: i64, 18 => flag: i64, 19 => flag_ext: i64, 20 => point: i64, 21 => concerned: u8, 22 => shielded: u8, 23 => special_title: String, 24 => special_title_expire_time: i64, 25 => job: String, 26 => apollo_flag: u8, 27 => apollo_timestamp: i64, 28 => global_group_level: i64, 29 => title_id: i64, 30 => shut_up_timestap: i64, 31 => global_group_point: i64, 33 => rich_card_name_ver: u8, 34 => vip_type: i64, 35 => vip_level: i64, 36 => big_club_level: i64, 37 => big_club_flag: i64, 38 => nameplate: i64, 39 => group_honor: Bytes, }); JceStruct!(ModifyGroupCardRequest { 0 => zero: i64, 1 => group_code: i64, 2 => new_seq: i64, 3 => uin_info: Vec, }); JceStruct!(UinInfo { 0 => uin: i64, 1 => flag: i64, 2 => name: String, 3 => gender: u8, 4 => phone: String, 5 => email: String, 6 => remark: String, }); JceStruct!(SummaryCardReq { 0 => uin: i64, 1 => come_from: i32, 2 => qzone_feed_timestamp: i64, 3 => is_friend: u8, 4 => group_code: i64, 5 => group_uin: i64, 8 => get_control: i64, 9 => add_friend_source: i32, 10 => secure_sig: Bytes, 14 => req_services: Vec, // todo 15 => tiny_id: i64, 16 => like_source: i64, 18 => req_medal_wall_info: u8, 19 => req_0x5eb_field_id: Vec, 20 => req_nearby_god_info: u8, 22 => req_extend_card: u8, }); #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct RespSummaryCard { #[jce(1)] pub sex: u8, #[jce(2)] pub age: u8, #[jce(3)] pub nickname: String, #[jce(5)] pub level: i32, #[jce(7)] pub city: String, #[jce(8)] pub sign: String, #[jce(11)] pub mobile: String, #[jce(23)] pub uin: i64, #[jce(36)] pub login_days: i64, } #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct RespSummaryCardHead { #[jce(0)] pub sex: i32, #[jce(1)] pub age: i32, #[jce(2)] pub err_str: String, #[jce(3)] pub cookie: Bytes, } JceStruct!(SummaryCardReqSearch { 0 => keyword: String, 1 => country_code: String, 2 => version: i32, 3 => req_services: Vec, // todo // busi }); JceStruct!(DelFriendReq { 0 => uin: i64, 1 => del_uin: i64, 2 => del_type: u8, 3 => version: i32, }); JceStruct!(DelFriendResp{ 0 => uin : i64, 1 => del_uin : i64, 2 => result : i32, 3 => error_code : i16, }); #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct QQServiceReqHead { #[jce(0)] pub uin: i64, #[jce(1)] pub sh_version: i16, #[jce(2)] pub seq: i32, #[jce(3)] pub req_type: u8, #[jce(4)] pub triggered: u8, #[jce(5)] pub cookies: Bytes, } #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct ReqFavorite { #[jce(0)] pub header: QQServiceReqHead, #[jce(1)] pub mid: i64, #[jce(2)] pub op_type: i32, #[jce(3)] pub source: i32, #[jce(4)] pub count: i32, } #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct MsgType0x210 { #[jce(0)] pub sub_msg_type: i64, #[jce(10)] pub v_protobuf: Bytes, } #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct RequestPushForceOffline { #[jce(0)] pub uin: i64, #[jce(1)] pub title: String, #[jce(2)] pub tips: String, #[jce(3)] pub same_device: u8, } #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct RequestMSFForceOffline { #[jce(0)] pub uin: i64, #[jce(1)] pub seq_no: i64, #[jce(2)] pub kick_type: u8, #[jce(3)] pub info: String, #[jce(4)] pub title: String, #[jce(5)] pub sig_kick: u8, #[jce(6)] pub sig_kick_data: Bytes, #[jce(7)] pub same_device: u8, } #[derive(Debug, Clone, JceGet, JcePut, Default)] pub struct RspMSFForceOffline { #[jce(0)] pub uin: i64, #[jce(1)] pub seq_no: i64, #[jce(2)] pub const_zero: u8, } #[cfg(test)] mod tests { use bytes::*; use crate::crypto::qqtea_decrypt; use crate::hex::decode_hex; use super::*; #[test] fn sso_address_resp_decode() { static SSO_ADDRESS_RESP: &str = "6e477b1c09e193f1d6084a1e8a052c259f6cf4e608e614d5db262ba96fceb6e8a5d19d72860d0db5dfac554cb7de435e4400f9cd0e8b14f98d4020962c496393e6947f85adcaf00656782b9512713177b41d8489ddf8952766c9850639e329905911aa989d618be1e14b133d228f187efd0ae3a8bdec00c6028078862e965940ad8acc937c5522abf967de737632f19d4f27b5bdf34a2003b11d8547cd4f82c7f1fcb8ea8219ada296ca5ed38bc38b4bbb475f51974dffb85daf1a12b3be2d853ff7877eba722148612abe85535492dd955ae9ff2b6cd9bb570711acc0869cdab62f0aa7fb1caa9862abd1e3aca39a96a9b45116cc92a065c2736420cab691e540534307dcc2d872da82c35b03f7a94b0a9bd6fbf73caa002702c10c9af38616ed9e6c54912de021ace4d969ca264d7f9d94ea4913a1a2184e77b9a1bc850c38d7de55b82e21c2f0e45e0e12ab602c54641b20409d1013245f79ec151e1ee773b9cca9f6d172084d1a125b9ff0e3d5efac8f0ad9e4cdec94f9366e6346274b6c9994ae62f35060c961948b5aa23eb2402c008603fa2e416d7803c9e466a9658d5e3470abda44c9b00c985ee28ae01e2f837666b8b8c5fb5fdfd263fa4173c948cf282efcd779fc15e21dbc29d4fa826d92d75bd7ebf19036c621d2fb96e6940fcab78ebea48da089893374b67d8341a10d86c33a182a9354268a7a7e168bd5c4a33e8b6110cd7e838c9bf53b8869ddbe747daf94183627e8dab6f71c67f71c621eddce055eb20803c962433baa1c7b0d1caf8a8b8b10ed3d429adaa38afaf1effab224ea16454e6e99b35dbcc55bfa972b8511ca1dee7af4485e45a4c0b5a6940168678dc0471420e4d9c401a514f12423972d6deb28192e7e9f74987282a67c4e1ea7e987ebbac989a72c85faac5d2648a284dfdb9374dfd17887c4b31b3e676436bbd47c5a4258406552187f2ca3586c58b1187c979c9303561fd4308646f30908d8abb19bae79d493100c034e6b34b2cd810ddb700c1a2d5418f556a8c46ed8912b1ea507d289c350e0ac86e2b80cee31263c10f431353d049890e7bbb732fed29353e75c4172193f45c7260157ee6f5cf84f11c3183f9889e30e239cf2fae16f1d65f14b519f407f848bd014c7b95d61ef6393b137b6989979641af5325f53411090c54164148ce3f732e0cdaac01de3b8585594d1d2e8f76723d995497d2c314f377efbc3974363d031e5de9fa4799a42b4acc28f7b2834203a1f3fa00510604801c7777ae1f62e89e08b6a0248d46a2c055e93458498077b175a2f08313ee373d42b861a727935be574838cdc15e654c6a6d01143d23d072bf7ea93e1094d4c5330a1ee3ea421f78127b60ea65dc9628a4e2eb440f21daaa514d30dde9ba5eea69c19a8ae8c65b60bad64859692dcf9c947b99333c31b21418e2984b178ee61011c0a3cec9d97abc4511ee96599e49fa8dc7b27d38d1cac74071c0b840db24d6ad4f2923622f7482ca0de4dc35698b69bbb3692d85f698f629e1476ac54a35502098ebf607ec1ad0d053745c8685e5fa69f91694163ab89c3391b7e0856239486a3206465e521364b4de7d5e5e2124e8c4a2bf9ca140f2af2c65b5e5c0a29f2cbcaaf0cea569250c1ce9c9356f1ad0ace6c9f729319cd61b701d5b8ccdd4509bfed9c2dbdcb7f6e47c97570b6914b43fd14798337dd997ce6d3d520d3db2a1fb0d50730f89a96df64d5f383c7889c405bb28e261f56a4f22c8dd8d5c95ac5d8653bdaa67e826d0e4214559d70e5f82ce0846970399b11e875c08571165b16029c5c575bb21316c19269ffac6db84efb2aa35b1c877bd824e8d00d18aaf4a7dfec14d8bfa3a92029936aea0dd4eb9c3c8b3c633d2b59339ea5865997c5e52facb546a222fa27e9ec00063a620ab44550241f3a115fc6251ad8b244a3588d400ee18ca0769d2ae64e09b8a6c7e439a856093087930e442d036e6bcfeee0c7b6fa908ab1c6906552cf0cd6341ac14bc04ef45c4bd558d45fa51b0ca3465a9c681fbe24fec1d231a73080b067caa5ac0641e6da3e3d105bf6491752e308c026e94e616582150d3a8a52a7bac25d3880c8324c18a92bcdc73c1dfa7f3591754f8944ac6c32d86163edff93de1d3e435367714c390bcb6ee741032e1981450112a1a1303969008c5170c0774dd7a61952778ff2bd6abab52834b7e5fa1d4c2ae47026e4072bea7d4fa359c25b36658c0372b11ad1c5723fb9308ce31ec4a4e0c38244fdfe4918079"; // static DE_DATA: &'static str = "0000063710032c3c4c560a436f6e666967487474706611487474705365727665724c6973745265737d000106050800010611487474705365727665724c6973745265731d000105ea0a100129000b0a160e3130392e3234342e3139382e3134211f9030014c5c600870018602737a96066f74686572730b0a160f3138302e3130322e3131312e313035211f9030014c5c6008700186027368960374656c0b0a160c3131332e39362e31322e3835211f9030014c5c600870018602737a960374656c0b0a160b31342e32322e332e3132322101bb30014c5c600870018602737a960374656c0b0a160d34322e38312e3139332e323530205030014c5c600870018602746a960374656c0b0a160e3131342e3232312e3134382e34392136b030014c5c6008700186027368960374656c0b0a160c3131332e39362e31332e39352101bb30014c5c600870018602737a960374656c0b0a160d34322e38312e3139322e323131211f9030014c5c600870018602746a960374656c0b0a160c3130312e39312e34322e3938205030014c5c6008700186027368960374656c0b0a16116d7366776966692e33672e71712e636f6d211f9030014c5c60087c86066f746865727396066f74686572730b0a160d34322e38312e3137322e323037205030014c5c600870018602746a960374656c0b39000b0a160e3130392e3234342e3139382e3134211f9030014c5c600870018602737a96066f74686572730b0a160f3138302e3130322e3131312e313035211f9030014c5c6008700186027368960374656c0b0a160c3131332e39362e31322e3835211f9030014c5c600870018602737a960374656c0b0a160b31342e32322e332e3132322101bb30014c5c600870018602737a960374656c0b0a160d34322e38312e3139332e323530205030014c5c600870018602746a960374656c0b0a160e3131342e3232312e3134382e34392136b030014c5c6008700186027368960374656c0b0a160c3131332e39362e31332e39352101bb30014c5c600870018602737a960374656c0b0a160d34322e38312e3139322e323131211f9030014c5c600870018602746a960374656c0b0a160c3130312e39312e34322e3938205030014c5c6008700186027368960374656c0b0a16116d7366776966692e33672e71712e636f6d211f9030014c5c60087c86066f746865727396066f74686572730b0a160d34322e38312e3137322e323037205030014c5c600870018602746a960374656c0b426155ae2b5138406c7c80029005acbcc900080a160e3130392e3234342e3132392e3135205030014c500360087c8602737a96066f74686572730b0a160e3131342e3232312e3134342e3232205030014c500360087c86027368960374656c0b0a160c3131332e39362e31332e3434205030014c500360087c8602737a960374656c0b0a160e3131392e3134372e3139302e3337205030014c500360087c8602737a960374656c0b0a160d34322e38312e3139332e323432205030014c500360087c8602746a960374656c0b0a160d3138302e3130322e35392e3530205030014c500360087c86027368960374656c0b0a160d34322e38312e3136392e313035205030014c500360087c8602746a960374656c0b0a160d34322e38312e3136392e313035205030014c500360087c8602746a960374656c0bd900080a160e3130392e3234342e3132392e3135205030014c500360087c8602737a96066f74686572730b0a160e3131342e3232312e3134342e3232205030014c500360087c86027368960374656c0b0a160c3131332e39362e31332e3434205030014c500360087c8602737a960374656c0b0a160e3131392e3134372e3139302e3337205030014c500360087c8602737a960374656c0b0a160d34322e38312e3139332e323432205030014c500360087c8602746a960374656c0b0a160d3138302e3130322e35392e3530205030014c500360087c86027368960374656c0b0a160d34322e38312e3136392e313035205030014c500360087c8602746a960374656c0b0a160d34322e38312e3136392e313035205030014c500360087c8602746a960374656c0bed000cf90f0cf9100cf9110cf01202f113ff38f61428323032312d30392d33302031363a33313a33392064656c6976657279696e67206120706f6c696379fc150b8c980ca80c"; let key = decode_hex("F0441F5FF42DA58FDCF7949ABA62D411").expect("failed to decode hex"); let data = decode_hex(SSO_ADDRESS_RESP).expect("failed to decode_hex"); let mut de_rsp = Bytes::from(qqtea_decrypt(&data, &key)); de_rsp.advance(4); let mut request_packet: RequestPacket = jcers::from_buf(&mut de_rsp).expect("failed to decode RequestPacket"); let mut request_data_version3: RequestDataVersion3 = jcers::from_buf(&mut request_packet.s_buffer) .expect("failed to decode RequestDataVersion3"); let sso_server_infos: HttpServerListRes = jcers::from_buf( request_data_version3 .map .get_mut("HttpServerListRes") .expect("failed to get HttpServerListRes"), ) .expect("failed to parse jce"); for s in sso_server_infos.sso_server_infos { println!("Get Addrs server:{} port:{}", s.server, s.port); } } } ================================================ FILE: ricq-core/src/lib.rs ================================================ #![feature(impl_trait_in_assoc_type)] #![feature(type_alias_impl_trait)] #![feature(return_position_impl_trait_in_trait)] use std::sync::atomic::{AtomicI32, AtomicI64, AtomicU16, Ordering}; use bytes::Bytes; use rand::Rng; pub use error::{RQError, RQResult}; use protocol::device::Device; use protocol::oicq; use protocol::transport::Transport; use protocol::version::Version; pub use crate::token::Token; pub mod binary; pub mod command; pub mod common; pub mod crypto; pub mod error; pub mod hex; pub mod highway; pub mod jce; pub mod msg; pub mod pb; pub mod protocol; pub mod structs; pub mod token; mod utils; pub mod wtlogin; // build_packet: param -> bytes // decode_packet: bytes -> struct // this should be wrapped in a rwlock (readonly after login) // TODO: build library for other language // no async and await pub struct Engine { pub uin: AtomicI64, pub transport: Transport, pub seq_id: AtomicU16, pub request_packet_request_id: AtomicI32, pub group_seq: AtomicI32, pub friend_seq: AtomicI32, pub group_data_trans_seq: AtomicI32, pub highway_apply_up_seq: AtomicI32, } impl Engine { pub fn new(device: Device, version: Version) -> Self { Self { uin: AtomicI64::new(0), transport: Transport::new(device, version), seq_id: AtomicU16::new(0x3635), request_packet_request_id: AtomicI32::new(1921334513), group_seq: AtomicI32::new(rand::thread_rng().gen_range(0..20000)), friend_seq: AtomicI32::new(rand::thread_rng().gen_range(0..20000)), group_data_trans_seq: AtomicI32::new(rand::thread_rng().gen_range(0..20000)), highway_apply_up_seq: AtomicI32::new(rand::thread_rng().gen_range(0..20000)), } } pub fn uin(&self) -> i64 { self.uin.load(Ordering::Relaxed) } pub fn next_seq(&self) -> u16 { self.seq_id.fetch_add(1, Ordering::Relaxed) } pub fn next_packet_seq(&self) -> i32 { self.request_packet_request_id .fetch_add(2, Ordering::Relaxed) } pub fn next_group_seq(&self) -> i32 { self.group_seq.fetch_add(2, Ordering::Relaxed) } pub fn next_friend_seq(&self) -> i32 { self.friend_seq.fetch_add(2, Ordering::Relaxed) } pub fn next_group_data_trans_seq(&self) -> i32 { self.group_data_trans_seq.fetch_add(2, Ordering::Relaxed) } pub fn next_highway_apply_seq(&self) -> i32 { self.highway_apply_up_seq.fetch_add(2, Ordering::Relaxed) } pub fn gen_token(&self) -> Token { Token { uin: self.uin(), d2: self.transport.sig.d2.to_vec(), d2key: self.transport.sig.d2key.to_vec(), tgt: self.transport.sig.tgt.to_vec(), srm_token: self.transport.sig.srm_token.to_vec(), t133: self.transport.sig.t133.to_vec(), encrypted_a1: self.transport.sig.encrypted_a1.to_vec(), out_packet_session_id: self.transport.sig.out_packet_session_id.to_vec(), tgtgt_key: self.transport.sig.tgtgt_key.to_vec(), wt_session_ticket_key: self.transport.oicq_codec.wt_session_ticket_key.to_vec(), } } pub fn load_token(&mut self, token: Token) { self.uin.store(token.uin, Ordering::Relaxed); self.transport.sig.d2 = Bytes::from(token.d2); self.transport.sig.d2key = Bytes::from(token.d2key); self.transport.sig.tgt = Bytes::from(token.tgt); self.transport.sig.srm_token = Bytes::from(token.srm_token); self.transport.sig.t133 = Bytes::from(token.t133); self.transport.sig.encrypted_a1 = Bytes::from(token.encrypted_a1); self.transport.sig.out_packet_session_id = Bytes::from(token.out_packet_session_id); self.transport.sig.tgtgt_key = Bytes::from(token.tgtgt_key); self.transport.oicq_codec.wt_session_ticket_key = Bytes::from(token.wt_session_ticket_key); } } ================================================ FILE: ricq-core/src/msg/elem/anonymous.rs ================================================ use crate::msg::{MessageChainBuilder, MessageElem, PushBuilder}; use crate::pb::msg; use crate::pb::msg::AnonymousGroupMessage; #[derive(Default, Debug, Clone)] pub struct Anonymous { // 用于禁言 pub anon_id: Vec, pub nick: String, pub portrait_index: i32, pub bubble_index: i32, pub expire_time: i32, pub color: String, } impl From for MessageElem { fn from(e: Anonymous) -> Self { MessageElem::AnonGroupMsg(msg::AnonymousGroupMessage { flags: Some(2), anon_id: None, anon_nick: Some(e.nick.into_bytes()), head_portrait: Some(e.portrait_index), expire_time: Some(e.expire_time), bubble_id: Some(e.bubble_index), rank_color: Some(e.color.into_bytes()), }) } } impl PushBuilder for Anonymous { fn push_builder(elem: Self, builder: &mut MessageChainBuilder) { builder.elems.insert(0, elem.into()); } } impl From for Anonymous { fn from(e: AnonymousGroupMessage) -> Self { Self { anon_id: e.anon_id.unwrap_or_default(), nick: String::from_utf8_lossy(&e.anon_nick.unwrap_or_default()).into_owned(), portrait_index: e.head_portrait.unwrap_or_default(), bubble_index: e.bubble_id.unwrap_or_default(), expire_time: e.expire_time.unwrap_or_default(), color: String::from_utf8_lossy(&e.rank_color.unwrap_or_default()).into_owned(), } } } ================================================ FILE: ricq-core/src/msg/elem/at.rs ================================================ use std::fmt; use bytes::{Buf, BufMut}; use crate::msg::{MessageChainBuilder, PushBuilder}; use crate::msg::{MessageElem, PushElem}; use crate::pb::msg; use crate::{push_builder_impl, to_elem_vec_impl}; #[derive(Default, Debug, Clone)] pub struct At { pub target: i64, pub display: String, } impl At { pub fn new(target: i64) -> Self { Self { target, display: format!("@{target}"), } } } impl PushElem for At { fn push_to(elem: Self, vec: &mut Vec) { vec.push(MessageElem::Text(msg::Text { attr6_buf: Some({ let mut w = Vec::new(); w.put_u16(1); w.put_u16(0); w.put_u16(elem.display.chars().count() as u16); w.put_u8(if elem.target == 0 { 1 } else { 0 }); w.put_u32(elem.target as u32); w.put_u16(0); w }), str: Some(elem.display), ..Default::default() })); } } impl From for At { fn from(e: msg::Text) -> Self { let (_, mut attr6) = e.attr6_buf().split_at(7); let target = attr6.get_u32() as i64; Self { target, display: e.str.unwrap_or_default(), } } } impl fmt::Display for At { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{}]", self.display) } } to_elem_vec_impl!(At); push_builder_impl!(At); ================================================ FILE: ricq-core/src/msg/elem/face.rs ================================================ use std::fmt; use prost::Message; use crate::msg::{MessageChainBuilder, PushBuilder}; use crate::msg::{MessageElem, PushElem}; use crate::pb::msg; use crate::{push_builder_impl, to_elem_vec_impl}; #[derive(Default, Debug, Clone)] pub struct Face { pub index: i32, pub name: String, } impl Face { pub fn new(id: i32) -> Self { Self { index: id, name: Self::name(id).into(), } } pub fn name(id: i32) -> &'static str { face_id_map(id).unwrap_or("未知表情") } pub fn new_from_name(name: &str) -> Option { face_name_map(name).map(Self::new) } } impl PushElem for Face { fn push_to(e: Self, vec: &mut Vec) { let elem = if e.index >= 260 { let text = format!("/{}", e.name).as_bytes().to_vec(); let elem = msg::MsgElemInfoServtype33 { index: Some(e.index as u32), text: Some(text.clone()), compat: Some(text), buf: None, } .encode_to_vec(); msg::elem::Elem::CommonElem(msg::CommonElem { service_type: Some(33), pb_elem: Some(elem), business_type: Some(1), }) } else { msg::elem::Elem::Face(msg::Face { index: Some(e.index), old: Some(((0x1445 - 4 + e.index) as u16).to_be_bytes().to_vec()), buf: Some(vec![0x00, 0x01, 0x00, 0x04, 0x52, 0xCC, 0xF5, 0xD0]), }) }; vec.push(elem); } } impl From for Face { fn from(e: msg::Face) -> Self { Self::new(e.index()) } } impl From for Face { fn from(e: msg::MsgElemInfoServtype33) -> Self { Self::new(e.index() as i32) } } impl fmt::Display for Face { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{}]", self.name) } } to_elem_vec_impl!(Face); push_builder_impl!(Face); #[cfg(test)] mod tests { use super::*; #[test] fn test() { let name = Face::name(1); println!("{name:?}") } } // pub fn face_id_map(key: i32) -> Option<&'static str> { // match key { // 14 => Some("微笑"), // _ => None, // } // } // pub fn face_name_map(key: &str) -> Option { // match key { // "微笑" => Some(14), // _ => None, // } // } macro_rules! faces_map { ($(($id: expr, $name: expr)),*) => { pub fn face_id_map(id: i32) -> Option<&'static str> { match id { $( $id => Some($name), )* _ => None, } } pub fn face_name_map(name: &str) -> Option { match name { $( $name => Some($id), )* _ => None, } } }; } faces_map!( (14, "微笑"), (1, "撇嘴"), (2, "色"), (3, "发呆"), (4, "得意"), (6, "害羞"), (7, "闭嘴"), (8, "睡"), (9, "大哭"), (5, "流泪"), (10, "尴尬"), (11, "发怒"), (12, "调皮"), (13, "呲牙"), (0, "惊讶"), (15, "难过"), (16, "酷"), (96, "冷汗"), (18, "抓狂"), (19, "吐"), (20, "偷笑"), (21, "可爱"), (22, "白眼"), (23, "傲慢"), (24, "饥饿"), (25, "困"), (26, "惊恐"), (27, "流汗"), (28, "憨笑"), (29, "悠闲"), (30, "奋斗"), (31, "咒骂"), (32, "疑问"), (33, "嘘"), (34, "晕"), (35, "折磨"), (36, "衰"), (37, "骷髅"), (38, "敲打"), (39, "再见"), (97, "擦汗"), (98, "抠鼻"), (99, "鼓掌"), (100, "糗大了"), (101, "坏笑"), (102, "左哼哼"), (103, "右哼哼"), (104, "哈欠"), (105, "鄙视"), (106, "委屈"), (107, "快哭了"), (108, "阴险"), (305, "右亲亲"), (109, "左亲亲"), (110, "吓"), (111, "可怜"), (172, "眨眼睛"), (182, "笑哭"), (179, "doge"), (173, "泪奔"), (174, "无奈"), (212, "托腮"), (175, "卖萌"), (178, "斜眼笑"), (177, "喷血"), (180, "惊喜"), (181, "骚扰"), (176, "小纠结"), (183, "我最美"), (245, "加油必胜"), (246, "加油抱抱"), (247, "口罩护体"), (260, "搬砖中"), (261, "忙到飞起"), (262, "脑阔疼"), (263, "沧桑"), (264, "捂脸"), (265, "辣眼睛"), (266, "哦哟"), (267, "头秃"), (268, "问号脸"), (269, "暗中观察"), (270, "emm"), (271, "吃瓜"), (272, "呵呵哒"), (277, "汪汪"), (307, "喵喵"), (306, "牛气冲天"), (281, "无眼笑"), (282, "敬礼"), (283, "狂笑"), (284, "面无表情"), (285, "摸鱼"), (293, "摸锦鲤"), (286, "魔鬼笑"), (287, "哦"), (288, "请"), (289, "睁眼"), (294, "期待"), (295, "拿到红包"), (296, "真好"), (297, "拜谢"), (298, "元宝"), (299, "牛啊"), (300, "胖三斤"), (301, "好闪"), (303, "右拜年"), (302, "左拜年"), (304, "红包包"), (322, "拒绝"), (323, "嫌弃"), (311, "打call"), (312, "变形"), (313, "嗑到了"), (314, "仔细分析"), (315, "加油"), (316, "我没事"), (317, "菜汪"), (318, "崇拜"), (319, "比心"), (320, "庆祝"), (321, "老色痞"), (324, "吃糖"), (325, "惊吓"), (326, "生气"), (53, "蛋糕"), (114, "篮球"), (327, "加一"), (328, "错号"), (329, "对号"), (330, "完成"), (331, "明白"), (49, "拥抱"), (66, "爱心"), (63, "玫瑰"), (64, "凋谢"), (187, "幽灵"), (146, "爆筋"), (116, "示爱"), (67, "心碎"), (60, "咖啡"), (185, "羊驼"), (192, "红包"), (137, "鞭炮"), (138, "灯笼"), (136, "双喜"), (76, "赞"), (124, "OK"), (118, "抱拳"), (78, "握手"), (119, "勾引"), (79, "胜利"), (120, "拳头"), (121, "差劲"), (77, "踩"), (122, "爱你"), (123, "NO"), (201, "点赞"), (203, "托脸"), (204, "吃"), (202, "无聊"), (200, "拜托"), (194, "不开心"), (193, "大笑"), (197, "冷漠"), (211, "我不看"), (210, "飙泪"), (198, "呃"), (199, "好棒"), (207, "花痴"), (205, "送花"), (206, "害怕"), (208, "小样儿"), (308, "求红包"), (309, "谢红包"), (310, "新年烟花"), (290, "敲开心"), (291, "震惊"), (292, "让我康康"), (226, "拍桌"), (215, "糊脸"), (237, "偷看"), (214, "啵啵"), (235, "颤抖"), (222, "抱抱"), (217, "扯一扯"), (221, "顶呱呱"), (225, "撩一撩"), (241, "生日快乐"), (227, "拍手"), (238, "扇脸"), (240, "喷脸"), (229, "干杯"), (216, "拍头"), (218, "舔一舔"), (233, "掐一掐"), (219, "蹭一蹭"), (244, "扔狗"), (232, "佛系"), (243, "甩头"), (223, "暴击"), (279, "打脸"), (280, "击掌"), (231, "哼"), (224, "开枪"), (278, "汗"), (236, "啃头"), (228, "恭喜"), (220, "拽炸天"), (239, "原谅"), (242, "头撞击"), (230, "嘲讽"), (234, "惊呆"), (273, "我酸了"), (75, "月亮"), (74, "太阳"), (46, "猪头"), (112, "菜刀"), (56, "刀"), (169, "手枪"), (171, "茶"), (59, "便便"), (144, "喝彩"), (147, "棒棒糖"), (89, "西瓜"), (61, "饭"), (148, "喝奶"), (274, "太南了"), (113, "啤酒"), (140, "K歌"), (188, "蛋"), (55, "炸弹"), (184, "河蟹"), (158, "钞票"), (54, "闪电"), (69, "礼物"), (190, "菊花"), (151, "飞机"), (145, "祈祷"), (117, "瓢虫"), (168, "药"), (115, "乒乓"), (57, "足球"), (41, "发抖"), (125, "转圈"), (42, "爱情"), (43, "跳跳"), (86, "怄火"), (129, "挥手"), (85, "飞吻"), (126, "磕头"), (128, "跳绳"), (130, "激动"), (127, "回头"), (132, "献吻"), (134, "右太极"), (133, "左太极"), (131, "街舞"), (276, "辣椒酱") ); ================================================ FILE: ricq-core/src/msg/elem/flash_image.rs ================================================ use std::fmt; use crate::command::common::PbToBytes; use crate::msg::elem::{FriendImage, GroupImage}; use crate::msg::{MessageChainBuilder, PushBuilder}; use crate::msg::{MessageElem, PushElem}; use crate::pb::msg; use crate::{push_builder_impl, to_elem_vec_impl}; #[derive(Debug, Clone)] pub enum FlashImage { FriendImage(FriendImage), GroupImage(GroupImage), } impl FlashImage { pub fn url(&self) -> String { match self { FlashImage::FriendImage(i) => i.url(), FlashImage::GroupImage(i) => i.url(), } } } impl PushElem for FlashImage { fn push_to(elem: Self, vec: &mut Vec) { let flash = { match elem { FlashImage::FriendImage(image) => msg::MsgElemInfoServtype3 { flash_c2c_pic: Some(image.into()), ..Default::default() }, FlashImage::GroupImage(image) => msg::MsgElemInfoServtype3 { flash_troop_pic: Some(image.into()), ..Default::default() }, } } .to_bytes(); vec.push(MessageElem::CommonElem(msg::CommonElem { service_type: Some(3), pb_elem: Some(flash.to_vec()), ..Default::default() })); vec.push(MessageElem::Text(msg::Text { str: Some("[闪照]请使用新版手机QQ查看闪照。".to_owned()), ..Default::default() })); } } impl From for FlashImage { fn from(e: FriendImage) -> Self { Self::FriendImage(e) } } impl From for FlashImage { fn from(e: GroupImage) -> Self { Self::GroupImage(e) } } impl fmt::Display for FlashImage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FlashImage::FriendImage(i) => { write!(f, "[FlashImage(friend): {}]", i.url()) } FlashImage::GroupImage(i) => { write!(f, "[FlashImage(group): {}]", i.url()) } } } } to_elem_vec_impl!(FlashImage); push_builder_impl!(FlashImage); ================================================ FILE: ricq-core/src/msg/elem/friend_image.rs ================================================ use std::fmt; use serde::{Deserialize, Serialize}; use crate::msg::elem::flash_image::FlashImage; use crate::msg::{MessageChainBuilder, PushBuilder}; use crate::msg::{MessageElem, PushElem}; use crate::pb::msg; use crate::{push_builder_impl, to_elem_vec_impl}; #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct FriendImage { pub res_id: String, pub file_path: String, pub md5: Vec, pub size: u32, pub width: u32, pub height: u32, pub image_type: i32, pub orig_url: String, pub download_path: String, } impl FriendImage { pub fn flash(self) -> FlashImage { FlashImage::from(self) } pub fn url(&self) -> String { if !self.orig_url.is_empty() { return format!("https://c2cpicdw.qpic.cn{}", self.orig_url); } format!( "https://c2cpicdw.qpic.cn/offpic_new/0/{}/0?term=3", if !self.download_path.is_empty() { self.download_path.clone() } else { self.res_id.clone() } ) } } impl From for msg::NotOnlineImage { fn from(e: FriendImage) -> Self { msg::NotOnlineImage { file_path: Some(e.file_path), res_id: Some(e.res_id), old_pic_md5: Some(false), pic_md5: Some(e.md5), download_path: Some(e.download_path), original: Some(0), // 是否原图,如果是0需要写 24,25 file_len: Some(e.size), img_type: Some(e.image_type), pic_width: Some(e.width), pic_height: Some(e.height), biz_type: Some(0), show_len: Some(0), download_len: Some(0), pb_reserve: Some(vec![0x78, 0x02]), ..Default::default() } } } impl PushElem for FriendImage { fn push_to(elem: Self, vec: &mut Vec) { vec.push(MessageElem::NotOnlineImage(elem.into())); } } impl From for FriendImage { fn from(e: msg::NotOnlineImage) -> Self { Self { file_path: e.file_path.unwrap_or_default(), size: e.file_len.unwrap_or_default(), width: e.pic_width.unwrap_or_default(), height: e.pic_height.unwrap_or_default(), md5: e.pic_md5.unwrap_or_default(), orig_url: e.orig_url.unwrap_or_default(), res_id: e.res_id.unwrap_or_default(), download_path: e.download_path.unwrap_or_default(), image_type: e.img_type.unwrap_or_default(), } } } impl fmt::Display for FriendImage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[FriendImage: {}]", self.url()) } } to_elem_vec_impl!(FriendImage); push_builder_impl!(FriendImage); ================================================ FILE: ricq-core/src/msg/elem/group_image.rs ================================================ use std::fmt; use serde::{Deserialize, Serialize}; use crate::hex::encode_hex; use crate::msg::elem::flash_image::FlashImage; use crate::msg::{MessageChainBuilder, PushBuilder}; use crate::msg::{MessageElem, PushElem}; use crate::pb::msg; use crate::pb::msg::CustomFace; use crate::{push_builder_impl, to_elem_vec_impl}; #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct GroupImage { pub file_path: String, // hex(md5).jpg pub file_id: i64, pub size: u32, pub width: u32, pub height: u32, pub md5: Vec, pub orig_url: Option, pub image_type: i32, pub signature: Vec, // highway session key pub server_ip: u32, pub server_port: u32, } impl GroupImage { pub fn flash(self) -> FlashImage { FlashImage::from(self) } pub fn url(&self) -> String { if let Some(orig_url) = &self.orig_url { format!("https://gchat.qpic.cn{orig_url}") } else { format!( "https://gchat.qpic.cn/gchatpic_new/0/0-0-{}/0?term=2", encode_hex(&self.md5).to_uppercase() ) } } } impl From for msg::CustomFace { fn from(e: GroupImage) -> Self { msg::CustomFace { file_type: Some(66), useful: Some(1), biz_type: Some(5), // 0? width: Some(e.width), height: Some(e.height), file_id: Some(e.file_id as i32), file_path: Some(e.file_path), md5: Some(e.md5), image_type: Some(e.image_type), size: Some(e.size), flag: Some(vec![0; 4]), signature: Some(e.signature), server_ip: Some(e.server_ip), server_port: Some(e.server_port), source: Some(200), // 200 origin: Some(0), // 是否原图 0/1,设为1不需要29,30 show_len: Some(0), // ? download_len: Some(0), // ? ..Default::default() } } } impl PushElem for GroupImage { fn push_to(elem: Self, vec: &mut Vec) { vec.push(MessageElem::CustomFace(elem.into())); } } impl From for GroupImage { fn from(custom_face: CustomFace) -> Self { if custom_face.md5().is_empty() { return Self::default(); } // guild image todo return Self { file_id: custom_face.file_id() as i64, file_path: custom_face.file_path().to_owned(), size: custom_face.size(), width: custom_face.width(), height: custom_face.height(), orig_url: custom_face.orig_url, md5: custom_face.md5.unwrap_or_default(), image_type: custom_face.image_type.unwrap_or(1000), signature: custom_face.signature.unwrap_or_default(), server_ip: custom_face.server_ip.unwrap_or_default(), server_port: custom_face.server_port.unwrap_or_default(), }; } } fn to_uuid(md5: &str) -> String { format!( "{}-{}-{}-{}-{}", &md5[0..8], &md5[8..12], &md5[12..16], &md5[16..20], &md5[20..32], ) } pub fn calculate_image_resource_id(md5: &[u8]) -> String { format!("{{{}}}.png", to_uuid(&encode_hex(md5))) } impl fmt::Display for GroupImage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[GroupImage: {}]", self.url()) } } to_elem_vec_impl!(GroupImage); push_builder_impl!(GroupImage); ================================================ FILE: ricq-core/src/msg/elem/light_app.rs ================================================ use std::fmt; use std::io::{Read, Write}; use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression}; use super::fmt_extract_attr; use crate::msg::{MessageChainBuilder, PushBuilder}; use crate::msg::{MessageElem, PushElem}; use crate::pb::msg; use crate::{push_builder_impl, to_elem_vec_impl}; // Some of the share card message will be a LightApp with pkg id `com.tencent.structmsg` #[derive(Default, Debug, Clone)] pub struct LightApp { pub content: String, } impl LightApp { pub fn new(content: String) -> Self { Self { content } } } impl PushElem for LightApp { fn push_to(elem: Self, vec: &mut Vec) { vec.push(MessageElem::LightApp(msg::LightApp { data: Some({ let mut encoder = ZlibEncoder::new(vec![1], Compression::default()); encoder.write_all(elem.content.as_bytes()).ok(); encoder.finish().unwrap_or_default() }), ..Default::default() })); } } impl From for LightApp { fn from(e: msg::LightApp) -> Self { let data = e.data.unwrap_or_default(); if data.len() > 1 { let content = if data[0] == 0 { data[1..].to_vec() } else { let mut uncompressed = Vec::new(); ZlibDecoder::new(&data[1..]) .read_to_end(&mut uncompressed) .ok(); uncompressed }; if !content.is_empty() && content.len() < 1024 ^ 3 { return Self { content: String::from_utf8_lossy(&content).into_owned(), }; } } Self::default() } } impl fmt::Display for LightApp { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("[LightApp:")?; fmt_extract_attr(f, &self.content, "app", r#""app":""#, "\"")?; fmt_extract_attr(f, &self.content, "prompt", r#""prompt":""#, "\"")?; fmt_extract_attr(f, &self.content, "desc", r#""desc":""#, "\"")?; fmt_extract_attr(f, &self.content, "url", r#""jumpUrl":""#, "\"")?; fmt_extract_attr(f, &self.content, "title", r#""title":""#, "\"")?; fmt_extract_attr(f, &self.content, "tag", r#""tag":""#, "\"")?; f.write_str("]") } } to_elem_vec_impl!(LightApp); push_builder_impl!(LightApp); ================================================ FILE: ricq-core/src/msg/elem/market_face.rs ================================================ use derivative; use crate::msg::{MessageChainBuilder, PushBuilder}; use crate::msg::{MessageElem, PushElem}; use crate::pb::msg; use crate::{push_builder_impl, to_elem_vec_impl}; // 不需要实现 Display,因为后面一定会跟 Text #[derive(Default, Debug, Clone)] pub struct MarketFace { pub name: String, pub face_id: Vec, pub tab_id: i32, pub item_type: i32, pub sub_type: i32, pub media_type: i32, pub encrypt_key: Vec, pub magic_value: String, } impl PushElem for MarketFace { fn push_to(elem: Self, vec: &mut Vec) { vec.push(MessageElem::MarketFace(msg::MarketFace { face_name: Some(elem.name.as_bytes().to_vec()), item_type: Some(elem.item_type as u32), face_info: Some(1), face_id: Some(elem.face_id), tab_id: Some(elem.tab_id as u32), sub_type: Some(elem.sub_type as u32), key: Some(elem.encrypt_key), media_type: Some(elem.media_type as u32), image_width: Some(200), image_height: Some(200), mobileparam: Some(elem.magic_value.as_bytes().to_vec()), ..Default::default() })); vec.push(MessageElem::Text(msg::Text { str: Some(elem.name), ..Default::default() })); } } impl From for MarketFace { fn from(e: msg::MarketFace) -> Self { Self { name: String::from_utf8(e.face_name.unwrap_or_default()).unwrap_or_default(), face_id: e.face_id.unwrap_or_default(), tab_id: e.tab_id.unwrap_or_default() as i32, item_type: e.item_type.unwrap_or_default() as i32, sub_type: e.sub_type.unwrap_or_default() as i32, media_type: e.media_type.unwrap_or_default() as i32, encrypt_key: e.key.unwrap_or_default(), magic_value: String::from_utf8(e.mobileparam.unwrap_or_default()).unwrap_or_default(), } } } #[derive(Default, Debug, Clone)] pub struct Dice { pub value: i32, // range: [1, 6] } impl Dice { pub fn new(value: i32) -> Self { Self { value } } } impl From for MarketFace { fn from(e: Dice) -> Self { Self { name: "[骰子]".into(), face_id: vec![ 72, 35, 211, 173, 177, 93, 240, 128, 20, 206, 93, 103, 150, 183, 110, 225, ], tab_id: 11464, item_type: 6, sub_type: 3, media_type: 0, encrypt_key: vec![ 52, 48, 57, 101, 50, 97, 54, 57, 98, 49, 54, 57, 49, 56, 102, 57, ], magic_value: format!("rscType?1;value={}", e.value - 1), } } } impl From for Dice { fn from(e: MarketFace) -> Self { Self { value: e .magic_value .split_once('=') .map_or(1, |v| v.1.parse::().unwrap_or_default() + 1), // 有一种没点数的奇怪骰子 } } } impl PushElem for Dice { fn push_to(elem: Self, vec: &mut Vec) { MarketFace::push_to(MarketFace::from(elem), vec); } } #[derive(Debug, Clone, derivative::Derivative)] #[derivative(Default)] pub enum FingerGuessing { #[derivative(Default)] Rock, Scissors, Paper, } impl From for MarketFace { fn from(e: FingerGuessing) -> Self { let value = match e { FingerGuessing::Rock => 0, FingerGuessing::Scissors => 1, FingerGuessing::Paper => 2, }; MarketFace { name: "[猜拳]".into(), face_id: vec![ 131, 200, 162, 147, 174, 101, 202, 20, 15, 52, 129, 32, 167, 116, 72, 238, ], tab_id: 11415, item_type: 6, sub_type: 3, media_type: 0, encrypt_key: vec![ 55, 100, 101, 51, 57, 102, 101, 98, 99, 102, 52, 53, 101, 54, 100, 98, ], magic_value: format!("rscType?1;value={value}"), } } } impl PushElem for FingerGuessing { fn push_to(elem: Self, vec: &mut Vec) { MarketFace::push_to(MarketFace::from(elem), vec); } } impl From for FingerGuessing { fn from(e: MarketFace) -> Self { let value = e.magic_value.split('=').collect::>()[1] .parse::() .unwrap_or_default(); match value { 0 => Self::Rock, 1 => Self::Scissors, 2 => Self::Paper, _ => Self::Rock, } } } to_elem_vec_impl!(MarketFace); push_builder_impl!(MarketFace); to_elem_vec_impl!(Dice); push_builder_impl!(Dice); to_elem_vec_impl!(FingerGuessing); push_builder_impl!(FingerGuessing); ================================================ FILE: ricq-core/src/msg/elem/mod.rs ================================================ use std::fmt; use prost::Message; pub use group_image::calculate_image_resource_id; pub(crate) use text::flush_builder; pub use crate::msg::elem::{ anonymous::Anonymous, at::At, face::Face, flash_image::FlashImage, friend_image::FriendImage, group_image::GroupImage, light_app::LightApp, market_face::{Dice, FingerGuessing, MarketFace}, reply::Reply, rich_msg::RichMsg, text::Text, video_file::VideoFile, }; use crate::pb::msg; mod anonymous; mod at; mod face; mod flash_image; mod friend_image; mod group_image; mod light_app; mod market_face; mod reply; mod rich_msg; mod text; mod video_file; #[derive(Debug, Clone)] pub enum RQElem { At(at::At), Text(text::Text), Face(face::Face), MarketFace(market_face::MarketFace), Dice(market_face::Dice), FingerGuessing(market_face::FingerGuessing), LightApp(light_app::LightApp), RichMsg(rich_msg::RichMsg), FriendImage(friend_image::FriendImage), GroupImage(group_image::GroupImage), FlashImage(flash_image::FlashImage), VideoFile(video_file::VideoFile), Other(Box), } impl From for RQElem { fn from(elem: msg::elem::Elem) -> Self { match elem { msg::elem::Elem::Text(e) => { // TODO guild at if !e.attr6_buf().is_empty() { RQElem::At(at::At::from(e)) } else { RQElem::Text(text::Text::from(e)) } } msg::elem::Elem::Face(e) => RQElem::Face(face::Face::from(e)), msg::elem::Elem::CommonElem(ref e) => match e.service_type() { // TODO image 3 => { if let Ok(flash) = msg::MsgElemInfoServtype3::decode(e.pb_elem()) { if let Some(i) = flash.flash_troop_pic { RQElem::FlashImage(group_image::GroupImage::from(i).flash()) } else if let Some(i) = flash.flash_c2c_pic { RQElem::FlashImage(friend_image::FriendImage::from(i).flash()) } else { RQElem::Other(Box::new(elem)) } } else { RQElem::Other(Box::new(elem)) } } 33 => { if let Ok(new_face) = msg::MsgElemInfoServtype33::decode(e.pb_elem()) { RQElem::Face(face::Face::from(new_face)) } else { RQElem::Other(Box::new(elem)) } } _ => RQElem::Other(Box::new(elem)), }, msg::elem::Elem::MarketFace(e) => { let face = MarketFace::from(e); match face.name.as_str() { // 从商城添加的会显示为“随机骰子”,但在遥远的曾经收藏的表情,会显示为“骰子” "[骰子]" | "[随机骰子]" => RQElem::Dice(Dice::from(face)), "[猜拳]" => RQElem::FingerGuessing(FingerGuessing::from(face)), _ => RQElem::MarketFace(face), } } msg::elem::Elem::LightApp(e) => RQElem::LightApp(light_app::LightApp::from(e)), msg::elem::Elem::RichMsg(e) => RQElem::RichMsg(rich_msg::RichMsg::from(e)), msg::elem::Elem::VideoFile(e) => RQElem::VideoFile(video_file::VideoFile::from(e)), msg::elem::Elem::NotOnlineImage(e) => { RQElem::FriendImage(friend_image::FriendImage::from(e)) } msg::elem::Elem::CustomFace(e) => RQElem::GroupImage(group_image::GroupImage::from(e)), _ => RQElem::Other(Box::new(elem)), } } } impl fmt::Display for RQElem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RQElem::At(e) => fmt::Display::fmt(e, f), RQElem::Text(e) => fmt::Display::fmt(e, f), RQElem::Face(e) => fmt::Display::fmt(e, f), RQElem::GroupImage(e) => fmt::Display::fmt(e, f), RQElem::FriendImage(e) => fmt::Display::fmt(e, f), RQElem::FlashImage(e) => fmt::Display::fmt(e, f), RQElem::LightApp(e) => fmt::Display::fmt(e, f), RQElem::RichMsg(e) => fmt::Display::fmt(e, f), _ => return Ok(()), }?; f.write_str(" ") } } /// Extract a field from xml / json and write to formatter. fn fmt_extract_attr( f: &mut fmt::Formatter, i: &str, name: &str, begin: &str, end: &str, ) -> fmt::Result { if let Some(v) = i .rsplit_once(begin) .and_then(|v| v.1.split_once(end)) .map(|v| v.0) { write!(f, " {name}='{v}'")?; } Ok(()) } macro_rules! impl_from { ($key: tt, $fty: ty) => { impl From<$fty> for RQElem { fn from(e: $fty) -> Self { RQElem::$key(e) } } }; } impl_from!(At, at::At); impl_from!(Text, text::Text); impl_from!(Face, face::Face); impl_from!(MarketFace, market_face::MarketFace); impl_from!(Dice, market_face::Dice); impl_from!(FingerGuessing, market_face::FingerGuessing); impl_from!(LightApp, light_app::LightApp); impl_from!(RichMsg, rich_msg::RichMsg); impl_from!(FriendImage, friend_image::FriendImage); impl_from!(GroupImage, group_image::GroupImage); impl_from!(FlashImage, flash_image::FlashImage); impl_from!(Other, Box); impl From for RQElem { fn from(s: String) -> Self { RQElem::Text(text::Text::new(s)) } } impl From<&str> for RQElem { fn from(s: &str) -> Self { RQElem::Text(text::Text::new(s.to_string())) } } ================================================ FILE: ricq-core/src/msg/elem/reply.rs ================================================ use std::fmt; use crate::msg::{MessageChainBuilder, MessageElem, PushBuilder}; use crate::pb::msg; use super::super::MessageChain; #[derive(Default, Debug, Clone)] pub struct Reply { pub reply_seq: i32, pub sender: i64, pub time: i32, pub elements: MessageChain, } impl From for MessageElem { fn from(e: Reply) -> Self { MessageElem::SrcMsg(msg::SourceMsg { orig_seqs: vec![e.reply_seq], sender_uin: Some(e.sender), time: Some(e.time), flag: Some(1), elems: e.elements.into(), rich_msg: Some(vec![]), pb_reserve: Some(vec![]), src_msg: Some(vec![]), troop_name: Some(vec![]), ..Default::default() }) } } impl PushBuilder for Reply { fn push_builder(elem: Self, builder: &mut MessageChainBuilder) { let index = if let Some(MessageElem::AnonGroupMsg(..)) = builder.elems.get(0) { 1 } else { 0 }; builder.elems.insert(index, elem.into()); } } impl From for Reply { fn from(e: msg::SourceMsg) -> Self { Self { reply_seq: e.orig_seqs[0], time: e.time(), sender: e.sender_uin(), elements: MessageChain::from(e.elems), } } } impl fmt::Display for Reply { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[Reply: {}]", self.reply_seq) } } ================================================ FILE: ricq-core/src/msg/elem/rich_msg.rs ================================================ use std::fmt; use std::io::{Read, Write}; use flate2::read::ZlibDecoder; use flate2::write::ZlibEncoder; use flate2::Compression; use super::fmt_extract_attr; use crate::msg::{MessageChainBuilder, PushBuilder}; use crate::msg::{MessageElem, PushElem}; use crate::pb::msg; use crate::{push_builder_impl, to_elem_vec_impl}; #[derive(Default, Debug, Clone)] pub struct RichMsg { pub service_id: i32, pub template1: String, } impl From for RichMsg { fn from(e: msg::RichMsg) -> Self { let data = e.template1.unwrap_or_default(); if data.len() > 1 { let content = if data[0] == 0 { data[1..].to_vec() } else { let mut uncompressed = Vec::new(); ZlibDecoder::new(&data[1..]) .read_to_end(&mut uncompressed) .ok(); uncompressed }; if !content.is_empty() && content.len() < 1024 ^ 3 { return Self { service_id: e.service_id.unwrap_or_default(), template1: String::from_utf8_lossy(&content).into_owned(), }; } } Self::default() } } impl PushElem for RichMsg { fn push_to(elem: Self, vec: &mut Vec) { vec.push(MessageElem::RichMsg(msg::RichMsg { template1: Some({ let mut encoder = ZlibEncoder::new(vec![1], Compression::default()); encoder.write_all(elem.template1.as_bytes()).ok(); encoder.finish().unwrap_or_default() }), service_id: Some(elem.service_id), ..Default::default() })); } } impl fmt::Display for RichMsg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("[RichMsg:")?; fmt_extract_attr(f, &self.template1, "brief", " brief=\"", "\"")?; fmt_extract_attr(f, &self.template1, "title", "", "")?; fmt_extract_attr(f, &self.template1, "summary", "", "")?; fmt_extract_attr(f, &self.template1, "url", " url=\"", "\"")?; fmt_extract_attr(f, &self.template1, "name", " name=\"", "\"")?; f.write_str("]") } } to_elem_vec_impl!(RichMsg); push_builder_impl!(RichMsg); ================================================ FILE: ricq-core/src/msg/elem/text.rs ================================================ use std::{fmt, mem}; use crate::msg::{MessageChainBuilder, MessageElem, PushBuilder, PushElem}; use crate::pb::msg; use crate::to_elem_vec_impl; #[derive(Default, Debug, Clone)] pub struct Text { pub content: String, } impl Text { pub fn new(content: String) -> Self { Self { content } } } to_elem_vec_impl!(Text); impl From for Text { fn from(e: msg::Text) -> Self { Self { content: e.str.unwrap_or_default(), } } } impl PushElem for Text { fn push_to(elem: Self, vec: &mut Vec) { vec.push(MessageElem::Text(msg::Text { str: Some(elem.content), ..Default::default() })); } } impl PushBuilder for Text { fn push_builder(elem: Self, builder: &mut MessageChainBuilder) { builder.buf_string.push_str(&elem.content); } } pub fn flush_builder(builder: &mut MessageChainBuilder) { if !builder.buf_string.is_empty() { let s = mem::take(&mut builder.buf_string); builder.elems.push(MessageElem::Text(msg::Text { str: Some(s), ..Default::default() })); } } impl fmt::Display for Text { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.content) } } ================================================ FILE: ricq-core/src/msg/elem/video_file.rs ================================================ use std::fmt; use crate::hex::encode_hex; use crate::msg::{MessageChainBuilder, PushBuilder}; use crate::msg::{MessageElem, PushElem}; use crate::pb::msg; use crate::{push_builder_impl, to_elem_vec_impl}; #[derive(Default, Debug, Clone)] pub struct VideoFile { pub name: String, pub uuid: Vec, pub size: i32, pub thumb_size: i32, pub md5: Vec, pub thumb_md5: Vec, } impl From for VideoFile { fn from(e: msg::VideoFile) -> Self { Self { name: e.file_name.unwrap_or_default(), uuid: e.file_uuid.unwrap_or_default(), size: e.file_size.unwrap_or_default(), thumb_size: e.thumb_file_size.unwrap_or_default(), md5: e.file_md5.unwrap_or_default(), thumb_md5: e.thumb_file_md5.unwrap_or_default(), } } } impl PushElem for VideoFile { fn push_to(elem: Self, vec: &mut Vec) { vec.push(MessageElem::Text(msg::Text { str: Some("你的QQ暂不支持查看视频短片,请期待后续版本。".into()), ..Default::default() })); vec.push(MessageElem::VideoFile(msg::VideoFile { file_uuid: Some(elem.uuid), file_name: Some(format!("{}.mp4", encode_hex(&elem.md5))), file_md5: Some(elem.md5), file_format: Some(3), file_time: Some(10), file_size: Some(elem.size), thumb_width: Some(1280), thumb_height: Some(720), thumb_file_md5: Some(elem.thumb_md5), thumb_file_size: Some(elem.thumb_size), busi_type: Some(0), // guild 4601 from_chat_type: Some(-1), to_chat_type: Some(-1), bool_support_progressive: Some(true), file_width: Some(1280), // guild 0 file_height: Some(720), // guild 0 sub_busi_type: None, // guild 4601 video_attr: None, // guild 0 ..Default::default() })); } } impl fmt::Display for VideoFile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[VideoFile: {}]", self.name) } } to_elem_vec_impl!(VideoFile); push_builder_impl!(VideoFile); ================================================ FILE: ricq-core/src/msg/fragment.rs ================================================ use crate::pb::msg; use super::MessageChain; impl MessageChain { // TODO test // https://github.com/mamoe/mirai/blob/dev/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt#L68 pub fn fragment(mut self) -> Vec { let mut results = vec![]; let mut txt_add = false; let mut last = vec![]; fn flush( txt_add: &mut bool, last: &mut Vec, results: &mut Vec, ) { *txt_add = false; if !last.is_empty() { results.push(MessageChain(last.clone())); last.clear() } } self.0.iter_mut().for_each(|element| { if last.len() >= 4 { flush(&mut txt_add, &mut last, &mut results); } if let msg::elem::Elem::Text(t) = element { if txt_add { flush(&mut txt_add, &mut last, &mut results); } if t.str.clone().unwrap_or_default().len() < 80 { txt_add = true; last.push(element.clone()); } else { flush(&mut txt_add, &mut last, &mut results); t.str .clone() .unwrap_or_default() .chars() .collect::>() .chunks(80) .map(|c| c.iter().collect::()) .for_each(|s| { results.push(MessageChain(vec![msg::elem::Elem::Text(msg::Text { str: Some(s), ..Default::default() })])) }); } } else { last.push(element.clone()); } }); flush(&mut txt_add, &mut last, &mut results); results } } ================================================ FILE: ricq-core/src/msg/macros.rs ================================================ #[macro_export] macro_rules! to_elem_vec_impl { ($t:ty) => { impl From<$t> for Vec { fn from(e: $t) -> Self { let mut vec = vec![]; <$t>::push_to(e, &mut vec); vec } } }; } #[macro_export] macro_rules! push_builder_impl { ($t:ty) => { impl PushBuilder for $t { fn push_builder(elem: Self, builder: &mut MessageChainBuilder) { builder.flush(); Self::push_to(elem, &mut builder.elems); } } }; } ================================================ FILE: ricq-core/src/msg/mod.rs ================================================ use std::fmt; use elem::RQElem; use elem::*; use crate::pb::msg; pub mod elem; mod fragment; mod macros; pub type MessageElem = msg::elem::Elem; /// [`MessageChain`]消息链, 用于发送消息 /// /// /// ## 示例 /// /// ```rust /// use ricq_core::msg::elem::{At, Text}; /// use ricq_core::msg::MessageChain; /// let mut chain = MessageChain::default(); /// chain.push(Text::new(String::from("Hello"))); /// chain.push(At::new(12345)); /// chain.push(Text::new(String::from("world"))); /// ``` /// /// 另请参阅: [`MessageChainBuilder`] /// #[derive(Debug, Default, Clone)] pub struct MessageChain(pub Vec); impl MessageChain { /// 从消息元素构造[`MessageChain`] /// /// ## 示例 /// /// ```rust /// use ricq_core::msg::elem::Text; /// use ricq_core::msg::MessageChain; /// let chain = MessageChain::new(Text::new(String::from("Hello world!"))); /// ``` /// pub fn new>>(e: E) -> Self { Self(e.into()) } /// 将消息元素添加至[`MessageChain`] /// /// ## 示例 /// /// ```rust /// use ricq_core::msg::elem::Text; /// use ricq_core::msg::MessageChain; /// let mut chain = MessageChain::default(); /// chain.push(Text::new(String::from("Hello"))); /// ``` /// pub fn push>>(&mut self, e: E) { self.0.extend(e.into()) } pub fn anonymous(&self) -> Option { self.0.iter().find_map(|e| match e { MessageElem::AnonGroupMsg(anonymous) => Some(Anonymous::from(anonymous.clone())), _ => None, }) } /// 获取此[`MessageChain`]中的引用回复 pub fn reply(&self) -> Option { self.0.iter().find_map(|e| match e { MessageElem::SrcMsg(src_msg) => Some(Reply::from(src_msg.clone())), _ => None, }) } pub fn with_anonymous(&mut self, anonymous: Anonymous) { self.0.insert(0, MessageElem::from(anonymous)) } /// 添加引用回复 pub fn with_reply(&mut self, reply: Reply) { let index = if self.anonymous().is_some() { 1 } else { 0 }; self.0.insert(index, MessageElem::from(reply)) } } impl FromIterator for MessageChain where E: Into>, { fn from_iter>(iter: T) -> Self { Self(iter.into_iter().flat_map(Into::into).collect()) } } impl IntoIterator for MessageChain { type Item = RQElem; type IntoIter = impl Iterator + 'static; fn into_iter(self) -> Self::IntoIter { self.0 .into_iter() .filter(|e| !matches!(e, MessageElem::SrcMsg(_) | MessageElem::AnonGroupMsg(_))) .map(RQElem::from) } } impl From> for MessageChain { fn from(elements: Vec) -> Self { Self(elements.into_iter().filter_map(|e| e.elem).collect()) } } impl From for Vec { fn from(e: MessageChain) -> Self { e.0.into_iter() .map(|e| msg::Elem { elem: Some(e) }) .collect() } } impl fmt::Display for MessageChain { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for x in self.clone().into_iter() { fmt::Display::fmt(&x, f)? } Ok(()) } } /// [`MessageChain`]构造器 /// /// /// ## 示例 /// /// ```rust /// use ricq_core::msg::elem::At; /// use ricq_core::msg::MessageChainBuilder; /// let mut builder = MessageChainBuilder::new(); /// builder.push_str("Hello") /// .push(At::new(12345)) /// .push_str("world"); /// let chain = builder.build(); /// ``` /// #[derive(Debug, Default, Clone)] pub struct MessageChainBuilder { pub elems: Vec, pub buf_string: String, } impl MessageChainBuilder { /// 创建一个新的[`MessageChainBuilder`] /// /// ## 示例 /// ```rust /// use ricq_core::msg::MessageChainBuilder; /// let mut builder = MessageChainBuilder::new(); /// ``` /// pub fn new() -> Self { Self::default() } /// 为当前[`MessageChainBuilder`]添加一个消息元素 /// /// ## 示例 /// ```rust /// use ricq_core::msg::elem::Text; /// use ricq_core::msg::MessageChainBuilder; /// let mut builder = MessageChainBuilder::new(); /// builder.push(Text::new(String::from("Hello world!"))); /// ``` /// pub fn push(&mut self, elem: E) -> &mut Self { E::push_builder(elem, self); self } /// 向当前[`MessageChainBuilder`]的添加一段字符串 /// /// 本函数会将字符串直接添加于[`MessageChainBuilder`]内部的字符串缓存,在每次push其他元素时刷新 /// /// ## 示例 /// ```rust /// use ricq_core::msg::MessageChainBuilder; /// let mut builder = MessageChainBuilder::new(); /// builder.push_str("Hello world!"); /// ``` /// pub fn push_str(&mut self, str: &str) -> &mut Self { self.buf_string.push_str(str); self } /// 将此[`MessageChainBuilder`]构造为[`MessageChain`] /// /// ## 示例 /// ```rust /// use ricq_core::msg::{MessageChain, MessageChainBuilder}; /// let mut builder = MessageChainBuilder::new(); /// let chain: MessageChain = builder.build(); /// ``` /// pub fn build(mut self) -> MessageChain { self.flush(); MessageChain::new(self.elems) } /// 清空内部字符串缓存, 将其构造为消息元素 fn flush(&mut self) { flush_builder(self); } } pub trait PushElem { fn push_to(elem: Self, vec: &mut Vec); } pub trait PushBuilder { fn push_builder(elem: Self, builder: &mut MessageChainBuilder); } #[cfg(test)] mod tests { use super::*; #[test] fn test_iter() { let mut chain = MessageChain::default(); chain.push(Text::new("hello".into())); for e in chain.into_iter() { println!("{e:?}") } } #[test] fn test_display() { let mut chain = MessageChain::default(); chain.with_anonymous(Anonymous::default()); chain.with_reply(Reply::default()); chain.push(Text::new("hello".into())); chain.push(At::new(12345)); chain.push(Text::new("world".into())); chain.push(Face::new(1)); chain.push(Dice::new(1)); chain.push(FingerGuessing::Rock); chain.push(MarketFace { name: "xx".into(), ..Default::default() }); chain.push(LightApp::new("{}".into())); println!("{chain}"); println!("{:?}", chain.reply()); println!("{:?}", chain.anonymous()); for item in chain { println!("{item:?}") } } #[test] fn test_builder() { let mut builder = MessageChainBuilder::new(); builder .push(Anonymous::default()) .push(Reply::default()) .push_str("hello") .push(At::new(12345)) .push_str("world") .push(Face::new(1)) .push(Dice::new(1)) .push(Text::new("hello".into())) .push_str("world2") .push(FingerGuessing::Rock) .push(MarketFace { name: "xx".into(), ..Default::default() }) .push(LightApp::new("{}".into())); let chain = builder.build(); println!("{chain}"); assert!(chain.reply().is_some()); assert!(chain.anonymous().is_some()); for item in chain { println!("{item:?}") } } } ================================================ FILE: ricq-core/src/pb/cmd0x346/cmd0x346.proto ================================================ syntax = "proto3"; package cmd0x346; message ApplyCleanTrafficRsp { int32 retCode = 10; string retMsg = 20; } message ApplyCopyFromReq { int64 srcUin = 10; int64 srcGroup = 20; int32 srcSvcid = 30; bytes srcParentfolder = 40; bytes srcUuid = 50; bytes fileMd5 = 60; int64 dstUin = 70; int64 fileSize = 80; string fileName = 90; int32 dangerLevel = 100; int64 totalSpace = 110; } message ApplyCopyFromRsp { int32 retCode = 10; string retMsg = 20; bytes uuid = 30; int64 totalSpace = 40; } message ApplyCopyToReq { int64 dstId = 10; int64 dstUin = 20; int32 dstSvcid = 30; int64 srcUin = 40; int64 fileSize = 50; string fileName = 60; string localFilepath = 70; bytes uuid = 80; } message ApplyCopyToRsp { int32 retCode = 10; string retMsg = 20; string fileKey = 30; } message ApplyDownloadAbsReq { int64 uin = 10; bytes uuid = 20; } message ApplyDownloadAbsRsp { int32 retCode = 10; string retMsg = 20; DownloadInfo downloadInfo = 30; } message ApplyDownloadReq { int64 uin = 10; bytes uuid = 20; int32 ownerType = 30; int32 extIntype = 500; int32 need_https_url = 501; } message ApplyDownloadRsp { int32 retCode = 10; string retMsg = 20; DownloadInfo downloadInfo = 30; FileInfo fileInfo = 40; } message ApplyForwardFileReq { int64 senderUin = 10; int64 recverUin = 20; bytes uuid = 30; int32 dangerLevel = 40; int64 totalSpace = 50; } message ApplyForwardFileRsp { int32 retCode = 10; string retMsg = 20; int64 totalSpace = 30; int64 usedSpace = 40; bytes uuid = 50; } message ApplyGetTrafficReq { } message ApplyGetTrafficRsp { int32 retCode = 10; string retMsg = 20; int64 useFileSize = 30; int32 useFileNum = 40; int64 allFileSize = 50; int32 allFileNum = 60; } message ApplyListDownloadReq { int64 uin = 10; int32 beginIndex = 20; int32 reqCount = 30; } message ApplyListDownloadRsp { int32 retCode = 10; string retMsg = 20; int32 totalCount = 30; int32 beginIndex = 40; int32 rspCount = 50; int32 isEnd = 60; repeated FileInfo fileList = 70; } message ApplyUploadHitReq { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; string localFilepath = 60; int32 dangerLevel = 70; int64 totalSpace = 80; } message ApplyUploadHitReqV2 { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; bytes bytes_3sha = 60; bytes sha = 70; string localFilepath = 80; int32 dangerLevel = 90; int64 totalSpace = 100; } message ApplyUploadHitReqV3 { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; bytes sha = 60; string localFilepath = 70; int32 dangerLevel = 80; int64 totalSpace = 90; } message ApplyUploadHitRsp { int32 retCode = 10; string retMsg = 20; string uploadIp = 30; int32 uploadPort = 40; string uploadDomain = 50; bytes uuid = 60; bytes uploadKey = 70; int64 totalSpace = 80; int64 usedSpace = 90; } message ApplyUploadHitRspV2 { int32 retCode = 10; string retMsg = 20; string uploadIp = 30; int32 uploadPort = 40; string uploadDomain = 50; bytes uuid = 60; bytes uploadKey = 70; int64 totalSpace = 80; int64 usedSpace = 90; } message ApplyUploadHitRspV3 { int32 retCode = 10; string retMsg = 20; string uploadIp = 30; int32 uploadPort = 40; string uploadDomain = 50; bytes uuid = 60; bytes uploadKey = 70; int64 totalSpace = 80; int64 usedSpace = 90; } message ApplyUploadReq { int64 senderUin = 10; int64 recverUin = 20; int32 fileType = 30; int64 fileSize = 40; string fileName = 50; bytes bytes_10mMd5 = 60; string localFilepath = 70; int32 dangerLevel = 80; int64 totalSpace = 90; } message ApplyUploadReqV2 { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; bytes bytes_3sha = 60; string localFilepath = 70; int32 dangerLevel = 80; int64 totalSpace = 90; } message ApplyUploadReqV3 { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; bytes sha = 60; string localFilepath = 70; int32 dangerLevel = 80; int64 totalSpace = 90; } message ApplyUploadRsp { int32 retCode = 10; string retMsg = 20; int64 totalSpace = 30; int64 usedSpace = 40; int64 uploadedSize = 50; string uploadIp = 60; string uploadDomain = 70; int32 uploadPort = 80; bytes uuid = 90; bytes uploadKey = 100; bool boolFileExist = 110; int32 packSize = 120; repeated string uploadipList = 130; } message ApplyUploadRspV2 { int32 retCode = 10; string retMsg = 20; int64 totalSpace = 30; int64 usedSpace = 40; int64 uploadedSize = 50; string uploadIp = 60; string uploadDomain = 70; int32 uploadPort = 80; bytes uuid = 90; bytes uploadKey = 100; bool boolFileExist = 110; int32 packSize = 120; repeated string uploadipList = 130; int32 httpsvrApiVer = 140; bytes sha = 141; } message ApplyUploadRspV3 { int32 retCode = 10; string retMsg = 20; int64 totalSpace = 30; int64 usedSpace = 40; int64 uploadedSize = 50; string uploadIp = 60; string uploadDomain = 70; int32 uploadPort = 80; bytes uuid = 90; bytes uploadKey = 100; bool boolFileExist = 110; int32 packSize = 120; repeated string uploadIpList = 130; int32 uploadHttpsPort = 140; string uploadHttpsDomain = 150; string uploadDns = 160; string uploadLanip = 170; } message DelMessageReq { int64 uinSender = 1; int64 uinReceiver = 2; int32 time = 10; int32 random = 20; int32 seqNo = 30; } message DeleteFileReq { int64 uin = 10; int64 peerUin = 20; int32 deleteType = 30; bytes uuid = 40; } message DeleteFileRsp { int32 retCode = 10; string retMsg = 20; } message DownloadInfo { bytes downloadKey = 10; string downloadIp = 20; string downloadDomain = 30; int32 port = 40; string downloadUrl = 50; repeated string downloadipList = 60; string cookie = 70; } message DownloadSuccReq { int64 uin = 10; bytes uuid = 20; } message DownloadSuccRsp { int32 retCode = 10; string retMsg = 20; int32 downStat = 30; } message ExtensionReq { int64 id = 1; int64 type = 2; string dstPhonenum = 3; int32 phoneConvertType = 4; bytes sig = 20; int64 routeId = 100; DelMessageReq delMessageReq = 90100; int32 downloadUrlType = 90200; int32 pttFormat = 90300; int32 isNeedInnerIp = 90400; int32 netType = 90500; int32 voiceType = 90600; int32 fileType = 90700; int32 pttTime = 90800; } message ExtensionRsp { } message FileInfo { int64 uin = 1; int32 dangerEvel = 2; int64 fileSize = 3; int32 lifeTime = 4; int32 uploadTime = 5; bytes uuid = 6; string fileName = 7; int32 absFileType = 90; bytes bytes_10mMd5 = 100; bytes sha = 101; int32 clientType = 110; int64 ownerUin = 120; int64 peerUin = 121; int32 expireTime = 130; } message FileQueryReq { int64 uin = 10; bytes uuid = 20; } message FileQueryRsp { int32 retCode = 10; string retMsg = 20; FileInfo fileInfo = 30; } message RecallFileReq { int64 uin = 1; bytes uuid = 2; } message RecallFileRsp { int32 retCode = 1; string retMsg = 2; } message RecvListQueryReq { int64 uin = 1; int32 beginIndex = 2; int32 reqCount = 3; } message RecvListQueryRsp { int32 retCode = 1; string retMsg = 2; int32 fileTotCount = 3; int32 beginIndex = 4; int32 rspFileCount = 5; int32 isEnd = 6; repeated FileInfo fileList = 7; } message RenewFileReq { int64 uin = 1; bytes uuid = 2; int32 addTtl = 3; } message RenewFileRsp { int32 retCode = 1; string retMsg = 2; } message C346ReqBody { int32 cmd = 1; int32 seq = 2; RecvListQueryReq recvListQueryReq = 3; SendListQueryReq sendListQueryReq = 4; RenewFileReq renewFileReq = 5; RecallFileReq recallFileReq = 6; ApplyUploadReq applyUploadReq = 7; ApplyUploadHitReq applyUploadHitReq = 8; ApplyForwardFileReq applyForwardFileReq = 9; UploadSuccReq uploadSuccReq = 10; DeleteFileReq deleteFileReq = 11; DownloadSuccReq downloadSuccReq = 12; ApplyDownloadAbsReq applyDownloadAbsReq = 13; ApplyDownloadReq applyDownloadReq = 14; ApplyListDownloadReq applyListDownloadReq = 15; FileQueryReq fileQueryReq = 16; ApplyCopyFromReq applyCopyFromReq = 17; ApplyUploadReqV2 applyUploadReqV2 = 18; ApplyUploadReqV3 applyUploadReqV3 = 19; ApplyUploadHitReqV2 applyUploadHitReqV2 = 20; ApplyUploadHitReqV3 applyUploadHitReqV3 = 21; int32 businessId = 101; int32 clientType = 102; ApplyCopyToReq applyCopyToReq = 90000; //ApplyCleanTrafficReq applyCleanTrafficReq = 90001; empty message ApplyGetTrafficReq applyGetTrafficReq = 90002; ExtensionReq extensionReq = 99999; } message C346RspBody { int32 cmd = 1; int32 seq = 2; RecvListQueryRsp recvListQueryRsp = 3; SendListQueryRsp sendListQueryRsp = 4; RenewFileRsp renewFileRsp = 5; RecallFileRsp recallFileRsp = 6; ApplyUploadRsp applyUploadRsp = 7; ApplyUploadHitRsp applyUploadHitRsp = 8; ApplyForwardFileRsp applyForwardFileRsp = 9; UploadSuccRsp uploadSuccRsp = 10; DeleteFileRsp deleteFileRsp = 11; DownloadSuccRsp downloadSuccRsp = 12; ApplyDownloadAbsRsp applyDownloadAbsRsp = 13; ApplyDownloadRsp applyDownloadRsp = 14; ApplyListDownloadRsp applyListDownloadRsp = 15; FileQueryRsp fileQueryRsp = 16; ApplyCopyFromRsp applyCopyFromRsp = 17; ApplyUploadRspV2 applyUploadRspV2 = 18; ApplyUploadRspV3 applyUploadRspV3 = 19; ApplyUploadHitRspV2 applyUploadHitRspV2 = 20; ApplyUploadHitRspV3 applyUploadHitRspV3 = 21; int32 businessId = 101; int32 clientType = 102; ApplyCopyToRsp applyCopyToRsp = 90000; ApplyCleanTrafficRsp applyCleanTrafficRsp = 90001; ApplyGetTrafficRsp applyGetTrafficRsp = 90002; ExtensionRsp extensionRsp = 99999; } message SendListQueryReq { int64 uin = 1; int32 beginIndex = 2; int32 reqCount = 3; } message SendListQueryRsp { int32 retCode = 1; string retMsg = 2; int32 fileTotCount = 3; int32 beginIndex = 4; int32 rspFileCount = 5; int32 isEnd = 6; int64 totLimit = 7; int64 usedLimit = 8; repeated FileInfo fileList = 9; } message UploadSuccReq { int64 senderUin = 10; int64 recverUin = 20; bytes uuid = 30; } message UploadSuccRsp { int32 retCode = 10; string retMsg = 20; FileInfo fileInfo = 30; } ================================================ FILE: ricq-core/src/pb/cmd0x352/cmd0x352.proto ================================================ syntax = "proto2"; package cmd0x352; /* message DelImgReq { optional uint64 srcUin = 1; optional uint64 dstUin = 2; optional uint32 reqTerm = 3; optional uint32 reqPlatformType = 4; optional uint32 buType = 5; optional bytes buildVer = 6; optional bytes fileResid = 7; optional uint32 picWidth = 8; optional uint32 picHeight = 9; } message DelImgRsp { optional uint32 result = 1; optional bytes failMsg = 2; optional bytes fileResid = 3; } message GetImgUrlReq { optional uint64 srcUin = 1; optional uint64 dstUin = 2; optional bytes fileResid = 3; optional uint32 urlFlag = 4; optional uint32 urlType = 6; optional uint32 reqTerm = 7; optional uint32 reqPlatformType = 8; optional uint32 srcFileType = 9; optional uint32 innerIp = 10; optional bool addressBook = 11; optional uint32 buType = 12; optional bytes buildVer = 13; optional uint32 picUpTimestamp = 14; optional uint32 reqTransferType = 15; } message GetImgUrlRsp { optional bytes fileResid = 1; optional uint32 clientIp = 2; optional uint32 result = 3; optional bytes failMsg = 4; repeated bytes thumbDownUrl = 5; repeated bytes originalDownUrl = 6; optional ImgInfo imgInfo = 7; repeated uint32 downIp = 8; repeated uint32 downPort = 9; optional bytes thumbDownPara = 10; optional bytes originalDownPara = 11; optional bytes downDomain = 12; repeated bytes bigDownUrl = 13; optional bytes bigDownPara = 14; optional bytes bigThumbDownPara = 15; optional uint32 httpsUrlFlag = 16; repeated IPv6Info downIp6 = 26; optional bytes clientIp6 = 27; } message IPv6Info { optional bytes ip6 = 1; optional uint32 port = 2; } */ message ReqBody { optional uint32 subcmd = 1; repeated D352TryUpImgReq tryupImgReq = 2; // repeated GetImgUrlReq getimgUrlReq = 3; // repeated DelImgReq delImgReq = 4; optional uint32 netType = 10; } message RspBody { optional uint32 subcmd = 1; repeated TryUpImgRsp tryupImgRsp = 2; // repeated GetImgUrlRsp getimgUrlRsp = 3; optional bool newBigchan = 4; // repeated DelImgRsp delImgRsp = 5; optional bytes failMsg = 10; } message D352TryUpImgReq { optional uint64 srcUin = 1; optional uint64 dstUin = 2; optional uint64 fileId = 3; optional bytes fileMd5 = 4; optional uint64 fileSize = 5; optional bytes fileName = 6; optional uint32 srcTerm = 7; optional uint32 platformType = 8; optional uint32 innerIp = 9; optional bool addressBook = 10; optional uint32 retry = 11; optional uint32 buType = 12; optional bool picOriginal = 13; optional uint32 picWidth = 14; optional uint32 picHeight = 15; optional uint32 picType = 16; optional bytes buildVer = 17; optional bytes fileIndex = 18; optional uint32 storeDays = 19; optional uint32 tryupStepflag = 20; optional bool rejectTryfast = 21; optional uint32 srvUpload = 22; optional bytes transferUrl = 23; } message TryUpImgRsp { optional uint64 fileId = 1; optional uint32 clientIp = 2; optional uint32 result = 3; optional bytes failMsg = 4; optional bool fileExit = 5; // optional ImgInfo imgInfo = 6; repeated uint32 upIp = 7; repeated uint32 upPort = 8; optional bytes upUkey = 9; optional string upResid = 10; optional bytes upUuid = 11; optional uint64 upOffset = 12; optional uint64 blockSize = 13; optional bytes encryptDstip = 14; optional uint32 roamdays = 15; // repeated IPv6Info upIp6 = 26; optional bytes clientIp6 = 27; optional bytes thumbDownPara = 60; optional bytes originalDownPara = 61; optional bytes downDomain = 62; optional bytes bigDownPara = 64; optional bytes bigThumbDownPara = 65; optional uint32 httpsUrlFlag = 66; // optional TryUpInfo4Busi info4Busi = 1001; } /* message TryUpInfo4Busi { optional bytes fileResid = 1; optional bytes downDomain = 2; optional bytes thumbDownUrl = 3; optional bytes originalDownUrl = 4; optional bytes bigDownUrl = 5; } */ ================================================ FILE: ricq-core/src/pb/cmd0x388/cmd0x388.proto ================================================ syntax = "proto2"; package cmd0x388; message DelImgReq { optional uint64 srcUin = 1; optional uint64 dstUin = 2; optional uint32 reqTerm = 3; optional uint32 reqPlatformType = 4; optional uint32 buType = 5; optional bytes buildVer = 6; optional bytes fileResid = 7; optional uint32 picWidth = 8; optional uint32 picHeight = 9; } message DelImgRsp { optional uint32 result = 1; optional bytes failMsg = 2; optional bytes fileResid = 3; } message ExpRoamExtendInfo { optional bytes resid = 1; } message ExpRoamPicInfo { optional uint32 shopFlag = 1; optional uint32 pkgId = 2; optional bytes picId = 3; } message ExtensionCommPicTryUp { repeated bytes extinfo = 1; } message ExtensionExpRoamTryUp { repeated ExpRoamPicInfo exproamPicInfo = 1; } message GetImgUrlReq { optional uint64 groupCode = 1; optional uint64 dstUin = 2; optional uint64 fileid = 3; optional bytes fileMd5 = 4; optional uint32 urlFlag = 5; optional uint32 urlType = 6; optional uint32 reqTerm = 7; optional uint32 reqPlatformType = 8; optional uint32 innerIp = 9; optional uint32 buType = 10; optional bytes buildVer = 11; optional uint64 fileId = 12; optional uint64 fileSize = 13; optional uint32 originalPic = 14; optional uint32 retryReq = 15; optional uint32 fileHeight = 16; optional uint32 fileWidth = 17; optional uint32 picType = 18; optional uint32 picUpTimestamp = 19; optional uint32 reqTransferType = 20; optional uint64 qqmeetGuildId = 21; optional uint64 qqmeetChannelId = 22; optional bytes downloadIndex = 23; } message GetImgUrlRsp { optional uint64 fileid = 1; optional bytes fileMd5 = 2; optional uint32 result = 3; optional bytes failMsg = 4; optional ImgInfo imgInfo = 5; repeated bytes thumbDownUrl = 6; repeated bytes originalDownUrl = 7; repeated bytes bigDownUrl = 8; repeated uint32 downIp = 9; repeated uint32 downPort = 10; optional bytes downDomain = 11; optional bytes thumbDownPara = 12; optional bytes originalDownPara = 13; optional bytes bigDownPara = 14; optional uint64 fileId = 15; optional uint32 autoDownType = 16; repeated uint32 orderDownType = 17; optional bytes bigThumbDownPara = 19; optional uint32 httpsUrlFlag = 20; repeated IPv6Info downIp6 = 26; optional bytes clientIp6 = 27; } message GetPttUrlReq { optional uint64 groupCode = 1; optional uint64 dstUin = 2; optional uint64 fileid = 3; optional bytes fileMd5 = 4; optional uint32 reqTerm = 5; optional uint32 reqPlatformType = 6; optional uint32 innerIp = 7; optional uint32 buType = 8; optional bytes buildVer = 9; optional uint64 fileId = 10; optional bytes fileKey = 11; optional uint32 codec = 12; optional uint32 buId = 13; optional uint32 reqTransferType = 14; optional uint32 isAuto = 15; } message GetPttUrlRsp { optional uint64 fileid = 1; optional bytes fileMd5 = 2; optional uint32 result = 3; optional bytes failMsg = 4; repeated bytes downUrl = 5; repeated uint32 downIp = 6; repeated uint32 downPort = 7; optional bytes downDomain = 8; optional bytes downPara = 9; optional uint64 fileId = 10; optional uint32 transferType = 11; optional uint32 allowRetry = 12; repeated IPv6Info downIp6 = 26; optional bytes clientIp6 = 27; optional string domain = 28; } message IPv6Info { optional bytes ip6 = 1; optional uint32 port = 2; } message ImgInfo { optional bytes fileMd5 = 1; optional uint32 fileType = 2; optional uint64 fileSize = 3; optional uint32 fileWidth = 4; optional uint32 fileHeight = 5; } message PicSize { optional uint32 original = 1; optional uint32 thumb = 2; optional uint32 high = 3; } message D388ReqBody { optional uint32 netType = 1; optional uint32 subcmd = 2; repeated TryUpImgReq tryupImgReq = 3; repeated GetImgUrlReq getimgUrlReq = 4; repeated TryUpPttReq tryupPttReq = 5; repeated GetPttUrlReq getpttUrlReq = 6; optional uint32 commandId = 7; repeated DelImgReq delImgReq = 8; optional bytes extension = 1001; } message D388RspBody { optional uint32 clientIp = 1; optional uint32 subcmd = 2; repeated D388TryUpImgRsp tryupImgRsp = 3; repeated GetImgUrlRsp getimgUrlRsp = 4; repeated TryUpPttRsp tryupPttRsp = 5; repeated GetPttUrlRsp getpttUrlRsp = 6; repeated DelImgRsp delImgRsp = 7; } message TryUpImgReq { optional uint64 groupCode = 1; optional uint64 srcUin = 2; optional uint64 fileId = 3; optional bytes fileMd5 = 4; optional uint64 fileSize = 5; optional bytes fileName = 6; optional uint32 srcTerm = 7; optional uint32 platformType = 8; optional uint32 buType = 9; optional uint32 picWidth = 10; optional uint32 picHeight = 11; optional uint32 picType = 12; optional bytes buildVer = 13; optional uint32 innerIp = 14; optional uint32 appPicType = 15; optional uint32 originalPic = 16; optional bytes fileIndex = 17; optional uint64 dstUin = 18; optional uint32 srvUpload = 19; optional bytes transferUrl = 20; optional uint64 qqmeetGuildId = 21; optional uint64 qqmeetChannelId = 22; } message D388TryUpImgRsp { optional uint64 fileId = 1; optional uint32 result = 2; optional bytes failMsg = 3; optional bool fileExit = 4; optional ImgInfo imgInfo = 5; repeated uint32 upIp = 6; repeated uint32 upPort = 7; optional bytes upUkey = 8; optional uint64 fileid = 9; optional uint64 upOffset = 10; optional uint64 blockSize = 11; optional bool newBigChan = 12; repeated IPv6Info upIp6 = 26; optional bytes clientIp6 = 27; optional bytes downloadIndex = 28; optional TryUpInfo4Busi info4Busi = 1001; } message TryUpInfo4Busi { optional bytes downDomain = 1; optional bytes thumbDownUrl = 2; optional bytes originalDownUrl = 3; optional bytes bigDownUrl = 4; optional bytes fileResid = 5; } message TryUpPttReq { optional uint64 groupCode = 1; optional uint64 srcUin = 2; optional uint64 fileId = 3; optional bytes fileMd5 = 4; optional uint64 fileSize = 5; optional bytes fileName = 6; optional uint32 srcTerm = 7; optional uint32 platformType = 8; optional uint32 buType = 9; optional bytes buildVer = 10; optional uint32 innerIp = 11; optional uint32 voiceLength = 12; optional bool newUpChan = 13; optional uint32 codec = 14; optional uint32 voiceType = 15; optional uint32 buId = 16; } message TryUpPttRsp { optional uint64 fileId = 1; optional uint32 result = 2; optional bytes failMsg = 3; optional bool fileExit = 4; repeated uint32 upIp = 5; repeated uint32 upPort = 6; optional bytes upUkey = 7; optional uint64 fileid = 8; optional uint64 upOffset = 9; optional uint64 blockSize = 10; optional bytes fileKey = 11; optional uint32 channelType = 12; repeated IPv6Info upIp6 = 26; optional bytes clientIp6 = 27; } ================================================ FILE: ricq-core/src/pb/cmd0x3bb/cmd0x3bb.proto ================================================ syntax = "proto2"; package cmd0x3bb; message AnonyMsg { optional uint32 cmd = 1; optional C3BBReqBody anonyReq = 10; optional C3BBRspBody anonyRsp = 11; } message AnonyStatus { optional uint32 forbidTalking = 1; optional bytes errMsg = 10; } message C3BBReqBody { optional uint64 uin = 1; optional uint64 groupCode = 2; } message C3BBRspBody { optional int32 ret = 1; optional uint64 groupCode = 2; optional bytes anonyName = 3; optional uint32 portraitIndex = 4; optional uint32 bubbleIndex = 5; optional uint32 expiredTime = 6; optional AnonyStatus anonyStatus = 10; optional string color = 15; } ================================================ FILE: ricq-core/src/pb/cmd0x6ff/smbcmd0x519.proto ================================================ syntax = "proto2"; package cmd0x6ff; message C519CRMMsgHead { optional uint32 crmSubCmd = 1; optional uint32 headLen = 2; optional uint32 verNo = 3; optional uint64 kfUin = 4; optional uint32 seq = 5; optional uint32 packNum = 6; optional uint32 curPack = 7; optional string bufSig = 8; optional uint64 pubQq = 9; optional uint32 clienttype = 10; optional uint64 laborUin = 11; optional string laborName = 12; optional uint64 puin = 13; } message GetNavigationMenuReqBody { optional uint64 puin = 1; optional uint64 uin = 2; optional uint32 verNo = 3; } message GetNavigationMenuRspBody { optional C519RetInfo ret = 1; optional int32 isShow = 2; optional string uctMsg = 3; optional uint32 verNo = 4; } message C519ReqBody { optional uint32 subCmd = 1; optional C519CRMMsgHead crmCommonHead = 2; optional GetAddressDetailListReqBody getAddressDetailListReqBody = 33; optional GetNavigationMenuReqBody getNavigationMenuReq = 35; } message C519RetInfo { optional uint32 retCode = 1; optional string errorMsg = 2; } message C519RspBody { optional uint32 subCmd = 1; optional C519CRMMsgHead crmCommonHead = 2; optional GetAddressDetailListRspBody getAddressDetailListRspBody = 33; optional GetNavigationMenuRspBody getNavigationMenuRsp = 35; } message GetAddressDetailListReqBody { optional fixed32 timestamp = 1; optional fixed64 timestamp2 = 2; } message GetAddressDetailListRspBody { optional C519RetInfo ret = 1; optional fixed32 timestamp = 2; optional bool full = 3; repeated AddressDetail addressDetail = 4; optional fixed64 timestamp2 = 5; } message AddressDetail { optional uint32 aid = 1; optional fixed32 modifyTime = 2; optional fixed32 createTime = 3; optional uint32 status = 4; optional uint32 groupid = 5; optional bytes addGroupName = 6; optional bytes name = 7; optional uint32 gender = 8; optional fixed32 birthday = 9; optional bytes company0 = 10; optional bytes companyPosition0 = 11; optional bytes company1 = 12; optional bytes companyPosition1 = 13; optional bytes fixedPhone0 = 14; optional bytes fixedPhone1 = 15; optional bytes email0 = 16; optional bytes email1 = 17; optional bytes fax0 = 18; optional bytes fax1 = 19; optional bytes comment = 20; optional bytes headUrl = 21; repeated AddressMobileInfo mobilePhone = 22; optional bool mobilePhoneUpdated = 23; repeated AddressQQinfo qq = 24; optional bool qqPhoneUpdated = 25; optional fixed64 modifyTime2 = 26; optional NewBizClientRegion clientRegion = 27; optional NewBizClientRegionCode clientRegionCode = 28; } message AddressMobileInfo { optional uint32 index = 1; optional bytes account = 2; optional bytes formattedAccount = 5; } message AddressQQinfo { optional uint32 index = 1; optional uint64 account = 2; } message NewBizClientRegion { optional string clientNation = 1; optional string clientProvince = 2; optional string clientCity = 3; optional string clientRegion = 4; } message NewBizClientRegionCode { optional uint64 nationid = 1; optional uint64 provinceid = 2; optional uint64 cityid = 3; optional uint64 regionid = 4; } ================================================ FILE: ricq-core/src/pb/cmd0x6ff/subcmd0x501.proto ================================================ syntax = "proto2"; package cmd0x6ff; message C501ReqBody { optional SubCmd0x501ReqBody ReqBody = 1281; } message C501RspBody { optional SubCmd0x501RspBody RspBody = 1281; } message SubCmd0x501ReqBody { optional uint64 uin = 1; optional uint32 idcId = 2; optional uint32 appid = 3; optional uint32 loginSigType = 4; optional bytes loginSigTicket = 5; optional uint32 requestFlag = 6; repeated uint32 serviceTypes = 7; optional uint32 bid = 8; } message SubCmd0x501RspBody { optional bytes sigSession = 1; optional bytes sessionKey = 2; repeated SrvAddrs addrs = 3; } message SrvAddrs { optional uint32 serviceType = 1; repeated IpAddr addrs = 2; } message IpAddr { optional uint32 type = 1; optional fixed32 ip = 2; optional uint32 port = 3; optional uint32 area = 4; } ================================================ FILE: ricq-core/src/pb/cmd0x899/cmd0x899.proto ================================================ syntax = "proto2"; package cmd0x899; message ReqBody { optional uint64 groupCode = 1; optional uint64 startUin = 2; optional uint32 identifyFlag = 3; repeated uint64 uinList = 4; optional memberlist memberlistOpt = 5; optional uint32 memberNum = 6; optional uint32 filterMethod = 7; optional uint32 onlineFlag = 8; } message RspBody { optional uint64 groupCode = 1; optional uint64 startUin = 2; optional uint32 identifyFlag = 3; repeated memberlist memberlist = 4; optional bytes errorinfo = 5; } message memberlist { optional uint64 memberUin = 1; optional uint32 uinFlag = 2; optional uint32 uinFlagex = 3; optional uint32 uinMobileFlag = 4; optional uint32 uinArchFlag = 5; optional uint32 joinTime = 6; optional uint32 oldMsgSeq = 7; optional uint32 newMsgSeq = 8; optional uint32 lastSpeakTime = 9; optional uint32 level = 10; optional uint32 point = 11; optional uint32 shutupTimestap = 12; optional uint32 flagex2 = 13; optional bytes specialTitle = 14; optional uint32 specialTitleExpireTime = 15; optional uint32 activeDay = 16; optional bytes uinKey = 17; optional uint32 privilege = 18; optional bytes richInfo = 19; } message uin_key { optional uint64 groupCode = 1; optional uint64 memberUin = 2; optional uint64 genTime = 3; optional uint32 validTime = 4; optional uint32 randNum = 5; } ================================================ FILE: ricq-core/src/pb/data.proto ================================================ syntax = "proto3"; package pb; message SSOReserveField { int32 flag = 9; string qimei = 12; int32 newconn_flag = 14; string uid = 16; int32 imsi = 18; int32 network_type = 19; int32 ip_stack_type = 20; int32 message_type = 21; SsoSecureInfo sec_info = 24; int32 sso_ip_origin = 28; } message SsoSecureInfo { bytes sec_sig = 1; bytes sec_device_token = 2; bytes sec_extra = 3; } message DeviceInfo { string bootloader = 1; string procVersion = 2; string codename = 3; string incremental = 4; string fingerprint = 5; string bootId = 6; string androidId = 7; string baseBand = 8; string innerVersion = 9; } message RequestBody { repeated ConfigSeq rpt_config_list = 1; } message ConfigSeq { int32 type = 1; int32 version = 2; } message D50ReqBody { int64 appid = 1; int32 maxPkgSize = 2; int32 startTime = 3; int32 startIndex = 4; int32 reqNum = 5; repeated int64 uinList = 6; int32 reqMusicSwitch = 91001; int32 reqMutualmarkAlienation = 101001; int32 reqMutualmarkScore = 141001; int32 reqKsingSwitch = 151001; int32 reqMutualmarkLbsshare = 181001; } message D388ReqBody { int32 netType = 1; int32 subcmd = 2; repeated TryUpImgReq msgTryUpImgReq = 3; repeated TryUpPttReq msgTryUpPttReq = 5; repeated GetPttUrlReq msgGetPttReq = 6; int32 commandId = 7; bytes extension = 1001; } message D388RespBody { int32 clientIp = 1; int32 subCmd = 2; repeated TryUpImgResp msgTryUpImgRsp = 3; repeated TryUpPttResp msgTryUpPttRsp = 5; repeated GetPttUrlRsp msgGetPttUrlRsp = 6; } message GetPttUrlReq { int64 groupCode = 1; int64 dstUin = 2; int64 fileId = 3; bytes fileMd5 = 4; int32 reqTerm = 5; int32 reqPlatformType = 6; int32 innerIp = 7; int32 buType = 8; bytes buildVer = 9; //int64 fileId = 10; bytes fileKey = 11; int32 codec = 12; int32 buId = 13; int32 reqTransferType = 14; int32 isAuto = 15; } message GetPttUrlRsp { int64 fileId = 1; bytes fileMd5 = 2; int32 result = 3; bytes failMsg = 4; bytes bytesDownUrl = 5; repeated int32 uint32DownIp = 6; repeated int32 uint32DownPort = 7; bytes downDomain = 8; bytes downPara = 9; //int64 fileId = 10; int32 transferType = 11; int32 allowRetry = 12; //repeated IPv6Info msgDownIp6 = 26; bytes clientIp6 = 27; string strDomain = 28; } message ReqDataHighwayHead { DataHighwayHead msgBasehead = 1; SegHead msgSeghead = 2; bytes reqExtendinfo = 3; int64 timestamp = 4; //LoginSigHead? msgLoginSigHead = 5; } message RspDataHighwayHead { DataHighwayHead msgBasehead = 1; SegHead msgSeghead = 2; int32 errorCode = 3; int32 allowRetry = 4; int32 cachecost = 5; int32 htcost = 6; bytes rspExtendinfo = 7; int64 timestamp = 8; int64 range = 9; int32 isReset = 10; } message DataHighwayHead { int32 version = 1; string uin = 2; string command = 3; int32 seq = 4; int32 retryTimes = 5; int32 appid = 6; int32 dataflag = 7; int32 commandId = 8; string buildVer = 9; int32 localeId = 10; } message SegHead { int32 serviceid = 1; int64 filesize = 2; int64 dataoffset = 3; int32 datalength = 4; int32 rtcode = 5; bytes serviceticket = 6; int32 flag = 7; bytes md5 = 8; bytes fileMd5 = 9; int32 cacheAddr = 10; int32 queryTimes = 11; int32 updateCacheip = 12; } message TryUpImgReq { int64 groupCode = 1; int64 srcUin = 2; int64 fileId = 3; bytes fileMd5 = 4; int64 fileSize = 5; string fileName = 6; int32 srcTerm = 7; int32 platformType = 8; int32 buType = 9; int32 picWidth = 10; int32 picHeight = 11; int32 picType = 12; string buildVer = 13; int32 innerIp = 14; int32 appPicType = 15; int32 originalPic = 16; bytes fileIndex = 17; int64 dstUin = 18; int32 srvUpload = 19; bytes transferUrl = 20; } message TryUpImgResp { int64 fileId = 1; int32 result = 2; string failMsg = 3; bool boolFileExit = 4; ImgInfo msgImgInfo = 5; repeated uint32 uint32UpIp = 6; repeated uint32 uint32UpPort = 7; bytes upUkey = 8; int64 fid = 9; } message TryUpPttReq { int64 groupCode = 1; int64 srcUin = 2; int64 fileId = 3; bytes fileMd5 = 4; int64 fileSize = 5; bytes fileName = 6; int32 srcTerm = 7; int32 platformType = 8; int32 buType = 9; string buildVer = 10; int32 innerIp = 11; int32 voiceLength = 12; bool boolNewUpChan = 13; int32 codec = 14; int32 voiceType = 15; int32 buId = 16; } message TryUpPttResp { int64 fileId = 1; int32 result = 2; string failMsg = 3; bool boolFileExit = 4; repeated int32 uint32UpIp = 5; repeated int32 uint32UpPort = 6; bytes upUkey = 7; int64 fileId2 = 8; int64 upOffset = 9; int64 blockSize = 10; bytes fileKey = 11; int32 channelType = 12; // List? msgUpIp6 = 26; // bytes clientIp6 = 27; } message ImgInfo { bytes fileMd5 = 1; int32 fileType = 2; int64 fileSize = 3; int32 fileWidth = 4; int32 fileHeight = 5; } message DeleteMessageRequest { repeated MessageItem items = 1; } message MessageItem { int64 fromUin = 1; int64 toUin = 2; int32 msgType = 3; int32 msgSeq = 4; int64 msgUid = 5; bytes sig = 7; } message SubD4 { int64 uin = 1; } message Sub8A { repeated Sub8AMsgInfo msg_info = 1; int32 appId = 2; int32 instId = 3; int32 longMessageFlag = 4; bytes reserved = 5; } message Sub8AMsgInfo { int64 fromUin = 1; int64 toUin = 2; int32 msgSeq = 3; int64 msgUid = 4; int64 msgTime = 5; int32 msgRandom = 6; int32 pkgNum = 7; int32 pkgIndex = 8; int32 devSeq = 9; } message SubB3 { int32 type = 1; SubB3AddFrdNotify msgAddFrdNotify = 2; } message SubB3AddFrdNotify { int64 uin = 1; string nick = 5; } message Sub44 { Sub44FriendSyncMsg friendSyncMsg = 1; Sub44GroupSyncMsg groupSyncMsg = 2; } message Sub44FriendSyncMsg { int64 uin = 1; int64 fUin = 2; int32 processType = 3; int32 time = 4; int32 processFlag = 5; int32 sourceId = 6; int32 sourceSubId = 7; repeated string strWording = 8; } message Sub44GroupSyncMsg { int32 msgType = 1; int64 msgSeq = 2; int64 grpCode = 3; int64 gaCode = 4; int64 optUin1 = 5; int64 optUin2 = 6; bytes msgBuf = 7; bytes authKey = 8; int32 msgStatus = 9; int64 actionUin = 10; int64 actionTime = 11; int32 curMaxMemCount = 12; int32 nextMaxMemCount = 13; int32 curMemCount = 14; int32 reqSrcId = 15; int32 reqSrcSubId = 16; int32 inviterRole = 17; int32 extAdminNum = 18; int32 processFlag = 19; } message GroupMemberReqBody { int64 groupCode = 1; int64 uin = 2; bool newClient = 3; int32 clientType = 4; int32 richCardNameVer = 5; } message GroupMemberRspBody { int64 groupCode = 1; int32 selfRole = 2; GroupMemberInfo memInfo = 3; bool boolSelfLocationShared = 4; int32 groupType = 5; } message GroupMemberInfo { int64 uin = 1; int32 result = 2; bytes errmsg = 3; bool IsFriend = 4; bytes remark = 5; bool IsConcerned = 6; int32 credit = 7; bytes card = 8; int32 sex = 9; bytes location = 10; bytes nick = 11; int32 age = 12; bytes lev = 13; int64 join = 14; int64 lastSpeak = 15; //repeated CustomEntry customEnties = 16; //repeated GBarInfo gbarConcerned = 17; bytes gbarTitle = 18; bytes gbarUrl = 19; int32 gbarCnt = 20; bool isAllowModCard = 21; bool isVip = 22; bool isYearVip = 23; bool isSuperVip = 24; bool isSuperQq = 25; int32 vipLev = 26; int32 role = 27; bool locationShared = 28; int64 int64Distance = 29; int32 concernType = 30; bytes specialTitle = 31; int32 specialTitleExpireTime = 32; //FlowersEntry flowerEntry = 33; //TeamEntry teamEntry = 34; bytes phoneNum = 35; bytes job = 36; int32 medalId = 37; int32 level = 39; string honor = 41; } ================================================ FILE: ricq-core/src/pb/longmsg/longmsg.proto ================================================ syntax = "proto3"; package longmsg; message LongMsgDeleteReq { bytes msgResid = 1; int32 msgType = 2; } message LongMsgDeleteRsp { int32 result = 1; bytes msgResid = 2; } message LongMsgDownReq { int32 srcUin = 1; bytes msgResid = 2; int32 msgType = 3; int32 needCache = 4; } message LongMsgDownRsp { int32 result = 1; bytes msgResid = 2; bytes msgContent = 3; } message LongMsgUpReq { int32 msgType = 1; int64 dstUin = 2; int32 msgId = 3; bytes msgContent = 4; int32 storeType = 5; bytes msgUkey = 6; int32 needCache = 7; } message LongMsgUpRsp { int32 result = 1; int32 msgId = 2; bytes msgResid = 3; } message LongReqBody { int32 subcmd = 1; int32 termType = 2; int32 platformType = 3; repeated LongMsgUpReq msgUpReq = 4; repeated LongMsgDownReq msgDownReq = 5; repeated LongMsgDeleteReq msgDelReq = 6; int32 agentType = 10; } message LongRspBody { int32 subcmd = 1; repeated LongMsgUpRsp msgUpRsp = 2; repeated LongMsgDownRsp msgDownRsp = 3; repeated LongMsgDeleteRsp msgDelRsp = 4; } ================================================ FILE: ricq-core/src/pb/mod.rs ================================================ #![allow(clippy::all)] include!(concat!(env!("OUT_DIR"), "/pb.rs")); macro_rules! add_includes { ($( $name:ident ),* $(,)?) => { $( pub mod $name { include!(concat!(env!("OUT_DIR"), "/", stringify!($name), ".rs")); } )* }; } add_includes!( cmd0x346, cmd0x352, cmd0x388, cmd0x3bb, cmd0x6ff, cmd0x899, longmsg, msf, msg, msgtype0x210, multimsg, notify, oidb, online_status, profilecard, sig_act, structmsg, short_video, ); ================================================ FILE: ricq-core/src/pb/msf/register_proxy.proto ================================================ syntax = "proto2"; package msf; message DiscussList { optional uint64 discussCode = 1; optional uint64 discussSeq = 2; optional uint64 memberSeq = 3; optional uint64 infoSeq = 4; optional bool bHotGroup = 5; optional uint64 redpackTime = 6; optional bool hasMsg = 7; optional int64 dicussFlag = 8; } message GroupList { optional uint64 groupCode = 1; optional uint64 groupSeq = 2; optional uint64 memberSeq = 3; optional uint64 mask = 4; optional uint64 redpackTime = 5; optional bool hasMsg = 6; optional int64 groupFlag = 7; optional uint64 groupType = 8; optional uint32 groupNameSeq = 9; optional uint32 groupMemberSeq = 10; optional uint32 uinFlagEx2 = 11; optional uint32 importantMsgLatestSeq = 12; } message SvcPbResponsePullDisMsgProxy { optional uint64 memberSeq = 1; optional bytes content = 2; } message SvcRegisterProxyMsgResp { optional uint32 result = 1; optional bytes errMsg = 2; optional uint32 flag = 3; optional uint32 seq = 4; optional SvcResponseMsgInfo info = 5; repeated GroupList groupList = 6; repeated DiscussList discussList = 7; repeated SvcResponsePbPullGroupMsgProxy groupMsg = 8; repeated SvcPbResponsePullDisMsgProxy discussMsg = 9; optional bytes c2CMsg = 10; optional bytes pubAccountMsg = 11; optional uint32 discussListFlag = 12; } message SvcResponseMsgInfo { optional uint32 groupNum = 1; optional uint32 discussNum = 2; } message SvcResponsePbPullGroupMsgProxy { optional uint64 memberSeq = 1; optional bytes content = 2; } ================================================ FILE: ricq-core/src/pb/msg/TextMsgExt.proto ================================================ syntax = "proto2"; package msg; message ExtChannelInfo { optional uint64 guildId = 1; optional uint64 channelId = 2; } message TextResvAttr { optional bytes wording = 1; optional uint32 textAnalysisResult = 2; optional uint32 atType = 3; optional uint64 atMemberUin = 4; optional uint64 atMemberTinyid = 5; optional ExtRoleInfo atMemberRoleInfo = 6; optional ExtRoleInfo atRoleInfo = 7; optional ExtChannelInfo atChannelInfo = 8; } message ExtRoleInfo { optional uint64 id = 1; optional bytes info = 2; optional uint32 flag = 3; } ================================================ FILE: ricq-core/src/pb/msg/head.proto ================================================ syntax = "proto2"; package msg; message C2CHead { optional uint64 toUin = 1; optional uint64 fromUin = 2; optional uint32 ccType = 3; optional uint32 ccCmd = 4; optional bytes authPicSig = 5; optional bytes authSig = 6; optional bytes authBuf = 7; optional uint32 serverTime = 8; optional uint32 clientTime = 9; optional uint32 rand = 10; optional string phoneNumber = 11; } message CSHead { optional uint64 uin = 1; optional uint32 command = 2; optional uint32 seq = 3; optional uint32 version = 4; optional uint32 retryTimes = 5; optional uint32 clientType = 6; optional uint32 pubno = 7; optional uint32 localid = 8; optional uint32 timezone = 9; optional fixed32 clientIp = 10; optional uint32 clientPort = 11; optional fixed32 connIp = 12; optional uint32 connPort = 13; optional fixed32 interfaceIp = 14; optional uint32 interfacePort = 15; optional fixed32 actualIp = 16; optional uint32 flag = 17; optional fixed32 timestamp = 18; optional uint32 subcmd = 19; optional uint32 result = 20; optional uint32 appId = 21; optional uint32 instanceId = 22; optional uint64 sessionId = 23; optional uint32 idcId = 24; } message DeltaHead { optional uint64 totalLen = 1; optional uint64 offset = 2; optional uint64 ackOffset = 3; optional bytes cookie = 4; optional bytes ackCookie = 5; optional uint32 result = 6; optional uint32 flags = 7; } message IMHead { optional uint32 headType = 1; optional CSHead csHead = 2; optional S2CHead s2CHead = 3; optional HttpConnHead httpconnHead = 4; optional uint32 paintFlag = 5; optional LoginSig loginSig = 6; optional DeltaHead deltaHead = 7; optional C2CHead c2CHead = 8; } message HttpConnHead { optional uint64 uin = 1; optional uint32 command = 2; optional uint32 subCommand = 3; optional uint32 seq = 4; optional uint32 version = 5; optional uint32 retryTimes = 6; optional uint32 clientType = 7; optional uint32 pubNo = 8; optional uint32 localId = 9; optional uint32 timeZone = 10; optional fixed32 clientIp = 11; optional uint32 clientPort = 12; optional fixed32 qzhttpIp = 13; optional uint32 qzhttpPort = 14; optional fixed32 sppIp = 15; optional uint32 sppPort = 16; optional uint32 flag = 17; optional bytes key = 18; optional uint32 compressType = 19; optional uint32 originSize = 20; optional uint32 errorCode = 21; optional RedirectMsg redirect = 22; optional uint32 commandId = 23; optional uint32 serviceCmdid = 24; optional TransOidbHead oidbhead = 25; } message LoginSig { optional uint32 type = 1; optional bytes sig = 2; } message RedirectMsg { optional fixed32 lastRedirectIp = 1; optional uint32 lastRedirectPort = 2; optional fixed32 redirectIp = 3; optional uint32 redirectPort = 4; optional uint32 redirectCount = 5; } message S2CHead { optional uint32 subMsgtype = 1; optional uint32 msgType = 2; optional uint64 fromUin = 3; optional uint32 msgId = 4; optional fixed32 relayIp = 5; optional uint32 relayPort = 6; optional uint64 toUin = 7; } message TransOidbHead { optional uint32 command = 1; optional uint32 serviceType = 2; optional uint32 result = 3; optional string errorMsg = 4; } ================================================ FILE: ricq-core/src/pb/msg/msg.proto ================================================ syntax = "proto2"; package msg; message GetMessageRequest { optional SyncFlag syncFlag = 1; optional bytes syncCookie = 2; optional int32 rambleFlag = 3; optional int32 latestRambleNumber = 4; optional int32 otherRambleNumber = 5; optional int32 onlineSyncFlag = 6; optional int32 contextFlag = 7; optional int32 whisperSessionId = 8; optional int32 msgReqType = 9; optional bytes pubaccountCookie = 10; optional bytes msgCtrlBuf = 11; optional bytes serverBuf = 12; } message SendMessageRequest { optional RoutingHead routingHead = 1; optional ContentHead contentHead = 2; optional MessageBody msgBody = 3; optional int32 msgSeq = 4; optional int32 msgRand = 5; optional bytes syncCookie = 6; //MsgComm.AppShareInfo? appShare = 7; optional int32 msgVia = 8; optional int32 dataStatist = 9; //MultiMsgAssist? multiMsgAssist = 10; //PbInputNotifyInfo? inputNotifyInfo = 11; optional MsgCtrl msgCtrl = 12; //ImReceipt.ReceiptReq? receiptReq = 13; optional int32 multiSendSeq = 14; } message SendMessageResponse { optional int32 result = 1; optional string errMsg = 2; } message MsgWithDrawReq { repeated C2CMsgWithDrawReq c2cWithDraw = 1; repeated GroupMsgWithDrawReq groupWithDraw = 2; } message C2CMsgWithDrawReq{ repeated C2CMsgInfo msgInfo = 1; optional int32 longMessageFlag = 2; optional bytes reserved = 3; optional int32 subCmd = 4; } message GroupMsgWithDrawReq{ optional int32 subCmd = 1; optional int32 groupType = 2; optional int64 groupCode = 3; repeated GroupMsgInfo msgList = 4; optional bytes userDef = 5; } message MsgWithDrawResp { repeated C2CMsgWithDrawResp c2cWithDraw = 1; repeated GroupMsgWithDrawResp groupWithDraw = 2; } message C2CMsgWithDrawResp { optional int32 result = 1; optional string errMsg = 2; } message GroupMsgWithDrawResp { optional int32 result = 1; optional string errMsg = 2; } message GroupMsgInfo { optional int32 msgSeq = 1; optional int32 msgRandom = 2; optional int32 msgType = 3; } message C2CMsgInfo { optional int64 fromUin = 1; optional int64 toUin = 2; optional int32 msgSeq = 3; optional int64 msgUid = 4; optional int64 msgTime = 5; optional int32 msgRandom = 6; optional int32 pkgNum = 7; optional int32 pkgIndex = 8; optional int32 divSeq = 9; optional int32 msgType = 10; optional RoutingHead routingHead = 20; } message RoutingHead { oneof RoutingHead{ C2C c2c = 1; Grp grp = 2; GrpTmp grpTmp = 3; WPATmp wpaTmp = 6; } /* Dis dis = 4; DisTmp disTmp = 5; SecretFileHead? secretFile = 7; PublicPlat? publicPlat = 8; TransMsg? transMsg = 9; AddressListTmp? addressList = 10; RichStatusTmp? richStatusTmp = 11; TransCmd? transCmd = 12; AccostTmp? accostTmp = 13; PubGroupTmp? pubGroupTmp = 14; Trans0x211? trans0x211 = 15; BusinessWPATmp? businessWpaTmp = 16; AuthTmp? authTmp = 17; BsnsTmp? bsnsTmp = 18; QQQueryBusinessTmp? qqQuerybusinessTmp = 19; NearByDatingTmp? nearbyDatingTmp = 20; NearByAssistantTmp? nearbyAssistantTmp = 21; CommTmp? commTmp = 22; */ } message WPATmp { optional uint64 toUin = 1; optional bytes sig = 2; } message C2C { optional int64 toUin = 1; } message Grp { optional int64 groupCode = 1; } message GrpTmp { optional int64 groupUin = 1; optional int64 toUin = 2; } message MsgCtrl { optional int32 msgFlag = 1; } message GetMessageResponse { optional int32 result = 1; optional string errorMessage = 2; optional bytes syncCookie = 3; optional SyncFlag syncFlag = 4; repeated UinPairMessage uinPairMsgs = 5; optional int64 bindUin = 6; optional int32 msgRspType = 7; optional bytes pubAccountCookie = 8; optional bool isPartialSync = 9; optional bytes msgCtrlBuf = 10; } message PushMessagePacket { optional Message message = 1; optional int32 svrip = 2; optional bytes pushToken = 3; optional int32 pingFLag = 4; optional int32 generalFlag = 9; } message UinPairMessage { optional int32 lastReadTime = 1; optional int64 peerUin = 2; optional int32 msgCompleted = 3; repeated Message messages = 4; } message Message { optional MessageHead head = 1; optional ContentHead content = 2; optional MessageBody body = 3; } message MessageBody { optional RichText richText = 1; optional bytes msgContent = 2; optional bytes msgEncryptContent = 3; } message RichText { optional Attr attr = 1; repeated Elem elems = 2; optional NotOnlineFile notOnlineFile = 3; optional Ptt ptt = 4; } message Elem { oneof elem { Text text = 1; Face face = 2; OnlineImage onlineImage = 3; NotOnlineImage notOnlineImage = 4; TransElem transElemInfo = 5; MarketFace marketFace = 6; //ElemFlags elemFlags = 7; CustomFace customFace = 8; ElemFlags2 elemFlags2 = 9; //FunFace funFace = 10; //SecretFileMsg secretFile = 11; RichMsg richMsg = 12; GroupFile groupFile = 13; //PubGroup pubGroup = 14; //MarketTrans marketTrans = 15; ExtraInfo extraInfo = 16; //ShakeWindow? shakeWindow = 17; //PubAccount? pubAccount = 18; VideoFile videoFile = 19; //TipsInfo? tipsInfo = 20; AnonymousGroupMessage anonGroupMsg = 21; //QQLiveOld? qqLiveOld = 22; //LifeOnlineAccount? lifeOnline = 23; QQWalletMsg QQWalletMsg = 24; //CrmElem? crmElem = 25; //ConferenceTipsInfo? conferenceTipsInfo = 26; //RedBagInfo? redbagInfo = 27; //LowVersionTips? lowVersionTips = 28; //bytes bankcodeCtrlInfo = 29; //NearByMessageType? nearByMsg = 30; CustomElem customElem = 31; //LocationInfo? locationInfo = 32; //PubAccInfo? pubAccInfo = 33; //SmallEmoji? smallEmoji = 34; //FSJMessageElem? fsjMsgElem = 35; //ArkAppElem? arkApp = 36; GeneralFlags generalFlags = 37; //CustomFace? hcFlashPic = 38; //DeliverGiftMsg? deliverGiftMsg = 39; //BitAppMsg? bitappMsg = 40; //OpenQQData? openQqData = 41; //ApolloActMsg? apolloMsg = 42; //GroupPubAccountInfo? groupPubAccInfo = 43; //BlessingMessage? blessMsg = 44; SourceMsg srcMsg = 45; //LolaMsg? lolaMsg = 46; //GroupBusinessMsg? groupBusinessMsg = 47; //WorkflowNotifyMsg? msgWorkflowNotify = 48; //PatsElem? patElem = 49; //GroupPostElem? groupPostElem = 50; LightApp lightApp = 51; //EIMInfo? eimInfo = 52; CommonElem commonElem = 53; } } message MarketFace { optional bytes faceName = 1; optional uint32 itemType = 2; optional uint32 faceInfo = 3; optional bytes faceId = 4; optional uint32 tabId = 5; optional uint32 subType = 6; optional bytes key = 7; optional bytes param = 8; optional uint32 mediaType = 9; optional uint32 imageWidth = 10; optional uint32 imageHeight = 11; optional bytes mobileparam = 12; optional bytes pbReserve = 13; } message ElemFlags2 { optional uint32 colorTextId = 1; optional uint64 msgId = 2; optional uint32 whisperSessionId = 3; optional uint32 pttChangeBit = 4; optional uint32 vipStatus = 5; optional uint32 compatibleId = 6; repeated Inst insts = 7; optional uint32 msgRptCnt = 8; optional Inst srcInst = 9; optional uint32 longtitude = 10; optional uint32 latitude = 11; optional uint32 customFont = 12; optional PcSupportDef pcSupportDef = 13; optional uint32 crmFlags = 14; message Inst { optional uint32 appId = 1; optional uint32 instId = 2; } } message PcSupportDef { optional uint32 pcPtlBegin = 1; optional uint32 pcPtlEnd = 2; optional uint32 macPtlBegin = 3; optional uint32 macPtlEnd = 4; repeated uint32 ptlsSupport = 5; repeated uint32 ptlsNotSupport = 6; } message CommonElem { optional int32 serviceType = 1; optional bytes pbElem = 2; optional int32 businessType = 3; } message QQWalletMsg { optional QQWalletAioBody aioBody = 1; } message QQWalletAioBody { optional uint64 sendUin = 1; optional QQWalletAioElem sender = 2; optional QQWalletAioElem receiver = 3; optional sint32 ChannelId = 4; optional sint32 templateId = 5; optional uint32 resend = 6; optional uint32 msgPriority = 7; optional sint32 redType = 8; optional bytes billNo = 9; optional bytes authKey = 10; optional sint32 sessionType = 11; optional sint32 msgType = 12; optional sint32 envelOpeId = 13; optional bytes name = 14; optional sint32 confType = 15; optional sint32 msgFrom = 16; optional bytes pcBody = 17; optional bytes index = 18; optional uint32 redChannel = 19; repeated uint64 grapUin = 20; optional bytes pbReserve = 21; } message QQWalletAioElem{ optional uint32 background = 1; optional uint32 icon = 2; optional string title = 3; optional string subtitle = 4; optional string content = 5; optional bytes linkUrl = 6; optional bytes blackStripe = 7; optional bytes notice = 8; optional uint32 titleColor = 9; optional uint32 subtitleColor = 10; optional bytes actionsPriority = 11; optional bytes jumpUrl = 12; optional bytes nativeIos = 13; optional bytes nativeAndroid = 14; optional bytes iconUrl = 15; optional uint32 contentColor = 16; optional uint32 contentBgColor = 17; optional bytes aioImageLeft = 18; optional bytes aioImageRight = 19; optional bytes cftImage = 20; optional bytes pbReserve = 21; } message RichMsg { optional bytes template1 = 1; optional int32 serviceId = 2; optional bytes msgResId = 3; optional int32 rand = 4; optional int32 seq = 5; } message CustomElem { optional bytes desc = 1; optional bytes data = 2; optional int32 enumType = 3; optional bytes ext = 4; optional bytes sound = 5; } message Text { optional string str = 1; optional string link = 2; optional bytes attr6Buf = 3; optional bytes attr7Buf = 4; optional bytes buf = 11; optional bytes pbReserve = 12; } message Attr { optional int32 codePage = 1; optional int32 time = 2; optional int32 random = 3; optional int32 color = 4; optional int32 size = 5; optional int32 effect = 6; optional int32 charSet = 7; optional int32 pitchAndFamily = 8; optional string fontName = 9; optional bytes reserveData = 10; } message Ptt { optional int32 fileType = 1; optional int64 srcUin = 2; optional bytes fileUuid = 3; optional bytes fileMd5 = 4; optional string fileName = 5; optional int32 fileSize = 6; optional bytes reserve = 7; optional int32 fileId = 8; optional int32 serverIp = 9; optional int32 serverPort = 10; optional bool boolValid = 11; optional bytes signature = 12; optional bytes shortcut = 13; optional bytes fileKey = 14; optional int32 magicPttIndex = 15; optional int32 voiceSwitch = 16; optional bytes pttUrl = 17; optional bytes groupFileKey = 18; optional int32 time = 19; optional bytes downPara = 20; optional int32 format = 29; optional bytes pbReserve = 30; repeated bytes bytesPttUrls = 31; optional int32 downloadFlag = 32; } message OnlineImage { optional bytes guid = 1; optional bytes filePath = 2; optional bytes oldVerSendFile = 3; } message NotOnlineImage { optional string filePath = 1; optional uint32 fileLen = 2; optional string downloadPath = 3; optional bytes oldVerSendFile = 4; optional int32 imgType = 5; optional bytes previewsImage = 6; optional bytes picMd5 = 7; optional uint32 picHeight = 8; optional uint32 picWidth = 9; optional string resId = 10; optional bytes flag = 11; optional string thumbUrl = 12; optional int32 original = 13; optional string bigUrl = 14; optional string origUrl = 15; optional int32 bizType = 16; optional int32 result = 17; optional int32 index = 18; optional bytes opFaceBuf = 19; optional bool oldPicMd5 = 20; optional int32 thumbWidth = 21; optional int32 thumbHeight = 22; optional int32 fileId = 23; optional int32 showLen = 24; optional int32 downloadLen = 25; optional bytes pbReserve = 29; } message NotOnlineFile { optional int32 fileType = 1; optional bytes sig = 2; optional bytes fileUuid = 3; optional bytes fileMd5 = 4; optional bytes fileName = 5; optional int64 fileSize = 6; optional bytes note = 7; optional int32 reserved = 8; optional int32 subcmd = 9; optional int32 microCloud = 10; repeated bytes bytesFileUrls = 11; optional int32 downloadFlag = 12; optional int32 dangerEvel = 50; optional int32 lifeTime = 51; optional int32 uploadTime = 52; optional int32 absFileType = 53; optional int32 clientType = 54; optional int32 expireTime = 55; optional bytes pbReserve = 56; } message TransElem { optional int32 elemType = 1; optional bytes elemValue = 2; } message ExtraInfo { optional bytes nick = 1; optional bytes groupCard = 2; optional int32 level = 3; optional int32 flags = 4; optional int32 groupMask = 5; optional int32 msgTailId = 6; optional bytes senderTitle = 7; optional bytes apnsTips = 8; optional int64 uin = 9; optional int32 msgStateFlag = 10; optional int32 apnsSoundType = 11; optional int32 newGroupFlag = 12; } message GroupFile { optional bytes filename = 1; optional int64 fileSize = 2; optional bytes fileId = 3; optional bytes batchId = 4; optional bytes fileKey = 5; optional bytes mark = 6; optional int64 sequence = 7; optional bytes batchItemId = 8; optional int32 feedMsgTime = 9; optional bytes pbReserve = 10; } message AnonymousGroupMessage { optional int32 flags = 1; optional bytes anonId = 2; optional bytes anonNick = 3; optional int32 headPortrait = 4; optional int32 expireTime = 5; optional int32 bubbleId = 6; optional bytes rankColor = 7; } message VideoFile { optional bytes fileUuid = 1; optional bytes fileMd5 = 2; optional string fileName = 3; optional int32 fileFormat = 4; optional int32 fileTime = 5; optional int32 fileSize = 6; optional int32 thumbWidth = 7; optional int32 thumbHeight = 8; optional bytes thumbFileMd5 = 9; optional bytes source = 10; optional int32 thumbFileSize = 11; optional int32 busiType = 12; optional int32 fromChatType = 13; optional int32 toChatType = 14; optional bool boolSupportProgressive = 15; optional int32 fileWidth = 16; optional int32 fileHeight = 17; optional int32 subBusiType = 18; optional int32 videoAttr = 19; repeated bytes bytesThumbFileUrls = 20; repeated bytes bytesVideoFileUrls = 21; optional int32 thumbDownloadFlag = 22; optional int32 videoDownloadFlag = 23; optional bytes pbReserve = 24; } message SourceMsg { repeated int32 origSeqs = 1; optional int64 senderUin = 2; optional int32 time = 3; optional int32 flag = 4; repeated Elem elems = 5; optional int32 type = 6; optional bytes richMsg = 7; optional bytes pbReserve = 8; optional bytes srcMsg = 9; optional int64 toUin = 10; optional bytes troopName = 11; } message Face { optional int32 index = 1; optional bytes old = 2; optional bytes buf = 11; } message LightApp { optional bytes data = 1; optional bytes msgResid = 2; } message CustomFace { optional bytes guid = 1; optional string filePath = 2; optional string shortcut = 3; optional bytes buffer = 4; optional bytes flag = 5; optional bytes oldData = 6; optional int32 fileId = 7; optional uint32 serverIp = 8; optional uint32 serverPort = 9; optional int32 fileType = 10; optional bytes signature = 11; optional int32 useful = 12; optional bytes md5 = 13; optional string thumbUrl = 14; optional string bigUrl = 15; optional string origUrl = 16; optional int32 bizType = 17; optional int32 repeatIndex = 18; optional int32 repeatImage = 19; optional int32 imageType = 20; optional int32 index = 21; optional uint32 width = 22; optional uint32 height = 23; optional int32 source = 24; optional uint32 size = 25; optional int32 origin = 26; optional int32 thumbWidth = 27; optional int32 thumbHeight = 28; optional int32 showLen = 29; optional int32 downloadLen = 30; optional string x400Url = 31;//x optional int32 x400Width = 32;//x optional int32 x400Height = 33;//x optional bytes pbReserve = 34; } message ContentHead { optional int32 pkgNum = 1; optional int32 pkgIndex = 2; optional int32 divSeq = 3; optional int32 autoReply = 4; } message MessageHead { optional int64 fromUin = 1; optional int64 toUin = 2; optional int32 msgType = 3; optional int32 c2cCmd = 4; optional int32 msgSeq = 5; optional int32 msgTime = 6; optional int64 msgUid = 7; optional C2CTempMessageHead c2cTmpMsgHead = 8; optional GroupInfo groupInfo = 9; optional int32 fromAppid = 10; optional int32 fromInstid = 11; optional int32 userActive = 12; optional DiscussInfo discussInfo = 13; optional string fromNick = 14; optional int64 authUin = 15; optional string authNick = 16; optional int32 msgFlag = 17; optional string authRemark = 18; optional string groupName = 19; optional MutilTransHead mutiltransHead = 20; optional InstCtrl msgInstCtrl = 21; optional int32 publicAccountGroupSendFlag = 22; optional int32 wseqInC2cMsghead = 23; optional int64 cpid = 24; optional ExtGroupKeyInfo extGroupKeyInfo = 25; optional string multiCompatibleText = 26; optional int32 authSex = 27; optional bool isSrcMsg = 28; } message GroupInfo { optional int64 groupCode = 1; optional int32 groupType = 2; optional int64 groupInfoSeq = 3; optional bytes groupCard = 4; optional bytes groupRank = 5; optional int32 groupLevel = 6; optional int32 groupCardType = 7; optional bytes groupName = 8; } message DiscussInfo { optional int64 discussUin = 1; optional int32 discussType = 2; optional int64 discussInfoSeq = 3; optional bytes discussRemark = 4; optional bytes discussName = 5; } message MutilTransHead{ optional int32 status = 1; optional int32 msgId = 2; } message C2CTempMessageHead { optional int32 c2cType = 1; optional int32 serviceType = 2; optional int64 groupUin = 3; optional int64 groupCode = 4; optional bytes sig = 5; optional int32 sigType = 6; optional string fromPhone = 7; optional string toPhone = 8; optional int32 lockDisplay = 9; optional int32 directionFlag = 10; optional bytes reserved = 11; } message InstCtrl { repeated InstInfo msgSendToInst = 1; repeated InstInfo msgExcludeInst = 2; optional InstInfo msgFromInst = 3; } message InstInfo { optional int32 apppid = 1; optional int32 instid = 2; optional int32 platform = 3; optional int32 enumDeviceType = 10; } message ExtGroupKeyInfo { optional int32 curMaxSeq = 1; optional int64 curTime = 2; } message SyncCookie { optional int64 time1 = 1; optional int64 time = 2; optional int64 ran1 = 3; optional int64 ran2 = 4; optional int64 const1 = 5; optional int64 const2 = 11; optional int64 const3 = 12; optional int64 lastSyncTime = 13; optional int64 const4 = 14; } message TransMsgInfo { optional int64 fromUin = 1; optional int64 toUin = 2; optional int32 msgType = 3; optional int32 msgSubtype = 4; optional int32 msgSeq = 5; optional int64 msgUid = 6; optional int32 msgTime = 7; optional int32 realMsgTime = 8; optional string nickName = 9; optional bytes msgData = 10; optional int32 svrIp = 11; optional ExtGroupKeyInfo extGroupKeyInfo = 12; optional int32 generalFlag = 17; } message GeneralFlags { optional int32 bubbleDiyTextId = 1; optional int32 groupFlagNew = 2; optional int64 uin = 3; optional bytes rpId = 4; optional int32 prpFold = 5; optional int32 longTextFlag = 6; optional string longTextResid = 7; optional int32 groupType = 8; optional int32 toUinFlag = 9; optional int32 glamourLevel = 10; optional int32 memberLevel = 11; optional int64 groupRankSeq = 12; optional int32 olympicTorch = 13; optional bytes babyqGuideMsgCookie = 14; optional int32 uin32ExpertFlag = 15; optional int32 bubbleSubId = 16; optional int64 pendantId = 17; optional bytes rpIndex = 18; optional bytes pbReserve = 19; } message PbMultiMsgItem { optional string fileName = 1; optional PbMultiMsgNew buffer = 2; } message PbMultiMsgNew { repeated Message msg = 1; } message PbMultiMsgTransmit { repeated Message msg = 1; repeated PbMultiMsgItem pbItemList = 2; } message MsgElemInfo_servtype3 { optional CustomFace flash_troop_pic = 1; optional NotOnlineImage flash_c2c_pic = 2; } message MsgElemInfo_servtype33 { optional uint32 index = 1; optional bytes text = 2; optional bytes compat = 3; optional bytes buf = 4; } message SubMsgType0x4Body { optional NotOnlineFile notOnlineFile = 1; optional uint32 msgTime = 2; optional uint32 onlineFileForPolyToOffline = 3; // fileImageInfo } enum SyncFlag { START = 0; CONTINUME = 1; STOP = 2; } message ResvAttr { optional uint32 imageBizType = 1; optional AnimationImageShow image_show = 7; } message AnimationImageShow { optional int32 effect_id = 1; optional bytes animation_param = 2; } message UinTypeUserDef { optional int32 fromUinType = 1; optional int64 fromGroupCode = 2; optional string fileUuid = 3; } message GetGroupMsgReq { optional uint64 groupCode = 1; optional uint64 beginSeq = 2; optional uint64 endSeq = 3; optional uint32 filter = 4; optional uint64 memberSeq = 5; optional bool publicGroup = 6; optional uint32 shieldFlag = 7; optional uint32 saveTrafficFlag = 8; } message GetGroupMsgResp { optional uint32 result = 1; optional string errmsg = 2; optional uint64 groupCode = 3; optional uint64 returnBeginSeq = 4; optional uint64 returnEndSeq = 5; repeated Message msg = 6; } message PbGetOneDayRoamMsgReq { optional uint64 peerUin = 1; optional uint64 lastMsgTime = 2; optional uint64 random = 3; optional uint32 readCnt = 4; } message PbGetOneDayRoamMsgResp { optional uint32 result = 1; optional string errMsg = 2; optional uint64 peerUin = 3; optional uint64 lastMsgTime = 4; optional uint64 random = 5; repeated Message msg = 6; optional uint32 isComplete = 7; } message PbPushMsg { optional Message msg = 1; optional int32 svrip = 2; optional bytes pushToken = 3; optional uint32 pingFlag = 4; optional uint32 generalFlag = 9; optional uint64 bindUin = 10; } message MsgElemInfo_servtype37 { optional bytes packid = 1; optional bytes stickerid = 2; optional uint32 qsid = 3; optional uint32 sourcetype = 4; optional uint32 stickertype = 5; optional bytes resultid = 6; optional bytes text = 7; optional bytes surpriseid = 8; optional uint32 randomtype = 9; } ================================================ FILE: ricq-core/src/pb/msg/objmsg.proto ================================================ syntax = "proto3"; package msg; message MsgPic { bytes smallPicUrl = 1; bytes originalPicUrl = 2; int32 localPicId = 3; } message ObjMsg { int32 msgType = 1; bytes title = 2; bytes bytesAbstact = 3; bytes titleExt = 5; repeated MsgPic msgPic = 6; repeated MsgContentInfo msgContentInfo = 7; int32 reportIdShow = 8; } message MsgContentInfo { bytes contentInfoId = 1; MsgFile msgFile = 2; } message MsgFile { int32 busId = 1; bytes filePath = 2; int64 fileSize = 3; string fileName = 4; int64 int64DeadTime = 5; bytes fileSha1 = 6; bytes ext = 7; } ================================================ FILE: ricq-core/src/pb/msg/report.proto ================================================ syntax = "proto2"; package msg; message PbMsgReadedReportReq { repeated PbGroupReadedReportReq grpReadReport = 1; repeated PbDiscussReadedReportReq disReadReport = 2; optional PbC2CReadedReportReq c2CReadReport = 3; //optional PbBindUinMsgReadedConfirmReq bindUinReadReport = 4; } message PbMsgReadedReportResp { repeated PbGroupReadedReportResp grpReadReport = 1; repeated PbDiscussReadedReportResp disReadReport = 2; optional PbC2CReadedReportResp c2CReadReport = 3; //optional PbBindUinMsgReadedConfirmResp bindUinReadReport = 4; } message PbGroupReadedReportReq { optional uint64 groupCode = 1; optional uint64 lastReadSeq = 2; } message PbDiscussReadedReportReq { optional uint64 confUin = 1; optional uint64 lastReadSeq = 2; } message PbC2CReadedReportReq { optional bytes syncCookie = 1; repeated UinPairReadInfo pairInfo = 2; } message UinPairReadInfo { optional uint64 peerUin = 1; optional uint32 lastReadTime = 2; optional bytes crmSig = 3; optional uint32 peerType = 4; optional uint32 chatType = 5; optional uint64 cpid = 6; optional uint32 aioType = 7; optional uint64 toTinyId = 9; } message PbGroupReadedReportResp { optional uint32 result = 1; optional string errmsg = 2; optional uint64 groupCode = 3; optional uint64 memberSeq = 4; optional uint64 groupMsgSeq = 5; } message PbDiscussReadedReportResp { optional uint32 result = 1; optional string errmsg = 2; optional uint64 confUin = 3; optional uint64 memberSeq = 4; optional uint64 confSeq = 5; } message PbC2CReadedReportResp { optional uint32 result = 1; optional string errmsg = 2; optional bytes syncCookie = 3; } ================================================ FILE: ricq-core/src/pb/msgtype0x210/subMsgType0x27.proto ================================================ syntax = "proto2"; package msgtype0x210; message AddGroup { optional uint32 groupid = 1; optional uint32 sortid = 2; optional bytes groupname = 3; } message AppointmentNotify { optional uint64 fromUin = 1; optional string appointId = 2; optional uint32 notifytype = 3; optional string tipsContent = 4; optional uint32 unreadCount = 5; optional string joinWording = 6; optional string viewWording = 7; optional bytes sig = 8; optional bytes eventInfo = 9; optional bytes nearbyEventInfo = 10; optional bytes feedEventInfo = 11; } message BinaryMsg { optional uint32 opType = 1; optional bytes opValue = 2; } message ChatMatchInfo { optional bytes sig = 1; optional uint64 uin = 2; optional uint64 matchUin = 3; optional bytes tipsWording = 4; optional uint32 leftChatTime = 5; optional uint64 timeStamp = 6; optional uint32 matchExpiredTime = 7; optional uint32 c2CExpiredTime = 8; optional uint32 matchCount = 9; optional bytes nick = 10; } message ConfMsgRoamFlag { optional uint64 confid = 1; optional uint32 flag = 2; optional uint64 timestamp = 3; } message DaRenNotify { optional uint64 uin = 1; optional uint32 loginDays = 2; optional uint32 days = 3; optional uint32 isYestodayLogin = 4; optional uint32 isTodayLogin = 5; } message DelFriend { repeated uint64 uins = 1; } message DelGroup { optional uint32 groupid = 1; } message FanpaiziNotify { optional uint64 fromUin = 1; optional string fromNick = 2; optional bytes tipsContent = 3; optional bytes sig = 4; } message ForwardBody { optional uint32 notifyType = 1; optional uint32 opType = 2; optional AddGroup addGroup = 3; optional DelGroup delGroup = 4; optional ModGroupName modGroupName = 5; optional ModGroupSort modGroupSort = 6; optional ModFriendGroup modFriendGroup = 7; optional ModProfile modProfile = 8; optional ModFriendRemark modFriendRemark = 9; optional ModLongNick modLongNick = 10; optional ModCustomFace modCustomFace = 11; optional ModGroupProfile modGroupProfile = 12; optional ModGroupMemberProfile modGroupMemberProfile = 13; optional DelFriend delFriend = 14; optional ModFrdRoamPriv roamPriv = 15; optional GrpMsgRoamFlag grpMsgRoamFlag = 16; optional ConfMsgRoamFlag confMsgRoamFlag = 17; optional ModLongNick modRichLongNick = 18; optional BinaryMsg binPkg = 19; optional ModSnsGeneralInfo modFriendRings = 20; optional ModConfProfile modConfProfile = 21; optional SnsUpdateFlag modFriendFlag = 22; optional AppointmentNotify appointmentNotify = 23; optional DaRenNotify darenNotify = 25; optional NewComeinUserNotify newComeinUserNotify = 26; optional PushSearchDev pushSearchDev = 200; optional PushReportDev pushReportDev = 201; optional QQPayPush qqPayPush = 202; optional bytes redpointInfo = 203; optional HotFriendNotify hotFriendNotify = 204; optional PraiseRankNotify praiseRankNotify = 205; optional MQQCampusNotify campusNotify = 210; optional ModLongNick modRichLongNickEx = 211; optional ChatMatchInfo chatMatchInfo = 212; optional FrdCustomOnlineStatusChange frdCustomOnlineStatusChange = 214; optional FanpaiziNotify fanpanziNotify = 2000; } message FrdCustomOnlineStatusChange { optional uint64 uin = 1; } message FriendGroup { optional uint64 fuin = 1; repeated uint32 oldGroupId = 2; repeated uint32 newGroupId = 3; } message FriendRemark { optional uint32 type = 1; optional uint64 fuin = 2; optional bytes rmkName = 3; optional uint64 groupCode = 4; } message GPS { optional int32 lat = 1; optional int32 lon = 2; optional int32 alt = 3; optional int32 type = 4; } message GroupMemberProfileInfo { optional uint32 field = 1; optional bytes value = 2; } message GroupProfileInfo { optional uint32 field = 1; optional bytes value = 2; } message GroupSort { optional uint32 groupid = 1; optional uint32 sortid = 2; } message GrpMsgRoamFlag { optional uint64 groupcode = 1; optional uint32 flag = 2; optional uint64 timestamp = 3; } message HotFriendNotify { optional uint64 dstUin = 1; optional uint32 praiseHotLevel = 2; optional uint32 chatHotLevel = 3; optional uint32 praiseHotDays = 4; optional uint32 chatHotDays = 5; optional uint32 closeLevel = 6; optional uint32 closeDays = 7; optional uint32 praiseFlag = 8; optional uint32 chatFlag = 9; optional uint32 closeFlag = 10; optional uint64 notifyTime = 11; optional uint64 lastPraiseTime = 12; optional uint64 lastChatTime = 13; optional uint32 qzoneHotLevel = 14; optional uint32 qzoneHotDays = 15; optional uint32 qzoneFlag = 16; optional uint64 lastQzoneTime = 17; } message MQQCampusNotify { optional uint64 fromUin = 1; optional string wording = 2; optional string target = 3; optional uint32 type = 4; optional string source = 5; } message ModConfProfile { optional uint64 uin = 1; optional uint32 confUin = 2; repeated ProfileInfo profileInfos = 3; } message ModCustomFace { optional uint32 type = 1; optional uint64 uin = 2; optional uint64 groupCode = 3; optional uint64 cmdUin = 4; } message ModFrdRoamPriv { repeated OneRoamPriv roamPriv = 1; } message ModFriendGroup { repeated FriendGroup frdGroup = 1; } message ModFriendRemark { repeated FriendRemark frdRmk = 1; } message ModGroupMemberProfile { optional uint64 groupUin = 1; optional uint64 uin = 2; repeated GroupMemberProfileInfo groupMemberProfileInfos = 3; optional uint64 groupCode = 4; } message ModGroupName { optional uint32 groupid = 1; optional bytes groupname = 2; } message ModGroupProfile { optional uint64 groupUin = 1; repeated GroupProfileInfo groupProfileInfos = 2; optional uint64 groupCode = 3; optional uint64 cmdUin = 4; } message ModGroupSort { repeated GroupSort groupsort = 1; } message ModLongNick { optional uint64 uin = 1; optional bytes value = 2; } message ModProfile { optional uint64 uin = 1; repeated ProfileInfo profileInfos = 2; } message ModSnsGeneralInfo { repeated SnsUpateBuffer snsGeneralInfos = 1; } message SubMsg0x27Body { repeated ForwardBody modInfos = 1; } message NewComeinUser { optional uint64 uin = 1; optional uint32 isFrd = 2; optional bytes remark = 3; optional bytes nick = 4; } message NewComeinUserNotify { optional uint32 msgType = 1; optional bool ongNotify = 2; optional uint32 pushTime = 3; optional NewComeinUser newComeinUser = 4; optional NewGroup newGroup = 5; optional NewGroupUser newGroupUser = 6; } message NewGroup { optional uint64 groupCode = 1; optional bytes groupName = 2; optional uint64 ownerUin = 3; optional bytes ownerNick = 4; optional bytes distance = 5; } message NewGroupUser { optional uint64 uin = 1; optional int32 sex = 2; optional int32 age = 3; optional string nick = 4; optional bytes distance = 5; } message OneRoamPriv { optional uint64 fuin = 1; optional uint32 privTag = 2; optional uint32 privValue = 3; } message PraiseRankNotify { optional uint32 isChampion = 11; optional uint32 rankNum = 12; optional string msg = 13; } message ProfileInfo { optional uint32 field = 1; optional bytes value = 2; } message PushReportDev { optional uint32 msgType = 1; optional bytes cookie = 4; optional uint32 reportMaxNum = 5; optional bytes sn = 6; } message PushSearchDev { optional uint32 msgType = 1; optional GPS gpsInfo = 2; optional uint32 devTime = 3; optional uint32 pushTime = 4; optional uint64 din = 5; optional string data = 6; } message QQPayPush { optional uint64 uin = 1; optional bool payOk = 2; } message SnsUpateBuffer { optional uint64 uin = 1; optional uint64 code = 2; optional uint32 result = 3; repeated SnsUpdateItem snsUpdateItem = 400; repeated uint32 idlist = 401; } message SnsUpdateFlag { repeated SnsUpdateOneFlag updateSnsFlag = 1; } message SnsUpdateItem { optional uint32 updateSnsType = 1; optional bytes value = 2; } message SnsUpdateOneFlag { optional uint64 XUin = 1; optional uint64 id = 2; optional uint32 flag = 3; } ================================================ FILE: ricq-core/src/pb/multimsg/multimsg.proto ================================================ syntax = "proto3"; package multimsg; message ExternMsg { int32 channelType = 1; } message MultiMsgApplyDownReq { bytes msgResid = 1; int32 msgType = 2; int64 srcUin = 3; } message MultiMsgApplyDownRsp { int32 result = 1; bytes thumbDownPara = 2; bytes msgKey = 3; repeated uint32 downIp = 4; repeated uint32 downPort = 5; bytes msgResid = 6; ExternMsg msgExternInfo = 7; repeated bytes bytesDownIpV6 = 8; repeated int32 uint32DownV6Port = 9; } message MultiMsgApplyUpReq { int64 dstUin = 1; int64 msgSize = 2; bytes msgMd5 = 3; int32 msgType = 4; int32 applyId = 5; } message MultiMsgApplyUpRsp { int32 result = 1; string msgResid = 2; bytes msgUkey = 3; repeated int32 uint32UpIp = 4; repeated int32 uint32UpPort = 5; int64 blockSize = 6; int64 upOffset = 7; int32 applyId = 8; bytes msgKey = 9; bytes msgSig = 10; ExternMsg msgExternInfo = 11; repeated bytes bytesUpIpV6 = 12; repeated int32 uint32UpV6Port = 13; } message MultiReqBody { int32 subcmd = 1; int32 termType = 2; int32 platformType = 3; int32 netType = 4; string buildVer = 5; repeated MultiMsgApplyUpReq multimsgApplyupReq = 6; repeated MultiMsgApplyDownReq multimsgApplydownReq = 7; int32 buType = 8; int32 reqChannelType = 9; } message MultiRspBody { int32 subcmd = 1; repeated MultiMsgApplyUpRsp multimsgApplyupRsp = 2; repeated MultiMsgApplyDownRsp multimsgApplydownRsp = 3; } ================================================ FILE: ricq-core/src/pb/notify/group0x857.proto ================================================ syntax = "proto3"; package notify; message NotifyMsgBody { AIOGrayTipsInfo optMsgGrayTips = 5; RedGrayTipsInfo optMsgRedTips = 9; MessageRecallReminder optMsgRecall = 11; GeneralGrayTipInfo optGeneralGrayTip = 26; QQGroupDigestMsg qqGroupDigestMsg = 33; int32 serviceType = 13; } message AIOGrayTipsInfo{ uint32 showLatest = 1; bytes content = 2; uint32 remind = 3; bytes brief = 4; uint64 receiverUin = 5; uint32 reliaoAdminOpt = 6; } message GeneralGrayTipInfo { uint64 busiType = 1; uint64 busiId = 2; uint32 ctrlFlag = 3; uint32 c2cType = 4; uint32 serviceType = 5; uint64 templId = 6; repeated TemplParam msgTemplParam = 7; string content = 8; } message TemplParam { string name = 1; string value = 2; } message MessageRecallReminder { int64 uin = 1; bytes nickname = 2; repeated RecalledMessageMeta recalledMsgList = 3; bytes reminderContent = 4; bytes userdef = 5; int32 groupType = 6; int32 opType = 7; } message RecalledMessageMeta { int32 seq = 1; int32 time = 2; int32 msgRandom = 3; int32 msgType = 4; int32 msgFlag = 5; int64 authorUin = 6; } message RedGrayTipsInfo { uint32 showLatest = 1; uint64 senderUin = 2; uint64 receiverUin = 3; string senderRichContent = 4; string receiverRichContent = 5; bytes authKey = 6; sint32 msgType = 7; uint32 luckyFlag = 8; uint32 hideFlag = 9; uint64 luckyUin = 12; } message QQGroupDigestMsg { uint64 groupCode = 1; uint32 seq = 2; uint32 random = 3; int32 opType = 4; uint64 sender = 5; uint64 digestOper = 6; uint32 opTime = 7; uint32 lastestMsgSeq = 8; bytes operNick = 9; bytes senderNick = 10; int32 extInfo = 11; } ================================================ FILE: ricq-core/src/pb/oidb/oidb.proto ================================================ syntax = "proto3"; package oidb; message OIDBSSOPkg { int32 command = 1; int32 serviceType = 2; int32 result = 3; bytes bodybuffer = 4; string errorMsg = 5; string clientVersion = 6; } message D8A0RspBody { int64 optUint64GroupCode = 1; repeated D8A0KickResult msgKickResult = 2; } message D8A0KickResult { int32 optUint32Result = 1; int64 optUint64MemberUin = 2; } message D8A0KickMemberInfo { int32 optUint32Operate = 1; int64 optUint64MemberUin = 2; int32 optUint32Flag = 3; bytes optBytesMsg = 4; } message D8A0ReqBody { int64 optUint64GroupCode = 1; repeated D8A0KickMemberInfo msgKickList = 2; repeated int64 kickList = 3; int32 kickFlag = 4; bytes kickMsg = 5; } message D89AReqBody { int64 groupCode = 1; D89AGroupinfo stGroupInfo = 2; int64 originalOperatorUin = 3; int32 reqGroupOpenAppid = 4; } message D89AGroupinfo { int32 groupExtAdmNum = 1; int32 flag = 2; bytes ingGroupName = 3; bytes ingGroupMemo = 4; bytes ingGroupFingerMemo = 5; bytes ingGroupAioSkinUrl = 6; bytes ingGroupBoardSkinUrl = 7; bytes ingGroupCoverSkinUrl = 8; int32 groupGrade = 9; int32 activeMemberNum = 10; int32 certificationType = 11; bytes ingCertificationText = 12; bytes ingGroupRichFingerMemo = 13; D89AGroupNewGuidelinesInfo stGroupNewguidelines = 14; int32 groupFace = 15; int32 addOption = 16; oneof shutupTime { int32 val = 17; } int32 groupTypeFlag = 18; bytes stringGroupTag = 19; D89AGroupGeoInfo msgGroupGeoInfo = 20; int32 groupClassExt = 21; bytes ingGroupClassText = 22; int32 appPrivilegeFlag = 23; int32 appPrivilegeMask = 24; D89AGroupExInfoOnly stGroupExInfo = 25; int32 groupSecLevel = 26; int32 groupSecLevelInfo = 27; int64 subscriptionUin = 28; int32 allowMemberInvite = 29; bytes ingGroupQuestion = 30; bytes ingGroupAnswer = 31; int32 groupFlagext3 = 32; int32 groupFlagext3Mask = 33; int32 groupOpenAppid = 34; int32 noFingerOpenFlag = 35; int32 noCodeFingerOpenFlag = 36; int64 rootId = 37; int32 msgLimitFrequency = 38; } message D89AGroupNewGuidelinesInfo { bool boolEnabled = 1; bytes ingContent = 2; } message D89AGroupExInfoOnly { int32 tribeId = 1; int32 moneyForAddGroup = 2; } message D89AGroupGeoInfo { int32 cityId = 1; int64 longtitude = 2; int64 latitude = 3; bytes ingGeoContent = 4; int64 poiId = 5; } message DED3ReqBody { int64 toUin = 1; int64 groupCode = 2; int32 msgSeq = 3; int32 msgRand = 4; int64 aioUin = 5; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0x6d6.proto ================================================ syntax = "proto2"; package oidb; message DeleteFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string parentFolderId = 4; optional string fileId = 5; } message DeleteFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; } message DownloadFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string fileId = 4; optional bool boolThumbnailReq = 5; optional int32 urlType = 6; optional bool boolPreviewReq = 7; } message DownloadFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional string downloadIp = 4; optional bytes downloadDns = 5; optional bytes downloadUrl = 6; optional bytes sha = 7; optional bytes sha3 = 8; optional bytes md5 = 9; optional bytes cookieVal = 10; optional string saveFileName = 11; optional int32 previewPort = 12; } message MoveFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string fileId = 4; optional string parentFolderId = 5; optional string destFolderId = 6; } message MoveFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional string parentFolderId = 4; } message RenameFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string fileId = 4; optional string parentFolderId = 5; optional string newFileName = 6; } message RenameFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; } message D6D6ReqBody { optional UploadFileReqBody uploadFileReq = 1; optional ResendReqBody resendFileReq = 2; optional DownloadFileReqBody downloadFileReq = 3; optional DeleteFileReqBody deleteFileReq = 4; optional RenameFileReqBody renameFileReq = 5; optional MoveFileReqBody moveFileReq = 6; } message ResendReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string fileId = 4; optional bytes sha = 5; } message ResendRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional string uploadIp = 4; optional bytes fileKey = 5; optional bytes checkKey = 6; } message D6D6RspBody { optional UploadFileRspBody uploadFileRsp = 1; optional ResendRspBody resendFileRsp = 2; optional DownloadFileRspBody downloadFileRsp = 3; optional DeleteFileRspBody deleteFileRsp = 4; optional RenameFileRspBody renameFileRsp = 5; optional MoveFileRspBody moveFileRsp = 6; } message UploadFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional int32 entrance = 4; optional string parentFolderId = 5; optional string fileName = 6; optional string localPath = 7; optional int64 int64FileSize = 8; optional bytes sha = 9; optional bytes sha3 = 10; optional bytes md5 = 11; optional bool supportMultiUpload = 15; } message UploadFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional string uploadIp = 4; optional string serverDns = 5; optional int32 busId = 6; optional string fileId = 7; optional bytes fileKey = 8; optional bytes checkKey = 9; optional bool boolFileExist = 10; repeated string uploadIpLanV4 = 12; repeated string uploadIpLanV6 = 13; optional int32 uploadPort = 14; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0x6d8.proto ================================================ syntax = "proto2"; package oidb; message D6D8ReqBody { optional GetFileInfoReqBody fileInfoReq = 1; optional GetFileListReqBody fileListInfoReq = 2; optional GetFileCountReqBody groupFileCountReq = 3; optional GetSpaceReqBody groupSpaceReq = 4; } message D6D8RspBody { optional GetFileInfoRspBody fileInfoRsp = 1; optional GetFileListRspBody fileListInfoRsp = 2; optional GetFileCountRspBody fileCountRsp = 3; optional GetSpaceRspBody groupSpaceRsp = 4; } message GetFileInfoReqBody { optional uint64 groupCode = 1; optional uint32 appId = 2; optional uint32 busId = 3; optional string fileId = 4; optional uint32 fieldFlag = 5; } message GetFileInfoRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional GroupFileInfo fileInfo = 4; } message GetFileListRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional bool isEnd = 4; message Item { optional uint32 type = 1; optional GroupFolderInfo folderInfo = 2; optional GroupFileInfo fileInfo = 3; } repeated Item itemList = 5; optional FileTimeStamp maxTimestamp = 6; optional uint32 allFileCount = 7; optional uint32 filterCode = 8; optional bool safeCheckFlag = 11; optional uint32 safeCheckRes = 12; optional uint32 nextIndex = 13; optional bytes context = 14; optional uint32 role = 15; optional uint32 openFlag = 16; } message GroupFileInfo {/* renamed from FileInfo */ optional string fileId = 1; optional string fileName = 2; optional uint64 fileSize = 3; optional uint32 busId = 4; optional uint64 uploadedSize = 5; optional uint32 uploadTime = 6; optional uint32 deadTime = 7; optional uint32 modifyTime = 8; optional uint32 downloadTimes = 9; optional bytes sha = 10; optional bytes sha3 = 11; optional bytes md5 = 12; optional string localPath = 13; optional string uploaderName = 14; optional uint64 uploaderUin = 15; optional string parentFolderId = 16; } message GroupFolderInfo {/* renamed from FolderInfo */ optional string folderId = 1; optional string parentFolderId = 2; optional string folderName = 3; optional uint32 createTime = 4; optional uint32 modifyTime = 5; optional uint64 createUin = 6; optional string creatorName = 7; optional uint32 totalFileCount = 8; } message GetFileListReqBody { optional uint64 groupCode = 1; optional uint32 appId = 2; optional string folderId = 3; optional FileTimeStamp startTimestamp = 4; optional uint32 fileCount = 5; optional FileTimeStamp maxTimestamp = 6; optional uint32 allFileCount = 7; optional uint32 reqFrom = 8; optional uint32 sortBy = 9; optional uint32 filterCode = 10; optional uint64 uin = 11; optional uint32 fieldFlag = 12; optional uint32 startIndex = 13; optional bytes context = 14; optional uint32 clientVersion = 15; } message GetFileCountReqBody { optional uint64 groupCode = 1; optional uint32 appId = 2; optional uint32 busId = 3; } message GetSpaceReqBody { optional uint64 groupCode = 1; optional uint32 appId = 2; } message GetFileCountRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional uint32 allFileCount = 4; optional bool fileTooMany = 5; optional uint32 limitCount = 6; optional bool isFull = 7; } message GetSpaceRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional uint64 totalSpace = 4; optional uint64 usedSpace = 5; } message FileTimeStamp { optional uint32 uploadTime = 1; optional string fileId = 2; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0x758.proto ================================================ syntax = "proto2"; package oidb; message InviteUinInfo { optional uint64 uin = 1; optional uint64 judgeGroupCode = 2; optional uint64 judgeConfCode = 3; } message D758ReqBody { optional uint64 joinGroupCode = 1; repeated InviteUinInfo beInvitedUinInfo = 2; optional string msg = 3; optional uint32 mainSourceId = 4; optional uint32 subSourceId = 5; optional string verifyToken = 6; optional uint32 verifyType = 7; } message D758RspBody { optional uint64 groupCode = 1; optional uint64 currentMaxMsgseq = 2; optional string verifyUrl = 3; optional uint32 verifyType = 4; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0x769.proto ================================================ syntax = "proto2"; package oidb; message CPU { optional string model = 1; optional uint32 cores = 2; optional uint32 frequency = 3; } message Camera { optional uint64 primary = 1; optional uint64 secondary = 2; optional bool flash = 3; } message D769ConfigSeq { optional uint32 type = 1; optional uint32 version = 2; } message Content { optional uint32 taskId = 1; optional uint32 compress = 2; optional bytes content = 10; } message D769DeviceInfo { optional string brand = 1; optional string model = 2; optional C41219OS os = 3; optional CPU cpu = 4; optional Memory memory = 5; optional Storage storage = 6; optional Screen screen = 7; optional Camera camera = 8; } message Memory { optional uint64 total = 1; optional uint64 process = 2; } message C41219OS { optional uint32 type = 1; optional string version = 2; optional string sdk = 3; optional string kernel = 4; optional string rom = 5; } message QueryUinPackageUsageReq { optional uint32 type = 1; optional uint64 uinFileSize = 2; } message QueryUinPackageUsageRsp { optional uint32 status = 1; optional uint64 leftUinNum = 2; optional uint64 maxUinNum = 3; optional uint32 proportion = 4; repeated UinPackageUsedInfo uinPackageUsedList = 10; } message D769ReqBody { repeated D769ConfigSeq configList = 1; optional D769DeviceInfo deviceInfo = 2; optional string info = 3; optional string province = 4; optional string city = 5; optional int32 reqDebugMsg = 6; optional QueryUinPackageUsageReq queryUinPackageUsageReq = 101; } message D769RspBody { optional uint32 result = 1; repeated D769ConfigSeq configList = 2; optional QueryUinPackageUsageRsp queryUinPackageUsageRsp = 101; } message Screen { optional string model = 1; optional uint32 width = 2; optional uint32 height = 3; optional uint32 dpi = 4; optional bool multiTouch = 5; } message Storage { optional uint64 builtin = 1; optional uint64 external = 2; } message UinPackageUsedInfo { optional uint32 ruleId = 1; optional string author = 2; optional string url = 3; optional uint64 uinNum = 4; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0x88d.proto ================================================ syntax = "proto2"; // 似乎查询服务端是通过 exists flag 来返回 group info 的 这地方只能用 proto2 package oidb; message D88DGroupHeadPortraitInfo { optional uint32 picId = 1; } message D88DGroupHeadPortrait { optional uint32 picCount = 1; repeated D88DGroupHeadPortraitInfo msgInfo = 2; optional uint32 defaultId = 3; optional uint32 verifyingPicCnt = 4; repeated D88DGroupHeadPortraitInfo msgVerifyingPicInfo = 5; } message D88DGroupExInfoOnly { optional uint32 tribeId = 1; optional uint32 moneyForAddGroup = 2; }; message D88DGroupInfo { optional uint64 groupOwner = 1; optional uint32 groupCreateTime = 2; optional uint32 groupFlag = 3; optional uint32 groupFlagExt = 4; optional uint32 groupMemberMaxNum = 5; optional uint32 groupMemberNum = 6; optional uint32 groupOption = 7; optional uint32 groupClassExt = 8; optional uint32 groupSpecialClass = 9; optional uint32 groupLevel = 10; optional uint32 groupFace = 11; optional uint32 groupDefaultPage = 12; optional uint32 groupInfoSeq = 13; optional uint32 groupRoamingTime = 14; optional bytes groupName = 15; optional bytes groupMemo = 16; optional bytes groupFingerMemo = 17; optional bytes groupClassText = 18; repeated uint32 groupAllianceCode = 19; optional uint32 groupExtraAadmNum = 20; optional uint64 groupUin = 21; optional uint32 groupCurMsgSeq = 22; optional uint32 groupLastMsgTime = 23; optional bytes groupQuestion = 24; optional bytes groupAnswer = 25; optional uint32 groupVisitorMaxNum = 26; optional uint32 groupVisitorCurNum = 27; optional uint32 levelNameSeq = 28; optional uint32 groupAdminMaxNum = 29; optional uint32 groupAioSkinTimestamp = 30; optional uint32 groupBoardSkinTimestamp = 31; optional bytes groupAioSkinUrl = 32; optional bytes groupBoardSkinUrl = 33; optional uint32 groupCoverSkinTimestamp = 34; optional bytes groupCoverSkinUrl = 35; optional uint32 groupGrade = 36; optional uint32 activeMemberNum = 37; optional uint32 certificationType = 38; optional bytes certificationText = 39; optional bytes groupRichFingerMemo = 40; repeated D88DTagRecord tagRecord = 41; optional D88DGroupGeoInfo groupGeoInfo = 42; optional uint32 headPortraitSeq = 43; optional D88DGroupHeadPortrait msgHeadPortrait = 44; optional uint32 shutupTimestamp = 45 ; optional uint32 shutupTimestampMe = 46 ; optional uint32 createSourceFlag = 47 ; optional uint32 cmduinMsgSeq = 48; optional uint32 cmduinJoinTime = 49; optional uint32 cmduinUinFlag = 50; optional uint32 cmduinFlagEx = 51; optional uint32 cmduinNewMobileFlag = 52; optional uint32 cmduinReadMsgSeq = 53; optional uint32 cmduinLastMsgTime = 54; optional uint32 groupTypeFlag = 55; optional uint32 appPrivilegeFlag = 56; optional D88DGroupExInfoOnly stGroupExInfo = 57; optional uint32 groupSecLevel = 58; optional uint32 groupSecLevelInfo = 59; optional uint32 cmduinPrivilege = 60; optional bytes poidInfo = 61; optional uint32 cmduinFlagEx2 = 62; optional uint64 confUin = 63; optional uint32 confMaxMsgSeq = 64; optional uint32 confToGroupTime = 65; optional uint32 passwordRedbagTime = 66; optional uint64 subscriptionUin = 67; optional uint32 memberListChangeSeq = 68; optional uint32 membercardSeq = 69; optional uint64 rootId = 70; optional uint64 parentId = 71; optional uint32 teamSeq = 72; optional uint64 historyMsgBeginTime = 73; optional uint64 inviteNoAuthNumLimit = 74; optional uint32 cmduinHistoryMsgSeq = 75; optional uint32 cmduinJoinMsgSeq = 76; optional uint32 groupFlagext3 = 77; optional uint32 groupOpenAppid = 78; optional uint32 isConfGroup = 79; optional uint32 isModifyConfGroupFace = 80; optional uint32 isModifyConfGroupName = 81; optional uint32 noFingerOpenFlag = 82; optional uint32 noCodeFingerOpenFlag = 83; }; message ReqGroupInfo { optional uint64 groupCode = 1; optional D88DGroupInfo stgroupinfo = 2; optional uint32 lastGetGroupNameTime = 3; }; message D88DReqBody { optional uint32 appId = 1; repeated ReqGroupInfo reqGroupInfo = 2; optional uint32 pcClientVersion = 3; }; message RspGroupInfo { optional uint64 groupCode = 1; optional uint32 result = 2; optional D88DGroupInfo groupInfo = 3; }; message D88DRspBody { repeated RspGroupInfo rspGroupInfo = 1; optional bytes strErrorInfo = 2; }; message D88DTagRecord { optional uint64 fromUin = 1; optional uint64 groupCode = 2; optional bytes tagId = 3; optional uint64 setTime = 4; optional uint32 goodNum = 5; optional uint32 badNum = 6; optional uint32 tagLen = 7; optional bytes tagValue = 8; }; message D88DGroupGeoInfo { optional uint64 owneruin = 1; optional uint32 settime = 2; optional uint32 cityid = 3; optional int64 longitude = 4; optional int64 latitude = 5; optional bytes geocontent = 6; optional uint64 poiId = 7; }; ================================================ FILE: ricq-core/src/pb/oidb/oidb0x8a7.proto ================================================ syntax = "proto2"; package oidb; message D8A7ReqBody { optional uint32 subCmd = 1; optional uint32 limitIntervalTypeForUin = 2; optional uint32 limitIntervalTypeForGroup = 3; optional uint64 uin = 4; optional uint64 groupCode = 5; } message D8A7RspBody { optional bool canAtAll = 1; optional uint32 remainAtAllCountForUin = 2; optional uint32 remainAtAllCountForGroup = 3; optional bytes promptMsg1 = 4; optional bytes promptMsg2 = 5; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0x8fc.proto ================================================ syntax = "proto2"; package oidb; message D8FCReqBody { optional int64 groupCode = 1; optional int32 showFlag = 2; repeated D8FCMemberInfo memLevelInfo = 3; repeated D8FCLevelName levelName = 4; optional int32 updateTime = 5; optional int32 officeMode = 6; optional int32 groupOpenAppid = 7; optional D8FCClientInfo msgClientInfo = 8; optional bytes authKey = 9; } message D8FCMemberInfo { optional int64 uin = 1; optional int32 point = 2; optional int32 activeDay = 3; optional int32 level = 4; optional bytes specialTitle = 5; optional int32 specialTitleExpireTime = 6; optional bytes uinName = 7; optional bytes memberCardName = 8; optional bytes phone = 9; optional bytes email = 10; optional bytes remark = 11; optional int32 gender = 12; optional bytes job = 13; optional int32 tribeLevel = 14; optional int32 tribePoint = 15; repeated D8FCCardNameElem richCardName = 16; optional bytes commRichCardName = 17; } message D8FCCardNameElem { optional int32 enumCardType = 1; optional bytes value = 2; } message D8FCLevelName { optional int32 level = 1; optional string name = 2; } message D8FCClientInfo { optional int32 implat = 1; optional string ingClientver = 2; } message D8FCCommCardNameBuf { repeated D8FCRichCardNameElem richCardName = 1; } message D8FCRichCardNameElem { optional bytes ctrl = 1; optional bytes text = 2; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0x990.proto ================================================ syntax = "proto3"; package oidb; message TranslateReqBody { // TranslateReq translate_req = 1; BatchTranslateReq batch_translate_req = 2; } message TranslateRspBody { // TranslateRsp translate_rsp = 1; BatchTranslateRsp batch_translate_rsp = 2; } message BatchTranslateReq { string src_language = 1; string dst_language = 2; repeated string src_text_list = 3; } message BatchTranslateRsp { int32 error_code = 1; bytes error_msg = 2; string src_language = 3; string dst_language = 4; repeated string src_text_list = 5; repeated string dst_text_list = 6; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0xb77.proto ================================================ syntax = "proto3"; package oidb; message DB77ReqBody { uint64 appId = 1; uint32 appType = 2; uint32 msgStyle = 3; uint64 senderUin = 4; DB77ClientInfo clientInfo = 5; string textMsg = 6; DB77ExtInfo extInfo = 7; uint32 sendType = 10; uint64 recvUin = 11; DB77RichMsgBody richMsgBody = 12; uint64 recvGuildId = 19; } message DB77ClientInfo { uint32 platform = 1; string sdkVersion = 2; string androidPackageName = 3; string androidSignature = 4; string iosBundleId = 5; string pcSign = 6; } message DB77ExtInfo { repeated uint32 customFeatureId = 11; string apnsWording = 12; uint32 groupSaveDbFlag = 13; uint32 receiverAppId = 14; uint64 msgSeq = 15; } message DB77RichMsgBody { string title = 10; string summary = 11; string brief = 12; string url = 13; string pictureUrl = 14; string action = 15; string musicUrl = 16; //ImageInfo imageInfo = 17; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0xe07.proto ================================================ syntax = "proto3"; package oidb; message DE07ReqBody { int32 version = 1; int32 client = 2; int32 entrance = 3; OCRReqBody ocrReqBody = 10; } message OCRReqBody { string imageUrl = 1; string languageType = 2; string scene = 3; string originMd5 = 10; string afterCompressMd5 = 11; int32 afterCompressFileSize = 12; int32 afterCompressWeight = 13; int32 afterCompressHeight = 14; bool isCut = 15; } message DE07RspBody { int32 retCode = 1; string errMsg = 2; string wording = 3; OCRRspBody ocrRspBody = 10; } message TextDetection { string detectedText = 1; int32 confidence = 2; Polygon polygon = 3; string advancedInfo = 4; } message Polygon { repeated Coordinate coordinates = 1; } message Coordinate { int32 X = 1; int32 Y = 2; } message Language { string language = 1; string languageDesc = 2; } message OCRRspBody { repeated TextDetection textDetections = 1; string language = 2; string requestId = 3; repeated string ocrLanguageList = 101; repeated string dstTranslateLanguageList = 102; repeated Language languageList = 103; int32 afterCompressWeight = 111; int32 afterCompressHeight = 112; } ================================================ FILE: ricq-core/src/pb/oidb/oidb0xeac.proto ================================================ syntax = "proto3"; package oidb; /* message ArkMsg { optional string appName = 1; optional string json = 2; } message BatchReqBody { optional uint64 groupCode = 1; repeated MsgInfo msgs = 2; } message BatchRspBody { optional string wording = 1; optional uint32 errorCode = 2; optional int32 succCnt = 3; repeated MsgProcessInfo procInfos = 4; optional uint32 digestTime = 5; } message DigestMsg { optional uint64 groupCode = 1; optional uint32 seq = 2; optional uint32 random = 3; repeated MsgElem content = 4; optional uint64 textSize = 5; optional uint64 picSize = 6; optional uint64 videoSize = 7; optional uint64 senderUin = 8; optional uint32 senderTime = 9; optional uint64 addDigestUin = 10; optional uint32 addDigestTime = 11; optional uint32 startTime = 12; optional uint32 latestMsgSeq = 13; optional uint32 opType = 14; } message FaceMsg { optional uint32 index = 1; optional string text = 2; } message GroupFileMsg { optional bytes fileName = 1; optional uint32 busId = 2; optional string fileId = 3; optional uint64 fileSize = 4; optional uint64 deadTime = 5; optional bytes fileSha1 = 6; optional bytes ext = 7; optional bytes fileMd5 = 8; } message ImageMsg { optional string md5 = 1; optional string uuid = 2; optional uint32 imgType = 3; optional uint32 fileSize = 4; optional uint32 width = 5; optional uint32 height = 6; optional uint32 fileId = 101; optional uint32 serverIp = 102; optional uint32 serverPort = 103; optional string filePath = 104; optional string thumbUrl = 201; optional string originalUrl = 202; optional string resaveUrl = 203; } message MsgElem { optional uint32 type = 1; optional TextMsg textMsg = 11; optional FaceMsg faceMsg = 12; optional ImageMsg imageMsg = 13; optional GroupFileMsg groupFileMsg = 14; optional ShareMsg shareMsg = 15; optional RichMsg richMsg = 16; optional ArkMsg arkMsg = 17; } message MsgInfo { optional uint32 seq = 1; optional uint32 random = 2; } message MsgProcessInfo { optional MsgInfo msg = 1; optional uint32 errorCode = 2; optional uint64 digestUin = 3; optional uint32 digestTime = 4; } */ message EACReqBody { optional uint64 groupCode = 1; optional uint32 seq = 2; optional uint32 random = 3; } /* message RichMsg { optional uint32 serviceId = 1; optional string xml = 2; optional string longMsgResid = 3; } */ message EACRspBody { optional string wording = 1; optional uint64 digestUin = 2; optional uint32 digestTime = 3; //optional DigestMsg msg = 4; optional uint32 errorCode = 10; } /* message ShareMsg { optional string type = 1; optional string title = 2; optional string summary = 3; optional string brief = 4; optional string url = 5; optional string pictureUrl = 6; optional string action = 7; optional string source = 8; optional string sourceUrl = 9; } message TextMsg { optional bytes str = 1; } */ ================================================ FILE: ricq-core/src/pb/oidb/oidb0xeb7.proto ================================================ syntax = "proto2"; package oidb; // DEB7 prefix message DEB7ReqBody { optional StSignInStatusReq signInStatusReq = 1; optional StSignInWriteReq signInWriteReq = 2; } message DEB7Ret { optional uint32 code = 1; optional string msg = 2; } message DEB7RspBody { optional StSignInStatusRsp signInStatusRsp = 1; optional StSignInWriteRsp signInWriteRsp = 2; } message SignInStatusBase { optional uint32 status = 1; optional int64 currentTimeStamp = 2; } message SignInStatusDoneInfo { optional string leftTitleWrod = 1; optional string rightDescWord = 2; repeated string belowPortraitWords = 3; optional string recordUrl = 4; } message SignInStatusGroupScore { optional string groupScoreWord = 1; optional string scoreUrl = 2; } message SignInStatusNotInfo { optional string buttonWord = 1; optional string signDescWordLeft = 2; optional string signDescWordRight = 3; } message SignInStatusYesterdayFirst { optional string yesterdayFirstUid = 1; optional string yesterdayWord = 2; optional string yesterdayNick = 3; } message StDaySignedInfo { optional string uid = 1; optional string uidGroupNick = 2; optional int64 signedTimeStamp = 3; optional int32 signInRank = 4; } message StDaySignedListReq { optional string dayYmd = 1; optional string uid = 2; optional string groupId = 3; optional int32 offset = 4; optional int32 limit = 5; } message StDaySignedListRsp { optional DEB7Ret ret = 1; repeated StDaySignedPage page = 2; } message StDaySignedPage { repeated StDaySignedInfo infos = 1; optional int32 offset = 2; optional int32 total = 3; } message StKingSignedInfo { optional string uid = 1; optional string groupNick = 2; optional int64 signedTimeStamp = 3; optional int32 signedCount = 4; } message StKingSignedListReq { optional string uid = 1; optional string groupId = 2; } message StKingSignedListRsp { optional DEB7Ret ret = 1; optional StKingSignedInfo yesterdayFirst = 2; repeated StKingSignedInfo topSignedTotal = 3; repeated StKingSignedInfo topSignedContinue = 4; } message StSignInRecordDaySigned { optional float daySignedRatio = 1; optional int32 dayTotalSignedUid = 2; optional StDaySignedPage daySignedPage = 3; optional string daySignedUrl = 4; } message StSignInRecordKing { optional StKingSignedInfo yesterdayFirst = 1; repeated StKingSignedInfo topSignedTotal = 2; repeated StKingSignedInfo topSignedContinue = 3; optional string kingUrl = 4; } message StSignInRecordReq { optional string dayYmd = 1; optional string uid = 2; optional string groupId = 3; } message StSignInRecordRsp { optional DEB7Ret ret = 1; optional SignInStatusBase base = 2; optional StSignInRecordUser userRecord = 3; optional StSignInRecordDaySigned daySigned = 4; optional StSignInRecordKing kingRecord = 5; optional StViewGroupLevel level = 6; } message StSignInRecordUser { optional int32 totalSignedDays = 2; optional int64 earliestSignedTimeStamp = 3; optional int64 continueSignedDays = 4; repeated string historySignedDays = 5; optional string groupName = 6; } message StSignInStatusReq { optional string uid = 1; optional string groupId = 2; optional uint32 scene = 3; optional string clientVersion = 4; } message StSignInStatusRsp { optional DEB7Ret ret = 1; optional SignInStatusBase base = 2; optional SignInStatusYesterdayFirst yesterday = 3; optional SignInStatusNotInfo notInfo = 4; optional SignInStatusDoneInfo doneInfo = 5; optional SignInStatusGroupScore groupScore = 6; optional string mantleUrl = 7; optional string backgroundUrl = 8; } message StSignInWriteReq { optional string uid = 1; optional string groupId = 2; optional string clientVersion = 3; } message StSignInWriteRsp { optional DEB7Ret ret = 1; optional SignInStatusDoneInfo doneInfo = 2; optional SignInStatusGroupScore groupScore = 3; } message StViewGroupLevel { optional string title = 1; optional string url = 2; } ================================================ FILE: ricq-core/src/pb/online_status/OnlineStatusExtInfo.java.proto ================================================ syntax = "proto2"; package online_status; message AutoStateBizInfo { optional uint64 updateTime = 1; } message CustomStatus { optional uint64 faceIndex = 1; optional string wording = 2; optional uint64 faceType = 3; } message WeatherBizInfo { optional string weatherType = 1; optional string weatherTypeId = 2; optional uint32 adcode = 3; optional uint64 updateTime = 4; optional string city = 5; optional string area = 6; optional string temper = 7; optional uint32 flag = 8; optional string weatherDesc = 9; } message ZodiacBizInfo { optional string todayTrend = 1; optional string tomorrowTrend = 2; optional string miniapp = 3; optional string todayDate = 4; optional string luckyColor = 5; optional string luckyNumber = 6; } ================================================ FILE: ricq-core/src/pb/profilecard/busi.proto ================================================ syntax = "proto2"; package profilecard; message BusiColor { optional int32 r = 1; optional int32 g = 2; optional int32 b = 3; } message BusiComm { optional int32 ver = 1; optional int32 seq = 2; optional int64 fromuin = 3; optional int64 touin = 4; optional int32 service = 5; optional int32 sessionType = 6; optional bytes sessionKey = 7; optional int32 clientIp = 8; optional BusiUi display = 9; optional int32 result = 10; optional string errMsg = 11; optional int32 platform = 12; optional string qqver = 13; optional int32 build = 14; optional BusiLoginSig msgLoginSig = 15; optional int32 version = 17; optional BusiUinInfo msgUinInfo = 18; optional BusiRichUi msgRichDisplay = 19; } message BusiCommonReq { optional string serviceCmd = 1; optional BusiVisitorCountReq vcReq = 2; optional BusiHideRecordsReq hrReq = 3; } message BusiDetailRecord { optional int32 fuin = 1; optional int32 source = 2; optional int32 vtime = 3; optional int32 mod = 4; optional int32 hideFlag = 5; } message BusiHideRecordsReq { optional int32 huin = 1; optional int32 fuin = 2; repeated BusiDetailRecord records = 3; } message BusiLabel { optional bytes name = 1; optional int32 enumType = 2; optional BusiColor textColor = 3; optional BusiColor edgingColor = 4; optional int32 labelAttr = 5; optional int32 labelType = 6; } message BusiLoginSig { optional int32 type = 1; optional bytes sig = 2; optional int32 appid = 3; } message BusiRichUi { optional string name = 1; optional string serviceUrl = 2; //repeated UiInfo uiList = 3; } message BusiUi { optional string url = 1; optional string title = 2; optional string content = 3; optional string jumpUrl = 4; } message BusiUinInfo { optional int64 int64Longitude = 1; optional int64 int64Latitude = 2; } message BusiVisitorCountReq { optional int32 requireuin = 1; optional int32 operuin = 2; optional int32 mod = 3; optional int32 reportFlag = 4; } message BusiVisitorCountRsp { optional int32 requireuin = 1; optional int32 totalLike = 2; optional int32 totalView = 3; optional int32 hotValue = 4; optional int32 redValue = 5; optional int32 hotDiff = 6; } ================================================ FILE: ricq-core/src/pb/profilecard/gate.proto ================================================ syntax = "proto2"; package profilecard; message GateCommTaskInfo { optional int32 appid = 1; optional bytes taskData = 2; } message GateGetGiftListReq { optional int32 uin = 1; } message GateGetGiftListRsp { repeated string giftUrl = 1; optional string customUrl = 2; optional string desc = 3; optional bool isOn = 4; } message GateGetVipCareReq { optional int64 uin = 1; } message GateGetVipCareRsp { optional int32 buss = 1; optional int32 notice = 2; } message GateOidbFlagInfo { optional int32 fieled = 1; optional bytes byetsValue = 2; } message GatePrivilegeBaseInfoReq { optional int64 uReqUin = 1; } message GatePrivilegeBaseInfoRsp { optional bytes msg = 1; optional bytes jumpUrl = 2; repeated GatePrivilegeInfo vOpenPriv = 3; repeated GatePrivilegeInfo vClosePriv = 4; optional int32 uIsGrayUsr = 5; } message GatePrivilegeInfo { optional int32 iType = 1; optional int32 iSort = 2; optional int32 iFeeType = 3; optional int32 iLevel = 4; optional int32 iFlag = 5; optional bytes iconUrl = 6; optional bytes deluxeIconUrl = 7; optional bytes jumpUrl = 8; optional int32 iIsBig = 9; } message GateVaProfileGateReq { optional int32 uCmd = 1; optional GatePrivilegeBaseInfoReq stPrivilegeReq = 2; optional GateGetGiftListReq stGiftReq = 3; repeated GateCommTaskInfo taskItem = 4; repeated GateOidbFlagInfo oidbFlag = 5; optional GateGetVipCareReq stVipCare = 6; } message GateQidInfoItem { optional string qid = 1; optional string url = 2; optional string color = 3; optional string logoUrl = 4; } message GateVaProfileGateRsp { optional int32 iRetCode = 1; optional bytes sRetMsg = 2; optional GatePrivilegeBaseInfoRsp stPrivilegeRsp = 3; optional GateGetGiftListRsp stGiftRsp = 4; repeated GateCommTaskInfo taskItem = 5; repeated GateOidbFlagInfo oidbFlag = 6; optional GateGetVipCareRsp stVipCare = 7; optional GateQidInfoItem qidInfo = 9; } ================================================ FILE: ricq-core/src/pb/short_video/short_video.proto ================================================ syntax = "proto3"; package short_video; message ShortVideoReqBody { int32 cmd = 1; int32 seq = 2; ShortVideoUploadReq pttShortVideoUploadReq = 3; ShortVideoDownloadReq pttShortVideoDownloadReq = 4; repeated ShortVideoExtensionReq extensionReq = 100; } message ShortVideoRspBody { int32 cmd = 1; int32 seq = 2; ShortVideoUploadRsp pttShortVideoUploadRsp = 3; ShortVideoDownloadRsp pttShortVideoDownloadRsp = 4; } message ShortVideoUploadReq { int64 fromUin = 1; int64 toUin = 2; int32 chatType = 3; int32 clientType = 4; ShortVideoFileInfo info = 5; int64 groupCode = 6; int32 agentType = 7; int32 businessType = 8; int32 supportLargeSize = 20; } message ShortVideoDownloadReq { int64 fromUin = 1; int64 toUin = 2; int32 chatType = 3; int32 clientType = 4; string fileId = 5; int64 groupCode = 6; int32 agentType = 7; bytes fileMd5 = 8; int32 businessType = 9; int32 fileType = 10; int32 downType = 11; int32 sceneType = 12; } message ShortVideoDownloadRsp { int32 retCode = 1; string retMsg = 2; repeated ShortVideoIpList sameAreaOutAddr = 3; repeated ShortVideoIpList diffAreaOutAddr = 4; bytes downloadKey = 5; bytes fileMd5 = 6; repeated ShortVideoIpList sameAreaInnerAddr = 7; repeated ShortVideoIpList diffAreaInnerAddr = 8; ShortVideoAddr downloadAddr = 9; bytes encryptKey = 10; } message ShortVideoUploadRsp { int32 retCode = 1; string retMsg = 2; repeated ShortVideoIpList sameAreaOutAddr = 3; repeated ShortVideoIpList diffAreaOutAddr = 4; bytes fileId = 5; bytes uKey = 6; int32 fileExists = 7; repeated ShortVideoIpList sameAreaInnerAddr = 8; repeated ShortVideoIpList diffAreaInnerAddr = 9; repeated DataHole dataHole = 10; } message ShortVideoFileInfo { string fileName = 1; bytes fileMd5 = 2; bytes thumbFileMd5 = 3; int64 fileSize = 4; int32 fileResLength = 5; int32 fileResWidth = 6; int32 fileFormat = 7; int32 fileTime = 8; int64 thumbFileSize = 9; } message DataHole { int64 begin = 1; int64 end = 2; } message ShortVideoIpList { int32 ip = 1; int32 port = 2; } message ShortVideoAddr { repeated string host = 10; string urlArgs = 11; //repeated string domain = 13; } message ShortVideoExtensionReq { int32 subBusiType = 1; int32 userCnt = 2; } ================================================ FILE: ricq-core/src/pb/sig_act/sig_act.proto ================================================ syntax = "proto2"; package sig_act; message Platform { optional int64 platform = 1; optional string osver = 2; optional string mqqver = 3; } message ReqBody { optional uint32 cmd = 1; optional uint64 seq = 2; optional Platform plf = 3; optional SigactReq req = 4; optional SigauthReq authReq = 5; optional uint32 source = 6; } message RspBody { optional int32 ret = 1; optional string desc = 2; optional uint32 cmd = 3; optional uint64 seq = 4; optional SigactRsp rsp = 5; optional SigauthRsp authRsp = 6; } message SigactReq { optional uint64 uinDisable = 1; optional int32 actid = 2; optional int32 acttype = 3; } message SigactRsp { optional uint64 uin = 1; optional uint32 rank = 2; } message SigauthReq { optional uint64 uinDisable = 1; optional int32 itemid = 2; optional int32 len = 3; optional bytes data = 4; optional int32 fontid = 5; } message SigauthRsp { optional bytes result = 1; optional string url = 2; optional TipsInfo tipsInfo = 3; optional int32 authfailedAppid = 4; message TipsInfo { optional bool valid = 1; optional int32 ret = 2; optional uint32 type = 3; optional string titleWording = 4; optional string wording = 5; optional string rightBtnWording = 6; optional string leftBtnWording = 7; optional string vipType = 8; optional uint32 vipMonth = 9; optional string url = 10; } } ================================================ FILE: ricq-core/src/pb/structmsg/structmsg.proto ================================================ syntax = "proto3"; //option go_package = "./;structmsg"; package structmsg; message AddFrdSNInfo { int32 notSeeDynamic = 1; int32 setSn = 2; } message FlagInfo { int32 grpMsgKickAdmin = 1; int32 grpMsgHiddenGrp = 2; int32 grpMsgWordingDown = 3; int32 frdMsgGetBusiCard = 4; int32 grpMsgGetOfficialAccount = 5; int32 grpMsgGetPayInGroup = 6; int32 frdMsgDiscuss2ManyChat = 7; int32 grpMsgNotAllowJoinGrpInviteNotFrd = 8; int32 frdMsgNeedWaitingMsg = 9; int32 frdMsgUint32NeedAllUnreadMsg = 10; int32 grpMsgNeedAutoAdminWording = 11; int32 grpMsgGetTransferGroupMsgFlag = 12; int32 grpMsgGetQuitPayGroupMsgFlag = 13; int32 grpMsgSupportInviteAutoJoin = 14; int32 grpMsgMaskInviteAutoJoin = 15; int32 grpMsgGetDisbandedByAdmin = 16; int32 grpMsgGetC2cInviteJoinGroup = 17; } message FriendInfo { string msgJointFriend = 1; string msgBlacklist = 2; } message SGroupInfo { int32 groupAuthType = 1; int32 displayAction = 2; string msgAlert = 3; string msgDetailAlert = 4; string msgOtherAdminDone = 5; int32 appPrivilegeFlag = 6; } message MsgInviteExt { int32 srcType = 1; int64 srcCode = 2; int32 waitState = 3; } message MsgPayGroupExt { int64 joinGrpTime = 1; int64 quitGrpTime = 2; } message ReqNextSystemMsg { int32 msgNum = 1; int64 followingFriendSeq = 2; int64 followingGroupSeq = 3; int32 checktype = 4; FlagInfo flag = 5; int32 language = 6; int32 version = 7; int64 friendMsgTypeFlag = 8; } message ReqSystemMsg { int32 msgNum = 1; int64 latestFriendSeq = 2; int64 latestGroupSeq = 3; int32 version = 4; int32 language = 5; } message ReqSystemMsgAction { int32 msgType = 1; int64 msgSeq = 2; int64 reqUin = 3; int32 subType = 4; int32 srcId = 5; int32 subSrcId = 6; int32 groupMsgType = 7; SystemMsgActionInfo actionInfo = 8; int32 language = 9; } message ReqSystemMsgNew { int32 msgNum = 1; int64 latestFriendSeq = 2; int64 latestGroupSeq = 3; int32 version = 4; int32 checktype = 5; FlagInfo flag = 6; int32 language = 7; bool isGetFrdRibbon = 8; bool isGetGrpRibbon = 9; int64 friendMsgTypeFlag = 10; int32 reqMsgType = 11; } message ReqSystemMsgRead { int64 latestFriendSeq = 1; int64 latestGroupSeq = 2; int32 type = 3; int32 checktype = 4; } message RspHead { int32 result = 1; string msgFail = 2; } message RspNextSystemMsg { RspHead head = 1; repeated StructMsg msgs = 2; int64 followingFriendSeq = 3; int64 followingGroupSeq = 4; int32 checktype = 5; string gameNick = 100; bytes undecidForQim = 101; int32 unReadCount3 = 102; } message RspSystemMsg { RspHead head = 1; repeated StructMsg msgs = 2; int32 unreadCount = 3; int64 latestFriendSeq = 4; int64 latestGroupSeq = 5; int64 followingFriendSeq = 6; int64 followingGroupSeq = 7; string msgDisplay = 8; } message RspSystemMsgAction { RspHead head = 1; string msgDetail = 2; int32 type = 3; string msgInvalidDecided = 5; int32 remarkResult = 6; } message RspSystemMsgNew { RspHead head = 1; int32 unreadFriendCount = 2; int32 unreadGroupCount = 3; int64 latestFriendSeq = 4; int64 latestGroupSeq = 5; int64 followingFriendSeq = 6; int64 followingGroupSeq = 7; repeated StructMsg friendmsgs = 9; repeated StructMsg groupmsgs = 10; StructMsg msgRibbonFriend = 11; StructMsg msgRibbonGroup = 12; string msgDisplay = 13; string grpMsgDisplay = 14; int32 over = 15; int32 checktype = 20; string gameNick = 100; bytes undecidForQim = 101; int32 unReadCount3 = 102; } message RspSystemMsgRead { RspHead head = 1; int32 type = 2; int32 checktype = 3; } message StructMsg { int32 version = 1; int32 msgType = 2; int64 msgSeq = 3; int64 msgTime = 4; int64 reqUin = 5; int32 unreadFlag = 6; SystemMsg msg = 50; } message SystemMsg { int32 subType = 1; string msgTitle = 2; string msgDescribe = 3; string msgAdditional = 4; string msgSource = 5; string msgDecided = 6; int32 srcId = 7; int32 subSrcId = 8; repeated SystemMsgAction actions = 9; int64 groupCode = 10; int64 actionUin = 11; int32 groupMsgType = 12; int32 groupInviterRole = 13; FriendInfo friendInfo = 14; SGroupInfo groupInfo = 15; int64 actorUin = 16; string msgActorDescribe = 17; string msgAdditionalList = 18; int32 relation = 19; int32 reqsubtype = 20; int64 cloneUin = 21; int64 discussUin = 22; int64 eimGroupId = 23; MsgInviteExt msgInviteExtinfo = 24; MsgPayGroupExt msgPayGroupExtinfo = 25; int32 sourceFlag = 26; bytes gameNick = 27; bytes gameMsg = 28; int32 groupFlagext3 = 29; int64 groupOwnerUin = 30; int32 doubtFlag = 31; bytes warningTips = 32; bytes nameMore = 33; int32 reqUinFaceid = 50; string reqUinNick = 51; string groupName = 52; string actionUinNick = 53; string msgQna = 54; string msgDetail = 55; int32 groupExtFlag = 57; string actorUinNick = 58; string picUrl = 59; string cloneUinNick = 60; string reqUinBusinessCard = 61; string eimGroupIdName = 63; string reqUinPreRemark = 64; string actionUinQqNick = 65; string actionUinRemark = 66; int32 reqUinGender = 67; int32 reqUinAge = 68; int32 c2cInviteJoinGroupFlag = 69; int32 cardSwitch = 101; } message SystemMsgAction { string name = 1; string result = 2; int32 action = 3; SystemMsgActionInfo actionInfo = 4; string detailName = 5; } message SystemMsgActionInfo { int32 type = 1; int64 groupCode = 2; bytes sig = 3; string msg = 50; int32 groupId = 51; string remark = 52; bool blacklist = 53; AddFrdSNInfo addFrdSNInfo = 54; } ================================================ FILE: ricq-core/src/protocol/device.rs ================================================ use bytes::Bytes; use rand::distributions::DistString; use rand::{distributions::Alphanumeric, Rng, RngCore}; use serde::{Deserialize, Serialize}; use crate::hex::encode_hex; use crate::protocol::qimei::Qimei; //系统版本 #[derive(Serialize, Deserialize, Debug, Clone)] pub struct OSVersion { pub incremental: String, pub release: String, pub codename: String, pub sdk: u32, } impl Default for OSVersion { fn default() -> Self { OSVersion { incremental: "5891938".into(), release: "10".into(), codename: "REL".into(), sdk: 29, } } } //手机设备信息 #[derive(Default, Serialize, Deserialize, Debug, Clone)] pub struct Device { pub display: String, pub product: String, pub device: String, pub board: String, pub model: String, pub finger_print: String, pub boot_id: String, pub proc_version: String, pub imei: String, pub brand: String, pub bootloader: String, pub base_band: String, pub version: OSVersion, pub sim_info: String, pub os_type: String, pub mac_address: String, pub ip_address: Vec, pub wifi_bssid: String, pub wifi_ssid: String, pub imsi_md5: Vec, pub android_id: String, pub apn: String, pub vendor_name: String, pub vendor_os_name: String, pub qimei: Option, } impl Device { pub fn random() -> Self { Self::random_with_rng(&mut rand::thread_rng()) } pub fn random_with_rng(rng: &mut RNG) -> Self { Self { display: format!("RICQ.{}.001", rng.gen_range(100000..999999)), product: "iarim".into(), device: "sagit".into(), board: "eomam".into(), model: "MI 6".into(), finger_print: format!( "xiaomi/iarim/sagit:10/eomam.200122.001/{}:user/release-keys", rng.gen_range(1000000..9999999) ), boot_id: random_uuid(rng), imei: random_imei(rng), proc_version: format!( "Linux 5.4.0-54-generic-{} (android-build@google.com)", Alphanumeric.sample_string(rng, 8) ), brand: "Xiaomi".into(), bootloader: "U-boot".into(), base_band: "".into(), version: OSVersion::default(), sim_info: "T-Mobile".into(), os_type: "android".into(), mac_address: "00:50:56:C0:00:08".into(), ip_address: vec![10, 0, 1, 3], wifi_bssid: "00:50:56:C0:00:08".into(), wifi_ssid: "".into(), imsi_md5: md5::compute(rng.gen::<[u8; 16]>()).to_vec(), android_id: encode_hex(&rng.gen::<[u8; 8]>()), apn: "wifi".into(), vendor_name: "MIUI".into(), vendor_os_name: "ricq".into(), qimei: None, } } pub fn ksid(&self) -> Bytes { Bytes::from( format!("|{}|A8.2.7.27f6ea96", self.imei) .as_bytes() .to_vec(), ) } pub fn set_qimei(&mut self, qimei: Qimei) { self.qimei = Some(qimei) } } pub fn random_string(len: usize) -> String { Alphanumeric.sample_string(&mut rand::thread_rng(), len) } pub fn random_uuid(rng: &mut RNG) -> String { let r = md5::compute(rng.gen::<[u8; 16]>()).to_vec(); format!( "{}-{}-{}-{}-{}", encode_hex(&r[0..4]), encode_hex(&r[4..6]), encode_hex(&r[6..8]), encode_hex(&r[8..10]), encode_hex(&r[10..16]) ) } pub fn random_imei(rng: &mut RNG) -> String { let mut sum = 0; let mut str = String::new(); for i in 0..14 { let mut to_add = rng.gen_range(0..10); if (i + 2) % 2 == 0 { to_add *= 2; if to_add >= 10 { to_add = (to_add % 10) + 1 } } sum += to_add; str.push_str(&to_add.to_string()); } let ctrl_digit = (sum * 9) % 10; str.push_str(&ctrl_digit.to_string()); str } ================================================ FILE: ricq-core/src/protocol/mod.rs ================================================ pub mod device; pub mod oicq; pub mod packet; pub mod qimei; pub mod sig; pub mod transport; pub mod version; ================================================ FILE: ricq-core/src/protocol/oicq.rs ================================================ use bytes::{Buf, BufMut, Bytes, BytesMut}; use rand::Rng; use crate::binary::BinaryWriter; use crate::crypto::{qqtea_decrypt, EncryptECDH}; use crate::{RQError, RQResult}; #[derive(Debug, derivative::Derivative)] #[derivative(Default)] pub enum EncryptionMethod { #[derivative(Default)] ECDH, ST, } #[derive(Default)] pub struct Message { pub uin: u32, pub command: u16, pub body: Bytes, pub encryption_method: EncryptionMethod, } pub struct Codec { pub ecdh: EncryptECDH, pub random_key: Bytes, pub wt_session_ticket_key: Bytes, } impl Default for Codec { fn default() -> Self { Self { ecdh: Default::default(), random_key: Bytes::from(rand::thread_rng().gen::<[u8; 16]>().to_vec()), wt_session_ticket_key: Default::default(), } } } impl Codec { pub fn encode(&self, m: Message) -> Bytes { let mut w = BytesMut::new(); w.put_u8(0x02); w.put_u16(0); // TODO w.len() w.put_u16(8001); w.put_u16(m.command); w.put_u16(1); w.put_u32(m.uin); w.put_u8(0x03); match m.encryption_method { EncryptionMethod::ECDH => w.put_u8(0x87), EncryptionMethod::ST => w.put_u8(0x45), } w.put_u8(0); w.put_u32(2); w.put_u32(0); w.put_u32(0); match m.encryption_method { EncryptionMethod::ECDH => { w.put_u8(0x02); w.put_u8(0x01); w.put_slice(&self.random_key); w.put_u16(0x01_31); w.put_u16(self.ecdh.public_key_ver); w.put_u16(self.ecdh.public_key.len() as u16); w.put_slice(&self.ecdh.public_key); w.encrypt_and_write(&self.ecdh.initial_share_key, &m.body); } EncryptionMethod::ST => { w.put_u8(0x01); w.put_u8(0x03); w.put_slice(&self.random_key); w.put_u16(0x0102); w.put_u16(0x0000); w.encrypt_and_write(&self.random_key, &m.body); } } w.put_u8(0x03); let len = w.len(); w[1..3].as_mut().put_u16(len as u16); w.freeze() } pub fn decode(&self, mut reader: B) -> RQResult where B: Buf, { let flag = reader.get_u8(); if flag != 2 { return Err(RQError::UnknownFlag(flag)); } let mut m = Message::default(); reader.get_u16(); // len reader.get_u16(); // version m.command = reader.get_u16(); reader.get_u16(); // 1 m.uin = reader.get_i32() as u32; reader.get_u8(); let encrypt_type = reader.get_u8(); reader.get_u8(); match encrypt_type { 0 => { let len = reader.remaining() - 1; let d = reader.copy_to_bytes(len); m.body = Bytes::from(qqtea_decrypt(&d, &self.ecdh.initial_share_key)); } 3 => { let len = reader.remaining() - 1; let d = reader.copy_to_bytes(len); m.body = Bytes::from(qqtea_decrypt(&d, &self.wt_session_ticket_key)); } _ => return Err(RQError::UnknownEncryptType), } Ok(m) } } ================================================ FILE: ricq-core/src/protocol/packet.rs ================================================ use bytes::Bytes; use crate::{RQError, RQResult}; #[derive(PartialEq, derivative::Derivative, Eq)] #[derivative(Default, Debug, Clone)] pub enum PacketType { #[derivative(Default)] Simple, Login, } impl PacketType { pub fn value(&self) -> u32 { match self { PacketType::Login => 0x0A, PacketType::Simple => 0x0B, } } pub fn from_i32(v: i32) -> RQResult { match v { 0x0A => Ok(Self::Login), 0x0B => Ok(Self::Simple), _ => Err(RQError::InvalidPacketType), } } } #[derive(PartialEq, derivative::Derivative, Eq, Clone)] #[derivative(Default, Debug)] pub enum EncryptType { #[derivative(Default)] NoEncrypt, D2Key, EmptyKey, } impl EncryptType { pub fn value(&self) -> u32 { match self { EncryptType::NoEncrypt => 0x00, EncryptType::D2Key => 0x01, EncryptType::EmptyKey => 0x02, } } pub fn from_u8(v: u8) -> RQResult { match v { 0x00 => Ok(Self::NoEncrypt), 0x01 => Ok(Self::D2Key), 0x02 => Ok(Self::EmptyKey), _ => Err(RQError::InvalidEncryptType), } } } #[derive(Default, Debug, Clone)] pub struct Packet { pub packet_type: PacketType, pub encrypt_type: EncryptType, pub seq_id: i32, pub body: Bytes, pub command_name: String, pub uin: i64, pub message: String, pub sign: Option, } impl Packet { pub fn check_command_name(self, command_name: &str) -> RQResult { if self.command_name != command_name { Err(RQError::CommandNameMismatch( command_name.to_owned(), self.command_name, )) } else { Ok(self) } } } ================================================ FILE: ricq-core/src/protocol/qimei.rs ================================================ use crate::hex::encode_hex; use crate::protocol::device::Device; use crate::protocol::version::Version; use crate::{RQError, RQResult}; use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; use base64::Engine; use rand::distributions::Slice; use rand::{CryptoRng, Rng, RngCore}; use rsa::traits::PaddingScheme; use serde::{Deserialize, Serialize}; use x509_cert::spki::DecodePublicKey; const RSA_PUB_KEY: &str = r#"-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEIxgwoutfwoJxcGQeedgP7FG9 qaIuS0qzfR8gWkrkTZKM2iWHn2ajQpBRZjMSoSf6+KJGvar2ORhBfpDXyVtZCKpq LQ+FLkpncClKVIrBwv6PHyUvuCb0rIarmgDnzkfQAqVufEtR64iazGDKatvJ9y6B 9NMbHddGSAUmRTCrHQIDAQAB -----END PUBLIC KEY-----"#; const SECRET: &str = "ZdJqM15EeO2zWc08"; pub fn aes_decrypt(text: &[u8], key: &[u8]) -> RQResult> { cbc::Decryptor::::new_from_slices(key, key)? .decrypt_padded_vec_mut::(text) .map_err(Into::into) } pub fn aes_encrypt(text: &[u8], key: &[u8]) -> RQResult> { Ok(cbc::Encryptor::::new_from_slices(key, key)? .encrypt_padded_vec_mut::(text)) } pub fn rsa_pub_key() -> RQResult { rsa::RsaPublicKey::from_public_key_pem(RSA_PUB_KEY).map_err(Into::into) } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct DeviceReserved<'a> { pub bod: &'a str, pub brd: &'a str, pub clone: &'a str, pub containe: &'a str, pub dv: &'a str, pub first_level: &'a str, pub harmony: &'a str, pub host: &'a str, pub kelong: &'a str, pub kernel: &'a str, pub manufact: &'a str, pub multi_user: &'a str, pub name: &'a str, pub oo: &'a str, pub oz: &'a str, pub uptimes: String, } impl<'a> DeviceReserved<'a> { pub fn from_device(rng: &mut RNG, device: &'a Device) -> DeviceReserved<'a> { let now = chrono::Local::now(); let offset = chrono::Duration::seconds(rng.gen_range(0..14400)); let uptimes = now - offset; DeviceReserved { bod: &device.board, brd: &device.brand, clone: "0", containe: "", dv: &device.device, first_level: "", harmony: "0", host: "se.infra", kelong: "0", kernel: &device.proc_version, manufact: &device.brand, multi_user: "0", name: &device.model, oo: "Xecjt+9S1+f8Pz2VLSxgpw==", oz: "UhYmelwouA+V2nPWbOvLTgN2/m8jwGB+yUB5v9tysQg=", uptimes: uptimes.format("%F %T").to_string(), } } } pub fn rand_beacon_id(rng: &mut RNG) -> String { let mut beacon_id = String::with_capacity(1024); let month = chrono::Local::now().format("%Y-%m-01").to_string(); let rand1 = rng.gen_range(100000..999999).to_string(); let rand2 = rng.gen_range(100000000..999999999).to_string(); for i in 1..=40 { match i { 1 | 2 | 13 | 14 | 17 | 18 | 21 | 22 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 => { beacon_id.push('k'); beacon_id.push_str(i.to_string().as_str()); beacon_id.push(':'); beacon_id.push_str(month.as_str()); beacon_id.push_str(rand1.as_str()); beacon_id.push('.'); beacon_id.push_str(rand2.as_str()); } 3 => { beacon_id.push_str("k3:0000000000000000"); } 4 => { beacon_id.push_str("k4:"); beacon_id.push_str( rng.sample_iter(Slice::new("123456789abcdef".as_bytes()).expect("empty slice")) .take(16) .map(|n| *n as char) .collect::() .as_str(), ); } _ => { beacon_id.push('k'); beacon_id.push_str(i.to_string().as_str()); beacon_id.push(':'); beacon_id.push_str(rng.gen_range(0..10000).to_string().as_str()); } } beacon_id.push(';'); } beacon_id } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct QimeiRequestPayload<'a> { android_id: &'a str, app_key: &'a str, app_version: &'a str, audit: &'a str, beacon_id_src: String, brand: &'a str, channel_id: &'a str, cid: &'a str, device_type: &'a str, imei: &'a str, imsi: &'a str, mac: &'a str, model: &'a str, network_type: &'a str, oaid: &'a str, os_version: String, package_id: &'a str, platform_id: i64, qimei: &'a str, qimei36: &'a str, reserved: String, sdk_name: &'a str, sdk_version: &'a str, user_id: &'a str, } impl<'a> QimeiRequestPayload<'a> { pub fn new( rng: &mut RNG, device: &'a Device, version: &Version, ) -> QimeiRequestPayload<'a> { let device_reserved = DeviceReserved::from_device(rng, device); let beacon_id = rand_beacon_id(rng); QimeiRequestPayload { android_id: &device.android_id, app_key: version.app_key, app_version: version.sort_version_name, audit: "", beacon_id_src: beacon_id, brand: &device.brand, channel_id: "2017", cid: "", device_type: "", imei: &device.imei, imsi: "", mac: "", model: &device.model, network_type: "unknown", oaid: "", os_version: format!( "Android {},level {}", device.version.release, device.version.sdk ), package_id: version.apk_id, platform_id: 1, qimei: "", qimei36: "", reserved: serde_json::to_string(&device_reserved).expect("json"), sdk_name: "", sdk_version: "1.2.13.6", user_id: "{}", } } } #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct QimeiRequest { extra: String, key: String, nonce: String, params: String, sign: String, time: i64, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct QimeiResponse { code: i64, data: String, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct Qimei { pub q16: String, pub q36: String, } impl QimeiRequest { pub fn new( rng: &mut RNG, device: &Device, version: &Version, crypt_key: &[u8], ) -> RQResult { let payload = serde_json::to_string(&QimeiRequestPayload::new(rng, device, version))?; let ts = chrono::Local::now().timestamp() * 1000; let nonce = rng .sample_iter(Slice::new("abcdef1234567890".as_bytes()).unwrap()) .take(16) .map(|n| *n as char) .collect::(); let pub_key = rsa_pub_key()?; let encrypted_aes_key = rsa::Pkcs1v15Encrypt.encrypt(rng, &pub_key, crypt_key)?; let encrypted_payload = aes_encrypt(payload.as_bytes(), crypt_key)?; let key = base64::engine::general_purpose::STANDARD.encode(encrypted_aes_key); let params = base64::engine::general_purpose::STANDARD.encode(encrypted_payload); Ok(QimeiRequest { extra: "".to_string(), sign: encode_hex( &md5::compute(format!("{}{}{}{}{}", key, params, ts, nonce, SECRET)).to_vec(), ), key, nonce, params, time: ts, }) } } impl QimeiResponse { pub fn to_payload(self, crypt_key: &[u8]) -> RQResult { if self.code != 0 { return Err(RQError::QimeiError(self.code)); } let encrypted_response = base64::engine::general_purpose::STANDARD.decode(self.data)?; let decrypted_response = aes_decrypt(&encrypted_response, crypt_key)?; serde_json::from_slice(&decrypted_response).map_err(Into::into) } } ================================================ FILE: ricq-core/src/protocol/sig.rs ================================================ use std::collections::HashMap; use bytes::Bytes; use crate::protocol::device::Device; #[derive(Default, Debug)] pub struct Sig { pub login_bitmap: u64, pub tgt: Bytes, pub tgt_key: Bytes, // study room manager | 0x16a pub srm_token: Bytes, pub t133: Bytes, pub encrypted_a1: Bytes, pub user_st_key: Bytes, pub user_st_web_sig: Bytes, pub s_key: Bytes, pub s_key_expired_time: i64, pub d2: Bytes, pub d2key: Bytes, // TODO 是不是可能None? pub device_token: Bytes, pub ps_key_map: HashMap, pub pt4_token_map: HashMap, pub out_packet_session_id: Bytes, pub dpwd: Bytes, pub t104: Bytes, pub t547: Bytes, pub t174: Bytes, pub g: Bytes, pub t402: Bytes, pub rand_seed: Bytes, // t403 pub sync_const1: u32, pub sync_const2: u32, pub sync_const3: u32, pub sync_cookie: Bytes, pub pub_account_cookie: Bytes, // device? pub guid: Bytes, pub tgtgt_key: Bytes, pub ksid: Bytes, } impl Sig { pub fn new(device: &Device) -> Self { let mut sig = Self::default(); sig.guid = Bytes::from(md5::compute(device.android_id.to_owned() + &device.mac_address).to_vec()); sig.tgtgt_key = Bytes::from(md5::compute(&sig.guid).to_vec()); sig.ksid = Bytes::from(format!("|{}|A8.2.7.27f6ea96", device.imei)); sig.sync_const1 = rand::random::(); sig.sync_const2 = rand::random::(); sig.sync_const3 = rand::random::(); sig } } ================================================ FILE: ricq-core/src/protocol/transport.rs ================================================ use std::io::Read; use bytes::{Buf, BufMut, Bytes, BytesMut}; use flate2::read::ZlibDecoder; use crate::binary::{BinaryReader, BinaryWriter}; use crate::command::common::PbToBytes; use crate::crypto::{qqtea_decrypt, qqtea_encrypt}; use crate::protocol::{ device::Device, packet::{EncryptType, Packet, PacketType}, sig::Sig, version::Version, }; use crate::{oicq, pb, RQError, RQResult}; pub struct Transport { pub sig: Sig, pub device: Device, pub version: Version, pub oicq_codec: oicq::Codec, } impl Transport { pub fn new(device: Device, version: Version) -> Self { Self { sig: Sig::new(&device), device, version, oicq_codec: Default::default(), } } } impl Transport { pub fn encode_packet(&self, mut pkt: Packet) -> Bytes { if self.sig.d2.is_empty() { pkt.encrypt_type = EncryptType::EmptyKey } let mut w = BytesMut::new(); // let pos = w.len(); // w.put_u32(0); // vvv w.Write(head) vvv w.put_u32(pkt.packet_type.value()); w.put_u8(pkt.encrypt_type.value() as u8); match pkt.packet_type { PacketType::Simple => w.put_u32(pkt.seq_id as u32), PacketType::Login => match pkt.encrypt_type { EncryptType::D2Key => { w.put_u32(self.sig.d2.len() as u32 + 4); w.put_slice(&self.sig.d2); } _ => w.put_u32(4), }, } w.put_u8(0x00); w.write_string(&pkt.uin.to_string()); // ^^^ w.Write(head) ^^^ let mut w2 = BytesMut::new(); self.encode_body(&pkt, &mut w2); let mut body = w2.freeze(); match pkt.encrypt_type { EncryptType::D2Key => { body = Bytes::from(qqtea_encrypt(&body, &self.sig.d2key)); } EncryptType::EmptyKey => { body = Bytes::from(qqtea_encrypt(&body, &[0; 16])); } EncryptType::NoEncrypt => {} } w.put_slice(&body); // let len = w.len(); // w[pos..pos + 4].as_mut().put_u32(len as u32); w.freeze() } pub fn decode_packet(&self, mut r: B) -> RQResult where B: Buf, { let mut pkt = Packet { packet_type: PacketType::from_i32(r.get_i32())?, encrypt_type: EncryptType::from_u8(r.get_u8())?, ..Default::default() }; r.get_u8(); // 0x00 pkt.uin = r.read_string().parse().unwrap_or_default(); let mut body = Bytes::from(r.chunk().to_owned()); match pkt.encrypt_type { EncryptType::NoEncrypt => {} EncryptType::D2Key => body = Bytes::from(qqtea_decrypt(&body, &self.sig.d2key)), EncryptType::EmptyKey => body = Bytes::from(qqtea_decrypt(&body, &[0; 16])), } self.decode_sso_frame(&mut pkt, body)?; if pkt.encrypt_type == EncryptType::EmptyKey { // decrypt oicq_codec pkt.body = self.oicq_codec.decode(pkt.body)?.body; } Ok(pkt) } fn encode_body(&self, pkt: &Packet, w: &mut BytesMut) { let pos = w.len(); w.put_u32(0); // len if pkt.packet_type == PacketType::Login { w.put_u32(pkt.seq_id as u32); w.put_u32(self.version.app_id); w.put_u32(self.version.sub_app_id); w.put_slice(&[ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, ]); let tgt = &self.sig.tgt; if tgt.is_empty() || tgt.len() == 4 { w.put_u32(0x04); } else { w.put_u32(tgt.len() as u32 + 4); w.put_slice(tgt); } } w.write_string(&pkt.command_name); w.put_u32(self.sig.out_packet_session_id.len() as u32 + 4); w.put_slice(&self.sig.out_packet_session_id); if pkt.packet_type == PacketType::Login { w.write_string(&self.device.imei); w.put_u32(0x04); w.put_u16(self.device.ksid().len() as u16 + 2); w.put_slice(&self.device.ksid()); } if let Some(ref sign) = pkt.sign { w.put_u32(sign.len() as u32 + 4); w.put_slice(&sign); } w.put_u32( 0x04 + self .device .qimei .as_ref() .map(|qimei| qimei.q16.len()) .unwrap_or_default() as u32, ); w.put_slice( self.device .qimei .as_ref() .map(|qimei| qimei.q16.as_bytes()) .unwrap_or_default(), ); // write len let len = w.len() - pos; w[pos..pos + 4].as_mut().put_u32(len as u32); w.put_u32(pkt.body.len() as u32 + 4); w.put_slice(&pkt.body); } fn decode_sso_frame(&self, pkt: &mut Packet, mut r: B) -> RQResult<()> where B: Buf, { let head_len = r.get_i32() as usize; if head_len - 4 > r.remaining() { return Err(RQError::PacketDropped); } let mut head = r.copy_to_bytes(head_len - 4); pkt.seq_id = head.get_i32(); let ret_code = head.get_i32(); match ret_code { 0 => {} -10008 => return Err(RQError::SessionExpired), other => return Err(RQError::UnsuccessfulRetCode(other)), } pkt.message = head.read_string(); pkt.command_name = head.read_string(); if &pkt.command_name == "Heartbeat.Alive" { return Ok(()); } let session_id_len = head.get_i32() as usize - 4; let _ = head.copy_to_bytes(session_id_len); let compress_flag = head.get_i32(); let mut body_len = r.get_i32() as usize - 4; body_len = if body_len > 0 && body_len <= r.remaining() { body_len } else { r.remaining() }; let mut body = r.copy_to_bytes(body_len); if compress_flag == 1 { let mut uncompressed = Vec::new(); ZlibDecoder::new(body.chunk()).read_to_end(&mut uncompressed)?; body = Bytes::from(uncompressed) } pkt.body = body; Ok(()) } pub fn encode_oidb_packet(&self, cmd: i32, service_type: i32, body: Bytes) -> Bytes { pb::oidb::OidbssoPkg { command: cmd, service_type, bodybuffer: body.to_vec(), client_version: format!("Android {}", self.version.sort_version_name), ..Default::default() } .to_bytes() } } ================================================ FILE: ricq-core/src/protocol/version.rs ================================================ use std::convert::TryFrom; // oicq/wlogin_sdk/request/WtloginHelper.java SigType pub const WLOGIN_A2: u32 = 64; pub const WLOGIN_A5: u32 = 2; pub const WLOGIN_AQSIG: u32 = 2097152; pub const WLOGIN_D2: u32 = 262144; pub const WLOGIN_DA2: u32 = 33554432; pub const WLOGIN_LHSIG: u32 = 4194304; pub const WLOGIN_LSKEY: u32 = 512; pub const WLOGIN_OPENKEY: u32 = 16384; pub const WLOGIN_PAYTOKEN: u32 = 8388608; pub const WLOGIN_PF: u32 = 16777216; pub const WLOGIN_PSKEY: u32 = 1048576; pub const WLOGIN_PT4_TOKEN: u32 = 134217728; pub const WLOGIN_QRPUSH: u32 = 67108864; pub const WLOGIN_RESERVED: u32 = 16; pub const WLOGIN_SID: u32 = 524288; pub const WLOGIN_SIG64: u32 = 8192; pub const WLOGIN_SKEY: u32 = 4096; pub const WLOGIN_ST: u32 = 128; pub const WLOGIN_STWEB: u32 = 32; pub const WLOGIN_TOKEN: u32 = 32768; pub const WLOGIN_VKEY: u32 = 131072; #[derive(Debug, Clone, derivative::Derivative, serde::Deserialize)] #[derivative(Default)] pub enum Protocol { #[derivative(Default)] IPad, AndroidPhone, AndroidWatch, AndroidPad, MacOS, QiDian, } #[derive(Debug, Clone, serde::Deserialize)] pub struct Version { pub apk_sign: &'static [u8], pub apk_id: &'static str, pub app_key: &'static str, pub sort_version_name: &'static str, pub build_ver: &'static str, pub sdk_version: &'static str, pub app_id: u32, pub sub_app_id: u32, pub build_time: u32, pub sso_version: u32, pub misc_bitmap: u32, pub sub_sig_map: u32, pub main_sig_map: u32, pub qua: &'static str, pub protocol: Protocol, } pub const fn get_version(p: Protocol) -> Version { match p { Protocol::IPad => IPAD, Protocol::AndroidPhone => ANDROID_PHONE, Protocol::AndroidWatch => ANDROID_WATCH, Protocol::AndroidPad => ANDROID_PAD, Protocol::MacOS => MACOS, Protocol::QiDian => QIDIAN, } } impl From for Version { fn from(p: Protocol) -> Version { get_version(p) } } pub const ANDROID_PHONE: Version = Version { apk_id: "com.tencent.mobileqq", app_id: 537164840, sub_app_id: 537164840, app_key: "0S200MNJT807V3GE", sort_version_name: "8.9.63.11390", build_ver: "8.9.63.11390", build_time: 1685069178, apk_sign: &[ 0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D, ], sdk_version: "6.0.0.2546", sso_version: 20, misc_bitmap: 150470524, sub_sig_map: 0x10400, // 16724722 main_sig_map: WLOGIN_A5 | WLOGIN_RESERVED | WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_LSKEY | WLOGIN_SKEY | WLOGIN_SIG64 | 1 << 16 | WLOGIN_VKEY | WLOGIN_D2 | WLOGIN_SID | WLOGIN_PSKEY | WLOGIN_AQSIG | WLOGIN_LHSIG | WLOGIN_PAYTOKEN, qua: "V1_AND_SQ_8.9.63_4194_YYB_D", protocol: Protocol::AndroidPhone, }; pub const APAD: Version = Version { apk_id: "com.tencent.mobileqq", app_id: 537164888, sub_app_id: 537164888, sort_version_name: "8.9.63.11390", build_ver: "8.9.33.614", build_time: 1685069178, apk_sign: &[ 0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D, ], sdk_version: "6.0.0.2546", sso_version: 20, misc_bitmap: 150470524, sub_sig_map: 0x10400, // 16724722 main_sig_map: WLOGIN_A5 | WLOGIN_RESERVED | WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_LSKEY | WLOGIN_SKEY | WLOGIN_SIG64 | 1 << 16 | WLOGIN_VKEY | WLOGIN_D2 | WLOGIN_SID | WLOGIN_PSKEY | WLOGIN_AQSIG | WLOGIN_LHSIG | WLOGIN_PAYTOKEN, protocol: Protocol::AndroidPad, app_key: "0S200MNJT807V3GE", qua: "V1_AND_SQ_8.9.63_4194_YYB_D", }; pub const IPAD: Version = Version { apk_id: "com.tencent.minihd.qq", app_id: 537151363, sub_app_id: 537151363, sort_version_name: "8.9.33.614", build_ver: "8.9.33.614", build_time: 1595836208, apk_sign: &[ 170, 57, 120, 244, 31, 217, 111, 249, 145, 74, 102, 158, 24, 100, 116, 199, ], sdk_version: "6.0.0.2433", sso_version: 19, misc_bitmap: 150470524, sub_sig_map: 66560, // 1970400 main_sig_map: WLOGIN_STWEB | WLOGIN_A2 | WLOGIN_ST | WLOGIN_SKEY | WLOGIN_VKEY | WLOGIN_D2 | WLOGIN_SID | WLOGIN_PSKEY, protocol: Protocol::IPad, app_key: "", qua: "", }; pub const ANDROID_WATCH: Version = Version { apk_id: "com.tencent.qqlite", app_id: 537064446, sub_app_id: 537064446, sort_version_name: "2.0.5", build_ver: "2.0.5", build_time: 1559564731, apk_sign: &[ 0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D, ], sdk_version: "6.0.0.236", sso_version: 5, misc_bitmap: 16252796, sub_sig_map: 0x10400, main_sig_map: 34869472, protocol: Protocol::AndroidWatch, qua: "", app_key: "", }; pub const MACOS: Version = Version { apk_id: "com.tencent.qq", // ok app_id: 0x2003ca32, // ok sub_app_id: 0x2003ca32, // ok sort_version_name: "6.7.9", // ok build_ver: "5.8.9.3460", // 6.7.9.xxx? build_time: 0, // ok apk_sign: "com.tencent.qq".as_bytes(), // ok sdk_version: "6.2.0.1023", // ok sso_version: 7, // ok misc_bitmap: 0x7ffc, // ok sub_sig_map: 66560, // ? main_sig_map: 1970400, // ? protocol: Protocol::MacOS, app_key: "", qua: "", }; pub const QIDIAN: Version = Version { apk_id: "com.tencent.qidian", app_id: 537061386, sub_app_id: 537036590, sort_version_name: "3.8.6", build_ver: "8.8.38.2266", build_time: 1556628836, apk_sign: &[ 160, 30, 236, 171, 133, 233, 227, 186, 43, 15, 106, 21, 140, 133, 92, 41, ], sdk_version: "6.0.0.2365", sso_version: 5, misc_bitmap: 49807228, sub_sig_map: 66560, main_sig_map: 34869472, protocol: Protocol::QiDian, app_key: "", qua: "", }; pub const ANDROID_PAD: Version = Version { apk_id: "com.tencent.mobileqq", app_id: 537154261, sub_app_id: 537154261, sort_version_name: "8.9.38.10545", build_ver: "8.8.38.2266", build_time: 1556628836, apk_sign: &[ 0xa6, 0xb7, 0x45, 0xbf, 0x24, 0xa2, 0xc2, 0x77, 0x52, 0x77, 0x16, 0xf6, 0xf3, 0x6e, 0xb6, 0x8d, ], sdk_version: "6.0.0.2535", sso_version: 19, misc_bitmap: 150470524, sub_sig_map: 66560, main_sig_map: 16724722, protocol: Protocol::AndroidPad, app_key: "", qua: "", }; impl TryFrom<&str> for Protocol { type Error = (); fn try_from(s: &str) -> Result { match s { "IPad" => Ok(Protocol::IPad), "AndroidPhone" | "APhone" => Ok(Protocol::AndroidPhone), "AndroidWatch" | "AWatch" => Ok(Protocol::AndroidWatch), "AndroidPad" | "APad" => Ok(Protocol::AndroidPad), "MacOS" => Ok(Protocol::MacOS), "QiDian" => Ok(Protocol::QiDian), _ => Err(()), } } } impl TryFrom for Protocol { type Error = (); fn try_from(u: u8) -> Result { match u { 0 => Ok(Protocol::IPad), // default 1 => Ok(Protocol::AndroidPhone), 2 => Ok(Protocol::AndroidWatch), 3 => Ok(Protocol::MacOS), 4 => Ok(Protocol::QiDian), 5 => Ok(Protocol::IPad), 6 => Ok(Protocol::AndroidPad), _ => Err(()), } } } ================================================ FILE: ricq-core/src/structs.rs ================================================ use bytes::Bytes; use std::time::Duration; pub use crate::command::multi_msg::{ForwardMessage, ForwardNode, MessageNode}; pub use crate::command::oidb_svc::{ LinkShare, MusicShare, MusicVersion, ProfileDetailUpdate, ShareTarget, }; pub use crate::command::stat_svc::{CustomOnlineStatus, ExtOnlineStatus, OnlineStatus, Status}; use crate::msg::MessageChain; use crate::{jce, pb}; #[derive(Default, Debug)] pub struct AccountInfo { pub nickname: String, pub age: u8, pub gender: u8, } #[derive(Default, Debug)] pub struct AddressInfo { pub srv_sso_addrs: Vec, pub other_srv_addrs: Vec, pub file_storage_info: jce::FileStoragePushFSSvcList, } #[derive(Debug, Default)] pub struct OtherClientInfo { pub app_id: i64, pub instance_id: i32, pub sub_platform: String, pub device_kind: String, } pub struct QiDianAccountInfo { pub master_uin: i64, pub ext_name: String, pub create_time: i64, pub big_data_req_addrs: Vec, pub big_data_req_session: BigDataReqSessionInfo, } #[derive(Debug, Default)] pub struct BigDataReqSessionInfo { pub sig_session: Bytes, pub session_key: Bytes, } #[derive(Debug, Default)] pub struct GroupInfo { pub uin: i64, pub code: i64, pub name: String, pub memo: String, pub owner_uin: i64, pub group_create_time: u32, pub group_level: u32, pub member_count: u16, pub max_member_count: u16, // 全群禁言时间 pub shut_up_timestamp: i64, // 自己被禁言时间 pub my_shut_up_timestamp: i64, // 最后一条信息的SEQ,只有通过 GetGroupInfo 函数获取的 GroupInfo 才会有 pub last_msg_seq: i64, } #[derive(Debug, Default, Clone)] pub struct GroupMemberInfo { pub group_code: i64, pub uin: i64, pub gender: u8, pub nickname: String, pub card_name: String, pub level: u16, pub join_time: i64, pub last_speak_time: i64, pub special_title: String, pub special_title_expire_time: i64, pub shut_up_timestamp: i64, pub permission: GroupMemberPermission, } #[derive(Debug, Clone, derivative::Derivative)] #[derivative(Default)] pub enum GroupMemberPermission { Owner = 1, Administrator = 2, #[derivative(Default)] Member = 3, } /// 好友信息 #[derive(Debug, Default, Clone)] pub struct FriendInfo { pub uin: i64, pub nick: String, pub remark: String, pub face_id: i16, pub group_id: u8, } /// 好友分组信息 #[derive(Debug, Default, Clone)] pub struct FriendGroupInfo { pub group_id: u8, pub group_name: String, pub friend_count: i32, pub online_friend_count: i32, pub seq_id: u8, } #[derive(Debug, Default, Clone)] pub struct SummaryCardInfo { pub uin: i64, pub sex: u8, pub age: u8, pub nickname: String, pub level: i32, pub city: String, pub sign: String, pub mobile: String, pub login_days: i64, /// 用于点赞 pub cookie: Bytes, } #[derive(Debug, Clone, Default)] pub struct FriendMessage { pub seqs: Vec, pub rands: Vec, pub target: i64, pub time: i32, pub from_uin: i64, pub from_nick: String, pub elements: MessageChain, } #[derive(Debug, Clone, Default)] pub struct GroupMessage { pub seqs: Vec, pub rands: Vec, pub group_code: i64, pub group_name: String, pub group_card: String, pub from_uin: i64, pub time: i32, pub elements: MessageChain, } #[derive(Debug, Clone, Default)] pub struct GroupTempMessage { pub seqs: Vec, pub rands: Vec, pub from_uin: i64, pub from_nick: String, pub time: i32, pub elements: MessageChain, pub group_code: i64, } #[derive(Debug, Clone, Default)] pub struct NewMember { pub group_code: i64, pub member_uin: i64, } #[derive(Debug, Clone, Default)] pub struct GroupMute { pub group_code: i64, pub operator_uin: i64, pub target_uin: i64, pub duration: Duration, } #[derive(Debug, Clone, Default)] pub struct FriendMessageRecall { pub msg_seq: i32, pub friend_uin: i64, pub time: i64, } #[derive(Debug, Clone, Default)] pub struct GroupMessageRecall { pub msg_seq: i32, pub group_code: i64, pub operator_uin: i64, pub author_uin: i64, pub time: i32, } #[derive(Debug, Clone, Default)] pub struct GroupLeave { pub group_code: i64, pub member_uin: i64, pub operator_uin: Option, } #[derive(Debug, Clone, Default)] pub struct FriendPoke { pub sender: i64, pub receiver: i64, } #[derive(Debug, Clone, Default)] pub struct GroupPoke { pub group_code: i64, pub sender: i64, pub receiver: i64, } #[derive(Debug, Clone, Default)] pub struct GroupNameUpdate { pub group_code: i64, pub operator_uin: i64, pub group_name: String, } #[derive(Debug, Clone, Default)] pub struct DeleteFriend { pub uin: i64, } #[derive(Debug, Clone, Default)] pub struct MemberPermissionChange { pub group_code: i64, pub member_uin: i64, pub new_permission: GroupMemberPermission, } #[derive(Debug, Clone, Default)] pub struct GroupDisband { pub group_code: i64, pub operator_uin: i64, } // 用于撤回 #[derive(Debug, Clone, Default)] pub struct MessageReceipt { pub seqs: Vec, pub rands: Vec, pub time: i64, } #[derive(Debug, Clone, Default)] pub struct GroupAudio(pub pb::msg::Ptt); #[derive(Debug, Clone, Default)] pub struct GroupAudioMessage { pub seqs: Vec, pub rands: Vec, pub group_code: i64, pub group_name: String, pub group_card: String, pub from_uin: i64, pub time: i32, pub audio: GroupAudio, } #[derive(Debug, Clone, Default)] pub struct FriendAudio(pub pb::msg::Ptt); #[derive(Debug, Clone, Default)] pub struct FriendAudioMessage { pub seqs: Vec, pub rands: Vec, pub target: i64, pub time: i32, pub from_uin: i64, pub from_nick: String, pub audio: FriendAudio, } // 群文件总数 #[derive(Debug, Clone, Default)] pub struct GroupFileCount { pub is_full: bool, pub all_file_count: u32, pub limit_count: u32, pub file_too_many: bool, } // 群文件列表 #[derive(Debug, Clone, Default)] pub struct GroupFileList { pub all_file_count: u32, pub is_end: bool, pub items: Vec, pub role: u32, pub next_index: u32, } // 群文件列表 #[derive(Debug, Clone, Default)] pub struct GroupFileItem { pub r#type: u32, pub folder_info: GroupFolderInfo, pub file_info: GroupFileInfo, } // 群文件夹 #[derive(Debug, Clone, Default)] pub struct GroupFolderInfo { pub folder_id: String, pub parent_folder_id: String, pub folder_name: String, pub create_time: u32, pub modify_time: u32, pub create_uin: u64, pub creator_name: String, pub total_file_count: u32, } // 群文件 #[derive(Debug, Clone, Default)] pub struct GroupFileInfo { pub file_id: String, pub file_name: String, pub file_size: u64, pub bus_id: u32, pub uploaded_size: u64, pub upload_time: u32, pub dead_time: u32, pub modify_time: u32, pub download_times: u32, pub sha: String, pub sha3: Bytes, pub md5: Bytes, pub local_path: String, pub uploader_name: String, pub uploader_uin: u64, pub parent_folder_id: String, } ================================================ FILE: ricq-core/src/token.rs ================================================ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Token { pub uin: i64, pub d2: Vec, pub d2key: Vec, pub tgt: Vec, pub srm_token: Vec, pub t133: Vec, pub encrypted_a1: Vec, pub out_packet_session_id: Vec, pub tgtgt_key: Vec, pub wt_session_ticket_key: Vec, // oicq } ================================================ FILE: ricq-core/src/utils/mod.rs ================================================ mod option_set; pub use option_set::OptionSet; ================================================ FILE: ricq-core/src/utils/option_set.rs ================================================ pub trait OptionSet: Sized { /// set value if is `Some(Self)` or do noting fn option_set(&mut self, value: Option); } impl OptionSet for T { #[inline] fn option_set(&mut self, value: Option) { if let Some(value) = value { *self = value } } } ================================================ FILE: ricq-core/src/wtlogin.rs ================================================ use std::sync::atomic::Ordering; use bytes::{BufMut, Bytes, BytesMut}; use crate::command::wtlogin::{ LoginDeviceLockLogin, LoginDeviceLocked, LoginNeedCaptcha, LoginResponse, LoginSuccess, QRCodeConfirmed, }; use crate::protocol::device::random_string; use crate::utils::OptionSet; use crate::Transport; impl super::Engine { pub fn process_qrcode_confirmed(&mut self, resp: &QRCodeConfirmed) { self.transport.sig.tgtgt_key = resp.tgtgt_key.clone(); self.uin.store(resp.uin, Ordering::Relaxed); } pub fn process_login_response(&mut self, login_response: &LoginResponse) { match login_response { LoginResponse::Success(resp) => self.process_login_success(resp.clone()), LoginResponse::NeedCaptcha(resp) => self.process_need_captcha(resp), LoginResponse::DeviceLocked(resp) => self.process_device_locked(resp), LoginResponse::DeviceLockLogin(resp) => self.process_device_lock_login(resp.clone()), _ => {} } } fn process_login_success(&mut self, resp: LoginSuccess) { let sig = &mut self.transport.sig; let oicq_codec = &mut self.transport.oicq_codec; // update sig.rand_seed.option_set(resp.rand_seed); sig.ksid.option_set(resp.ksid); if let Some(v) = resp.t512 { sig.ps_key_map = v.ps_key_map; sig.pt4_token_map = v.pt4_token_map; } oicq_codec .wt_session_ticket_key .option_set(resp.wt_session_ticket_key); sig.srm_token.option_set(resp.srm_token); sig.t133.option_set(resp.t133); sig.encrypted_a1.option_set(resp.encrypt_a1); sig.tgt.option_set(resp.tgt); sig.tgt_key.option_set(resp.tgt_key); sig.user_st_key.option_set(resp.user_st_key); sig.user_st_web_sig.option_set(resp.user_st_web_sig); sig.s_key.option_set(resp.s_key); sig.s_key_expired_time = resp.s_key_expired_time; sig.d2.option_set(resp.d2); sig.d2key.option_set(resp.d2key); sig.device_token.option_set(resp.device_token); if let Some(v) = resp.t402 { set_t402(&mut self.transport, v) } } fn process_need_captcha(&mut self, resp: &LoginNeedCaptcha) { self.transport.sig.t104.option_set(resp.t104.clone()); self.transport.sig.t547.option_set(resp.t547.clone()); } fn process_device_locked(&mut self, resp: &LoginDeviceLocked) { self.transport.sig.t104.option_set(resp.t104.clone()); self.transport.sig.t174.option_set(resp.t174.clone()); if let Some(v) = &resp.t402 { set_t402(&mut self.transport, v.clone()) } } fn process_device_lock_login(&mut self, resp: LoginDeviceLockLogin) { self.transport.sig.rand_seed.option_set(resp.rand_seed); self.transport.sig.t104.option_set(resp.t104); if let Some(v) = resp.t402 { set_t402(&mut self.transport, v) } } } fn set_t402(transport: &mut Transport, t402: Bytes) { transport.sig.dpwd = random_string(16).into(); transport.sig.t402 = t402; let mut v = BytesMut::new(); v.put_slice(&transport.sig.guid); v.put_slice(&transport.sig.dpwd); v.put_slice(&transport.sig.t402); transport.sig.g = Bytes::from(md5::compute(&v).to_vec()) } ================================================ FILE: ricq-guild/Cargo.toml ================================================ [package] name = "ricq-guild" version = "0.1.20" edition = "2021" description = "ricq-guild" license = "MPL-2.0" homepage = "https://github.com/lz1998/ricq" repository = "https://github.com/lz1998/ricq" readme = "README.md" keywords = ["qq", "protocol", "android", "mirai"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.htmlc [dependencies] bytes.workspace = true prost = { workspace = true, features = ["std"], default-features = false } prost-types.workspace = true tracing.workspace = true dynamic-protobuf.workspace = true rand.workspace = true ricq = { path = "../ricq" } ricq-core = { path = "../ricq-core" } tokio = { workspace = true, features = ["sync"] } [build-dependencies] prost-build = "0.9" ================================================ FILE: ricq-guild/README.md ================================================ # ricq-guild 频道支持 ================================================ FILE: ricq-guild/build.rs ================================================ use std::path::{Path, PathBuf}; fn recurse_dir(v: &mut Vec, dir: impl AsRef) { for entry in std::fs::read_dir(&dir).unwrap_or_else(|_| panic!("Unable to read dir: {:?}", dir.as_ref())) { let path = entry.expect("Unable to get direntry").path(); if path.is_dir() { recurse_dir(v, path); } else if let Some(true) = path.extension().map(|v| v == "proto") { v.push(path); } } } fn main() { let mut files = Vec::new(); recurse_dir(&mut files, "src/protocol/protobuf"); prost_build::Config::new() .extern_path(".msg", "::ricq_core::pb::msg") .compile_protos(&files, &["src/protocol/protobuf", "src/protocol/core"]) .expect("Cannot compile protobuf files"); } ================================================ FILE: ricq-guild/src/client/builder.rs ================================================ use crate::protocol::protobuf; use dynamic_protobuf::{dynamic_message, DynamicMessage}; use rand::Rng; use ricq_core::command::common::PbToBytes; use ricq_core::protocol::packet::Packet; use std::sync::atomic::Ordering; impl<'a> super::Engine<'a> { pub fn build_sync_channel_first_view_packet(&self) -> Packet { let req = protobuf::FirstViewReq { last_msg_time: Some(0), udc_flag: None, seq: Some(0), direct_message_flag: Some(1), }; let b = req.to_bytes(); self.uni_packet("trpc.group_pro.synclogic.SyncLogic.SyncFirstView", b) } pub fn build_get_user_profile_packet(&self, tiny_id: u64) -> Packet { let mut flags = DynamicMessage::new(); for i in 3..=29 { flags.set(i, 1u32) } flags.set(99, 1u32); flags.set(100, 1u32); let payload = { let msg = dynamic_message! { 1 => flags, 3 => tiny_id, 4 => 0u32, }; self.transport.encode_oidb_packet(0xf88, 1, msg.encode()) }; self.uni_packet("OidbSvcTrpcTcp.0xfc9_1", payload) } pub fn build_send_channel_message_packet( &self, elems: Vec, guild_id: u64, channel_id: u64, ) -> Packet { let routing = protobuf::ChannelRoutingHead { guild_id: Some(guild_id), channel_id: Some(channel_id), from_uin: Some(self.uin.load(Ordering::Relaxed) as _), from_tinyid: None, guild_code: None, from_appid: None, direct_message_flag: None, }; let mut rng = rand::thread_rng(); let random = rng.gen_range(0..i32::MAX); let content = protobuf::ChannelContentHead { r#type: Some(3840), sub_type: None, random: Some(random as _), seq: None, cnt_seq: None, time: None, meta: None, }; let msg_head = protobuf::ChannelMsgHead { routing_head: Some(routing), content_head: Some(content), }; let body = ricq_core::pb::msg::MessageBody { rich_text: Some(ricq_core::pb::msg::RichText { attr: None, elems, not_online_file: None, ptt: None, }), msg_content: None, msg_encrypt_content: None, }; let content = protobuf::ChannelMsgContent { head: Some(msg_head), ctrl_head: None, body: Some(body), ext_info: None, }; self.uni_packet( "MsgProxy.SendMsg", dynamic_message! { 1 => content.to_bytes(), } .encode(), ) } #[allow(clippy::too_many_arguments)] pub fn build_guild_image_store_packet( &self, channel_id: u64, guild_code: u64, file_name: String, md5: Vec, size: u64, width: u32, height: u32, image_type: u32, ) -> Packet { let req = ricq_core::pb::cmd0x388::D388ReqBody { net_type: Some(3), subcmd: Some(1), // TODO 支持多张图片? tryup_img_req: vec![ricq_core::pb::cmd0x388::TryUpImgReq { group_code: Some(channel_id), src_uin: Some(self.uin() as u64), file_id: Some(0), file_md5: Some(md5), file_size: Some(size), file_name: Some(file_name.into_bytes()), src_term: Some(5), platform_type: Some(9), bu_type: Some(211), pic_type: Some(image_type), pic_width: Some(width), pic_height: Some(height), build_ver: Some(self.transport.version.build_ver.as_bytes().to_vec()), app_pic_type: Some(1050), qqmeet_guild_id: Some(guild_code), qqmeet_channel_id: Some(channel_id), ..Default::default() }], command_id: Some(83), ..Default::default() }; self.uni_packet("ImgStore.QQMeetPicUp", req.to_bytes()) } } ================================================ FILE: ricq-guild/src/client/decoder.rs ================================================ use bytes::Bytes; use ricq_core::{RQError, RQResult}; use crate::protocol::protobuf::{self, FirstViewMsg, GuildUserProfile}; use crate::protocol::{FirstViewResponse, GuildImageStoreResp}; use crate::ricq_core::pb; use prost::Message; use ricq_core::common::RQAddr; pub struct Decoder; impl Decoder { pub fn decode_guild_first_view_response( &self, payload: Bytes, ) -> RQResult> { let rep = protobuf::FirstViewRsp::decode(&*payload)?; match rep { protobuf::FirstViewRsp { result: Some(r), err_msg: Some(err), .. } => Err(RQError::Decode(format!( "FirstViewRsp decode error: {}, {}", r, String::from_utf8_lossy(&err) ))), protobuf::FirstViewRsp { guild_count: Some(guild_count), self_tinyid: Some(self_tinyid), direct_message_switch: Some(direct_message_switch), direct_message_guild_count: Some(direct_message_guild_count), .. } => Ok(Some(FirstViewResponse { guild_count, self_tinyid, direct_message_switch, direct_message_guild_count, })), _ => Ok(None), } } pub fn decode_first_view_msg(&self, payload: Bytes) -> RQResult { let msg = FirstViewMsg::decode(&*payload)?; Ok(msg) } pub fn decode_guild_user_profile(&self, payload: Bytes) -> RQResult> { let pkg = pb::oidb::OidbssoPkg::decode(&*payload)?; let oidb = protobuf::ChannelOidb0xfc9Rsp::decode(&*pkg.bodybuffer)?; Ok(oidb.profile) } pub fn decode_guild_image_store_response( &self, payload: Bytes, ) -> RQResult { let mut rsp = pb::cmd0x388::D388RspBody::decode(&*payload)?; let rsp = rsp .tryup_img_rsp .pop() .ok_or(RQError::EmptyField("tryup_img_rsp"))?; if rsp.result() != 0 { return Err(RQError::Other( String::from_utf8_lossy(&rsp.fail_msg.unwrap_or_default()).into_owned(), )); } let download_index = rsp .download_index .ok_or(RQError::EmptyField("download_index"))?; Ok(if rsp.file_exit.unwrap_or_default() { GuildImageStoreResp::Exist { file_id: rsp.fileid.unwrap_or_default(), addrs: rsp .up_ip .into_iter() .zip(rsp.up_port) .map(|(ip, port)| RQAddr(ip, port as u16)) .collect(), download_index, } } else { GuildImageStoreResp::NotExist { file_id: rsp.fileid.unwrap_or_default(), upload_key: rsp.up_ukey.unwrap_or_default(), upload_addrs: rsp .up_ip .into_iter() .zip(rsp.up_port) .map(|(ip, port)| RQAddr(ip, port as u16)) .collect(), download_index, } }) } } ================================================ FILE: ricq-guild/src/client/mod.rs ================================================ use dynamic_protobuf::dynamic_message; use std::collections::HashMap; use std::ops::Deref; use std::sync::Arc; use tokio::sync::{broadcast, RwLockReadGuard}; use tokio::task::JoinHandle; use ricq::structs::ImageInfo; use ricq_core::highway::BdhInput; use ricq_core::msg::MessageChain; use ricq_core::protocol::packet::Packet; use ricq_core::{RQError, RQResult}; use crate::client::decoder::Decoder; use crate::protocol::protobuf::FirstViewMsg; use crate::protocol::{ protobuf, FirstView, FirstViewMessage, GuildImage, GuildImageStoreResp, GuildSelfProfile, }; pub mod builder; pub mod decoder; pub mod processor; #[allow(dead_code)] pub struct GuildClient { rq_client: Arc, listeners: HashMap<&'static str, broadcast::Receiver>, } impl GuildClient { pub async fn new(rq_client: &Arc) -> Self { let rq_client = rq_client.clone(); let listeners = HashMap::new(); Self { rq_client, listeners, } } pub async fn engine(&self) -> Engine<'_> { Engine::from_rq(self.rq_client.engine.read().await) } pub async fn fetch_guild_first_view(&self) -> RQResult> { let pkt = self.engine().await.build_sync_channel_first_view_packet(); let cli = self.rq_client.clone(); let first_view: JoinHandle> = tokio::spawn(async move { static COMMAND: &str = "trpc.group_pro.synclogic.SyncLogic.PushFirstView"; let mut rx = cli.listen_command(COMMAND).await; let r = rx.recv().await.unwrap(); let mut first_view: FirstViewMsg = Decoder.decode_first_view_msg(r.body)?; for _ in 0..2 { let r = rx.recv().await.unwrap(); let msg = Decoder.decode_first_view_msg(r.body)?; match msg { FirstViewMsg { push_flag, channel_msgs, get_msg_time, .. } if !channel_msgs.is_empty() => { first_view.push_flag = push_flag; first_view.channel_msgs = channel_msgs; first_view.get_msg_time = get_msg_time; } FirstViewMsg { direct_message_guild_nodes, .. } if !direct_message_guild_nodes.is_empty() => { first_view.direct_message_guild_nodes = direct_message_guild_nodes; } _ => {} } } Ok(first_view) }); let rsp = self.rq_client.send_and_wait(pkt).await?; let first_view_msg = first_view.await.unwrap()?; let first_view_rsp = Decoder.decode_guild_first_view_response(rsp.body)?; let opt = match (first_view_msg, first_view_rsp) { ( FirstViewMsg { push_flag: Some(push_flag), guild_nodes, channel_msgs, get_msg_time: Some(get_msg_time), direct_message_guild_nodes, .. }, Some(response), ) => { let message = FirstViewMessage { push_flag, guild_nodes, channel_msgs, get_msg_time, direct_message_guild_nodes, }; Some(FirstView { response, message }) } _ => None, }; Ok(opt) } pub async fn fetch_guild_self_profile( &self, tiny_id: u64, ) -> RQResult> { let pkt = self.engine().await.build_get_user_profile_packet(tiny_id); let rsp = self.rq_client.send_and_wait(pkt).await?; let usr = Decoder.decode_guild_user_profile(rsp.body)?; let prof = match usr { Some(protobuf::GuildUserProfile { tiny_id: Some(tiny_id), nickname: Some(nickname), avatar_url: Some(avatar_url), .. }) => Some(GuildSelfProfile { tiny_id, nickname, avatar_url, }), _ => None, }; Ok(prof) } pub async fn send_channel_message( &self, elems: MessageChain, guild_id: u64, channel_id: u64, ) -> RQResult { let pkt = self.engine().await.build_send_channel_message_packet( elems.into(), guild_id, channel_id, ); let ret = self.rq_client.send_and_wait(pkt).await?; Ok(ret) // todo: decode receipt } pub async fn upload_channel_image( &self, guild_id: u64, channel_id: u64, image: &[u8], ) -> RQResult { let info = ImageInfo::try_new(image)?; let image_store = self .get_guild_image_store(guild_id, channel_id, image) .await?; let fid; let dn_index; let server; match image_store { GuildImageStoreResp::Exist { file_id, mut addrs, download_index, } => { fid = file_id; dn_index = download_index; server = addrs.pop().ok_or(RQError::EmptyField("Address"))?; } GuildImageStoreResp::NotExist { file_id, upload_key, mut upload_addrs, download_index, } => { let addr = match self.rq_client.highway_addrs.read().await.first() { Some(addr) => *addr, None => upload_addrs .pop() .ok_or(RQError::EmptyField("upload_addrs"))?, }; self.rq_client .highway_upload_bdh( addr.into(), BdhInput { command_id: 83, ticket: upload_key, ext: dynamic_message! { 11 => guild_id, 12 => channel_id, } .encode() .to_vec(), encrypt: false, chunk_size: 256 * 1024, send_echo: true, }, image, ) .await?; fid = file_id; dn_index = download_index; server = addr; } }; let guild_image = GuildImage { file_id: fid, file_name: info.filename, size: info.size, width: info.width, height: info.height, image_type: info.image_type, download_index: dn_index, md5: info.md5, server_ip: server.0, server_port: server.1, }; Ok(guild_image) } pub async fn get_guild_image_store( &self, guild_id: u64, channel_id: u64, data: &[u8], ) -> RQResult { let image_info = ImageInfo::try_new(data)?; let req = self.engine().await.build_guild_image_store_packet( channel_id as _, guild_id, image_info.filename, image_info.md5, image_info.size as u64, image_info.width, image_info.height, image_info.image_type as u32, ); let resp = self.rq_client.send_and_wait(req).await?; Decoder.decode_guild_image_store_response(resp.body) } } pub struct Engine<'a>(RwLockReadGuard<'a, ricq_core::Engine>); impl<'a> Engine<'a> { fn from_rq(engine: RwLockReadGuard<'a, ricq_core::Engine>) -> Self { Self(engine) } } impl<'a> Deref for Engine<'a> { type Target = ricq_core::Engine; fn deref(&self) -> &Self::Target { &self.0 } } ================================================ FILE: ricq-guild/src/client/processor.rs ================================================ impl super::GuildClient {} ================================================ FILE: ricq-guild/src/lib.rs ================================================ pub mod client; pub mod protocol; extern crate ricq; extern crate ricq_core; ================================================ FILE: ricq-guild/src/protocol/core/cmd0x346/cmd0x346.proto ================================================ syntax = "proto3"; package cmd0x346; message ApplyCleanTrafficRsp { int32 retCode = 10; string retMsg = 20; } message ApplyCopyFromReq { int64 srcUin = 10; int64 srcGroup = 20; int32 srcSvcid = 30; bytes srcParentfolder = 40; bytes srcUuid = 50; bytes fileMd5 = 60; int64 dstUin = 70; int64 fileSize = 80; string fileName = 90; int32 dangerLevel = 100; int64 totalSpace = 110; } message ApplyCopyFromRsp { int32 retCode = 10; string retMsg = 20; bytes uuid = 30; int64 totalSpace = 40; } message ApplyCopyToReq { int64 dstId = 10; int64 dstUin = 20; int32 dstSvcid = 30; int64 srcUin = 40; int64 fileSize = 50; string fileName = 60; string localFilepath = 70; bytes uuid = 80; } message ApplyCopyToRsp { int32 retCode = 10; string retMsg = 20; string fileKey = 30; } message ApplyDownloadAbsReq { int64 uin = 10; bytes uuid = 20; } message ApplyDownloadAbsRsp { int32 retCode = 10; string retMsg = 20; DownloadInfo downloadInfo = 30; } message ApplyDownloadReq { int64 uin = 10; bytes uuid = 20; int32 ownerType = 30; int32 extIntype = 500; int32 need_https_url = 501; } message ApplyDownloadRsp { int32 retCode = 10; string retMsg = 20; DownloadInfo downloadInfo = 30; FileInfo fileInfo = 40; } message ApplyForwardFileReq { int64 senderUin = 10; int64 recverUin = 20; bytes uuid = 30; int32 dangerLevel = 40; int64 totalSpace = 50; } message ApplyForwardFileRsp { int32 retCode = 10; string retMsg = 20; int64 totalSpace = 30; int64 usedSpace = 40; bytes uuid = 50; } message ApplyGetTrafficReq { } message ApplyGetTrafficRsp { int32 retCode = 10; string retMsg = 20; int64 useFileSize = 30; int32 useFileNum = 40; int64 allFileSize = 50; int32 allFileNum = 60; } message ApplyListDownloadReq { int64 uin = 10; int32 beginIndex = 20; int32 reqCount = 30; } message ApplyListDownloadRsp { int32 retCode = 10; string retMsg = 20; int32 totalCount = 30; int32 beginIndex = 40; int32 rspCount = 50; int32 isEnd = 60; repeated FileInfo fileList = 70; } message ApplyUploadHitReq { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; string localFilepath = 60; int32 dangerLevel = 70; int64 totalSpace = 80; } message ApplyUploadHitReqV2 { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; bytes bytes_3sha = 60; bytes sha = 70; string localFilepath = 80; int32 dangerLevel = 90; int64 totalSpace = 100; } message ApplyUploadHitReqV3 { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; bytes sha = 60; string localFilepath = 70; int32 dangerLevel = 80; int64 totalSpace = 90; } message ApplyUploadHitRsp { int32 retCode = 10; string retMsg = 20; string uploadIp = 30; int32 uploadPort = 40; string uploadDomain = 50; bytes uuid = 60; bytes uploadKey = 70; int64 totalSpace = 80; int64 usedSpace = 90; } message ApplyUploadHitRspV2 { int32 retCode = 10; string retMsg = 20; string uploadIp = 30; int32 uploadPort = 40; string uploadDomain = 50; bytes uuid = 60; bytes uploadKey = 70; int64 totalSpace = 80; int64 usedSpace = 90; } message ApplyUploadHitRspV3 { int32 retCode = 10; string retMsg = 20; string uploadIp = 30; int32 uploadPort = 40; string uploadDomain = 50; bytes uuid = 60; bytes uploadKey = 70; int64 totalSpace = 80; int64 usedSpace = 90; } message ApplyUploadReq { int64 senderUin = 10; int64 recverUin = 20; int32 fileType = 30; int64 fileSize = 40; string fileName = 50; bytes bytes_10mMd5 = 60; string localFilepath = 70; int32 dangerLevel = 80; int64 totalSpace = 90; } message ApplyUploadReqV2 { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; bytes bytes_3sha = 60; string localFilepath = 70; int32 dangerLevel = 80; int64 totalSpace = 90; } message ApplyUploadReqV3 { int64 senderUin = 10; int64 recverUin = 20; int64 fileSize = 30; string fileName = 40; bytes bytes_10mMd5 = 50; bytes sha = 60; string localFilepath = 70; int32 dangerLevel = 80; int64 totalSpace = 90; } message ApplyUploadRsp { int32 retCode = 10; string retMsg = 20; int64 totalSpace = 30; int64 usedSpace = 40; int64 uploadedSize = 50; string uploadIp = 60; string uploadDomain = 70; int32 uploadPort = 80; bytes uuid = 90; bytes uploadKey = 100; bool boolFileExist = 110; int32 packSize = 120; repeated string uploadipList = 130; } message ApplyUploadRspV2 { int32 retCode = 10; string retMsg = 20; int64 totalSpace = 30; int64 usedSpace = 40; int64 uploadedSize = 50; string uploadIp = 60; string uploadDomain = 70; int32 uploadPort = 80; bytes uuid = 90; bytes uploadKey = 100; bool boolFileExist = 110; int32 packSize = 120; repeated string uploadipList = 130; int32 httpsvrApiVer = 140; bytes sha = 141; } message ApplyUploadRspV3 { int32 retCode = 10; string retMsg = 20; int64 totalSpace = 30; int64 usedSpace = 40; int64 uploadedSize = 50; string uploadIp = 60; string uploadDomain = 70; int32 uploadPort = 80; bytes uuid = 90; bytes uploadKey = 100; bool boolFileExist = 110; int32 packSize = 120; repeated string uploadIpList = 130; int32 uploadHttpsPort = 140; string uploadHttpsDomain = 150; string uploadDns = 160; string uploadLanip = 170; } message DelMessageReq { int64 uinSender = 1; int64 uinReceiver = 2; int32 time = 10; int32 random = 20; int32 seqNo = 30; } message DeleteFileReq { int64 uin = 10; int64 peerUin = 20; int32 deleteType = 30; bytes uuid = 40; } message DeleteFileRsp { int32 retCode = 10; string retMsg = 20; } message DownloadInfo { bytes downloadKey = 10; string downloadIp = 20; string downloadDomain = 30; int32 port = 40; string downloadUrl = 50; repeated string downloadipList = 60; string cookie = 70; } message DownloadSuccReq { int64 uin = 10; bytes uuid = 20; } message DownloadSuccRsp { int32 retCode = 10; string retMsg = 20; int32 downStat = 30; } message ExtensionReq { int64 id = 1; int64 type = 2; string dstPhonenum = 3; int32 phoneConvertType = 4; bytes sig = 20; int64 routeId = 100; DelMessageReq delMessageReq = 90100; int32 downloadUrlType = 90200; int32 pttFormat = 90300; int32 isNeedInnerIp = 90400; int32 netType = 90500; int32 voiceType = 90600; int32 fileType = 90700; int32 pttTime = 90800; } message ExtensionRsp { } message FileInfo { int64 uin = 1; int32 dangerEvel = 2; int64 fileSize = 3; int32 lifeTime = 4; int32 uploadTime = 5; bytes uuid = 6; string fileName = 7; int32 absFileType = 90; bytes bytes_10mMd5 = 100; bytes sha = 101; int32 clientType = 110; int64 ownerUin = 120; int64 peerUin = 121; int32 expireTime = 130; } message FileQueryReq { int64 uin = 10; bytes uuid = 20; } message FileQueryRsp { int32 retCode = 10; string retMsg = 20; FileInfo fileInfo = 30; } message RecallFileReq { int64 uin = 1; bytes uuid = 2; } message RecallFileRsp { int32 retCode = 1; string retMsg = 2; } message RecvListQueryReq { int64 uin = 1; int32 beginIndex = 2; int32 reqCount = 3; } message RecvListQueryRsp { int32 retCode = 1; string retMsg = 2; int32 fileTotCount = 3; int32 beginIndex = 4; int32 rspFileCount = 5; int32 isEnd = 6; repeated FileInfo fileList = 7; } message RenewFileReq { int64 uin = 1; bytes uuid = 2; int32 addTtl = 3; } message RenewFileRsp { int32 retCode = 1; string retMsg = 2; } message C346ReqBody { int32 cmd = 1; int32 seq = 2; RecvListQueryReq recvListQueryReq = 3; SendListQueryReq sendListQueryReq = 4; RenewFileReq renewFileReq = 5; RecallFileReq recallFileReq = 6; ApplyUploadReq applyUploadReq = 7; ApplyUploadHitReq applyUploadHitReq = 8; ApplyForwardFileReq applyForwardFileReq = 9; UploadSuccReq uploadSuccReq = 10; DeleteFileReq deleteFileReq = 11; DownloadSuccReq downloadSuccReq = 12; ApplyDownloadAbsReq applyDownloadAbsReq = 13; ApplyDownloadReq applyDownloadReq = 14; ApplyListDownloadReq applyListDownloadReq = 15; FileQueryReq fileQueryReq = 16; ApplyCopyFromReq applyCopyFromReq = 17; ApplyUploadReqV2 applyUploadReqV2 = 18; ApplyUploadReqV3 applyUploadReqV3 = 19; ApplyUploadHitReqV2 applyUploadHitReqV2 = 20; ApplyUploadHitReqV3 applyUploadHitReqV3 = 21; int32 businessId = 101; int32 clientType = 102; ApplyCopyToReq applyCopyToReq = 90000; //ApplyCleanTrafficReq applyCleanTrafficReq = 90001; empty message ApplyGetTrafficReq applyGetTrafficReq = 90002; ExtensionReq extensionReq = 99999; } message C346RspBody { int32 cmd = 1; int32 seq = 2; RecvListQueryRsp recvListQueryRsp = 3; SendListQueryRsp sendListQueryRsp = 4; RenewFileRsp renewFileRsp = 5; RecallFileRsp recallFileRsp = 6; ApplyUploadRsp applyUploadRsp = 7; ApplyUploadHitRsp applyUploadHitRsp = 8; ApplyForwardFileRsp applyForwardFileRsp = 9; UploadSuccRsp uploadSuccRsp = 10; DeleteFileRsp deleteFileRsp = 11; DownloadSuccRsp downloadSuccRsp = 12; ApplyDownloadAbsRsp applyDownloadAbsRsp = 13; ApplyDownloadRsp applyDownloadRsp = 14; ApplyListDownloadRsp applyListDownloadRsp = 15; FileQueryRsp fileQueryRsp = 16; ApplyCopyFromRsp applyCopyFromRsp = 17; ApplyUploadRspV2 applyUploadRspV2 = 18; ApplyUploadRspV3 applyUploadRspV3 = 19; ApplyUploadHitRspV2 applyUploadHitRspV2 = 20; ApplyUploadHitRspV3 applyUploadHitRspV3 = 21; int32 businessId = 101; int32 clientType = 102; ApplyCopyToRsp applyCopyToRsp = 90000; ApplyCleanTrafficRsp applyCleanTrafficRsp = 90001; ApplyGetTrafficRsp applyGetTrafficRsp = 90002; ExtensionRsp extensionRsp = 99999; } message SendListQueryReq { int64 uin = 1; int32 beginIndex = 2; int32 reqCount = 3; } message SendListQueryRsp { int32 retCode = 1; string retMsg = 2; int32 fileTotCount = 3; int32 beginIndex = 4; int32 rspFileCount = 5; int32 isEnd = 6; int64 totLimit = 7; int64 usedLimit = 8; repeated FileInfo fileList = 9; } message UploadSuccReq { int64 senderUin = 10; int64 recverUin = 20; bytes uuid = 30; } message UploadSuccRsp { int32 retCode = 10; string retMsg = 20; FileInfo fileInfo = 30; } ================================================ FILE: ricq-guild/src/protocol/core/cmd0x352/cmd0x352.proto ================================================ syntax = "proto2"; package cmd0x352; /* message DelImgReq { optional uint64 srcUin = 1; optional uint64 dstUin = 2; optional uint32 reqTerm = 3; optional uint32 reqPlatformType = 4; optional uint32 buType = 5; optional bytes buildVer = 6; optional bytes fileResid = 7; optional uint32 picWidth = 8; optional uint32 picHeight = 9; } message DelImgRsp { optional uint32 result = 1; optional bytes failMsg = 2; optional bytes fileResid = 3; } message GetImgUrlReq { optional uint64 srcUin = 1; optional uint64 dstUin = 2; optional bytes fileResid = 3; optional uint32 urlFlag = 4; optional uint32 urlType = 6; optional uint32 reqTerm = 7; optional uint32 reqPlatformType = 8; optional uint32 srcFileType = 9; optional uint32 innerIp = 10; optional bool addressBook = 11; optional uint32 buType = 12; optional bytes buildVer = 13; optional uint32 picUpTimestamp = 14; optional uint32 reqTransferType = 15; } message GetImgUrlRsp { optional bytes fileResid = 1; optional uint32 clientIp = 2; optional uint32 result = 3; optional bytes failMsg = 4; repeated bytes thumbDownUrl = 5; repeated bytes originalDownUrl = 6; optional ImgInfo imgInfo = 7; repeated uint32 downIp = 8; repeated uint32 downPort = 9; optional bytes thumbDownPara = 10; optional bytes originalDownPara = 11; optional bytes downDomain = 12; repeated bytes bigDownUrl = 13; optional bytes bigDownPara = 14; optional bytes bigThumbDownPara = 15; optional uint32 httpsUrlFlag = 16; repeated IPv6Info downIp6 = 26; optional bytes clientIp6 = 27; } message IPv6Info { optional bytes ip6 = 1; optional uint32 port = 2; } */ message ReqBody { optional uint32 subcmd = 1; repeated D352TryUpImgReq tryupImgReq = 2; // repeated GetImgUrlReq getimgUrlReq = 3; // repeated DelImgReq delImgReq = 4; optional uint32 netType = 10; } message RspBody { optional uint32 subcmd = 1; repeated TryUpImgRsp tryupImgRsp = 2; // repeated GetImgUrlRsp getimgUrlRsp = 3; optional bool newBigchan = 4; // repeated DelImgRsp delImgRsp = 5; optional bytes failMsg = 10; } message D352TryUpImgReq { optional uint64 srcUin = 1; optional uint64 dstUin = 2; optional uint64 fileId = 3; optional bytes fileMd5 = 4; optional uint64 fileSize = 5; optional bytes fileName = 6; optional uint32 srcTerm = 7; optional uint32 platformType = 8; optional uint32 innerIp = 9; optional bool addressBook = 10; optional uint32 retry = 11; optional uint32 buType = 12; optional bool picOriginal = 13; optional uint32 picWidth = 14; optional uint32 picHeight = 15; optional uint32 picType = 16; optional bytes buildVer = 17; optional bytes fileIndex = 18; optional uint32 storeDays = 19; optional uint32 tryupStepflag = 20; optional bool rejectTryfast = 21; optional uint32 srvUpload = 22; optional bytes transferUrl = 23; } message TryUpImgRsp { optional uint64 fileId = 1; optional uint32 clientIp = 2; optional uint32 result = 3; optional bytes failMsg = 4; optional bool fileExit = 5; // optional ImgInfo imgInfo = 6; repeated uint32 upIp = 7; repeated uint32 upPort = 8; optional bytes upUkey = 9; optional string upResid = 10; optional bytes upUuid = 11; optional uint64 upOffset = 12; optional uint64 blockSize = 13; optional bytes encryptDstip = 14; optional uint32 roamdays = 15; // repeated IPv6Info upIp6 = 26; optional bytes clientIp6 = 27; optional bytes thumbDownPara = 60; optional bytes originalDownPara = 61; optional bytes downDomain = 62; optional bytes bigDownPara = 64; optional bytes bigThumbDownPara = 65; optional uint32 httpsUrlFlag = 66; // optional TryUpInfo4Busi info4Busi = 1001; } /* message TryUpInfo4Busi { optional bytes fileResid = 1; optional bytes downDomain = 2; optional bytes thumbDownUrl = 3; optional bytes originalDownUrl = 4; optional bytes bigDownUrl = 5; } */ ================================================ FILE: ricq-guild/src/protocol/core/cmd0x388/cmd0x388.proto ================================================ syntax = "proto2"; package cmd0x388; message DelImgReq { optional uint64 srcUin = 1; optional uint64 dstUin = 2; optional uint32 reqTerm = 3; optional uint32 reqPlatformType = 4; optional uint32 buType = 5; optional bytes buildVer = 6; optional bytes fileResid = 7; optional uint32 picWidth = 8; optional uint32 picHeight = 9; } message DelImgRsp { optional uint32 result = 1; optional bytes failMsg = 2; optional bytes fileResid = 3; } message ExpRoamExtendInfo { optional bytes resid = 1; } message ExpRoamPicInfo { optional uint32 shopFlag = 1; optional uint32 pkgId = 2; optional bytes picId = 3; } message ExtensionCommPicTryUp { repeated bytes extinfo = 1; } message ExtensionExpRoamTryUp { repeated ExpRoamPicInfo exproamPicInfo = 1; } message GetImgUrlReq { optional uint64 groupCode = 1; optional uint64 dstUin = 2; optional uint64 fileid = 3; optional bytes fileMd5 = 4; optional uint32 urlFlag = 5; optional uint32 urlType = 6; optional uint32 reqTerm = 7; optional uint32 reqPlatformType = 8; optional uint32 innerIp = 9; optional uint32 buType = 10; optional bytes buildVer = 11; optional uint64 fileId = 12; optional uint64 fileSize = 13; optional uint32 originalPic = 14; optional uint32 retryReq = 15; optional uint32 fileHeight = 16; optional uint32 fileWidth = 17; optional uint32 picType = 18; optional uint32 picUpTimestamp = 19; optional uint32 reqTransferType = 20; optional uint64 qqmeetGuildId = 21; optional uint64 qqmeetChannelId = 22; optional bytes downloadIndex = 23; } message GetImgUrlRsp { optional uint64 fileid = 1; optional bytes fileMd5 = 2; optional uint32 result = 3; optional bytes failMsg = 4; optional ImgInfo imgInfo = 5; repeated bytes thumbDownUrl = 6; repeated bytes originalDownUrl = 7; repeated bytes bigDownUrl = 8; repeated uint32 downIp = 9; repeated uint32 downPort = 10; optional bytes downDomain = 11; optional bytes thumbDownPara = 12; optional bytes originalDownPara = 13; optional bytes bigDownPara = 14; optional uint64 fileId = 15; optional uint32 autoDownType = 16; repeated uint32 orderDownType = 17; optional bytes bigThumbDownPara = 19; optional uint32 httpsUrlFlag = 20; repeated IPv6Info downIp6 = 26; optional bytes clientIp6 = 27; } message GetPttUrlReq { optional uint64 groupCode = 1; optional uint64 dstUin = 2; optional uint64 fileid = 3; optional bytes fileMd5 = 4; optional uint32 reqTerm = 5; optional uint32 reqPlatformType = 6; optional uint32 innerIp = 7; optional uint32 buType = 8; optional bytes buildVer = 9; optional uint64 fileId = 10; optional bytes fileKey = 11; optional uint32 codec = 12; optional uint32 buId = 13; optional uint32 reqTransferType = 14; optional uint32 isAuto = 15; } message GetPttUrlRsp { optional uint64 fileid = 1; optional bytes fileMd5 = 2; optional uint32 result = 3; optional bytes failMsg = 4; repeated bytes downUrl = 5; repeated uint32 downIp = 6; repeated uint32 downPort = 7; optional bytes downDomain = 8; optional bytes downPara = 9; optional uint64 fileId = 10; optional uint32 transferType = 11; optional uint32 allowRetry = 12; repeated IPv6Info downIp6 = 26; optional bytes clientIp6 = 27; optional string domain = 28; } message IPv6Info { optional bytes ip6 = 1; optional uint32 port = 2; } message ImgInfo { optional bytes fileMd5 = 1; optional uint32 fileType = 2; optional uint64 fileSize = 3; optional uint32 fileWidth = 4; optional uint32 fileHeight = 5; } message PicSize { optional uint32 original = 1; optional uint32 thumb = 2; optional uint32 high = 3; } message D388ReqBody { optional uint32 netType = 1; optional uint32 subcmd = 2; repeated TryUpImgReq tryupImgReq = 3; repeated GetImgUrlReq getimgUrlReq = 4; repeated TryUpPttReq tryupPttReq = 5; repeated GetPttUrlReq getpttUrlReq = 6; optional uint32 commandId = 7; repeated DelImgReq delImgReq = 8; optional bytes extension = 1001; } message D388RspBody { optional uint32 clientIp = 1; optional uint32 subcmd = 2; repeated D388TryUpImgRsp tryupImgRsp = 3; repeated GetImgUrlRsp getimgUrlRsp = 4; repeated TryUpPttRsp tryupPttRsp = 5; repeated GetPttUrlRsp getpttUrlRsp = 6; repeated DelImgRsp delImgRsp = 7; } message TryUpImgReq { optional uint64 groupCode = 1; optional uint64 srcUin = 2; optional uint64 fileId = 3; optional bytes fileMd5 = 4; optional uint64 fileSize = 5; optional bytes fileName = 6; optional uint32 srcTerm = 7; optional uint32 platformType = 8; optional uint32 buType = 9; optional uint32 picWidth = 10; optional uint32 picHeight = 11; optional uint32 picType = 12; optional bytes buildVer = 13; optional uint32 innerIp = 14; optional uint32 appPicType = 15; optional uint32 originalPic = 16; optional bytes fileIndex = 17; optional uint64 dstUin = 18; optional uint32 srvUpload = 19; optional bytes transferUrl = 20; optional uint64 qqmeetGuildId = 21; optional uint64 qqmeetChannelId = 22; } message D388TryUpImgRsp { optional uint64 fileId = 1; optional uint32 result = 2; optional bytes failMsg = 3; optional bool fileExit = 4; optional ImgInfo imgInfo = 5; repeated uint32 upIp = 6; repeated uint32 upPort = 7; optional bytes upUkey = 8; optional uint64 fileid = 9; optional uint64 upOffset = 10; optional uint64 blockSize = 11; optional bool newBigChan = 12; repeated IPv6Info upIp6 = 26; optional bytes clientIp6 = 27; optional bytes downloadIndex = 28; optional TryUpInfo4Busi info4Busi = 1001; } message TryUpInfo4Busi { optional bytes downDomain = 1; optional bytes thumbDownUrl = 2; optional bytes originalDownUrl = 3; optional bytes bigDownUrl = 4; optional bytes fileResid = 5; } message TryUpPttReq { optional uint64 groupCode = 1; optional uint64 srcUin = 2; optional uint64 fileId = 3; optional bytes fileMd5 = 4; optional uint64 fileSize = 5; optional bytes fileName = 6; optional uint32 srcTerm = 7; optional uint32 platformType = 8; optional uint32 buType = 9; optional bytes buildVer = 10; optional uint32 innerIp = 11; optional uint32 voiceLength = 12; optional bool newUpChan = 13; optional uint32 codec = 14; optional uint32 voiceType = 15; optional uint32 buId = 16; } message TryUpPttRsp { optional uint64 fileId = 1; optional uint32 result = 2; optional bytes failMsg = 3; optional bool fileExit = 4; repeated uint32 upIp = 5; repeated uint32 upPort = 6; optional bytes upUkey = 7; optional uint64 fileid = 8; optional uint64 upOffset = 9; optional uint64 blockSize = 10; optional bytes fileKey = 11; optional uint32 channelType = 12; repeated IPv6Info upIp6 = 26; optional bytes clientIp6 = 27; } ================================================ FILE: ricq-guild/src/protocol/core/cmd0x3bb/cmd0x3bb.proto ================================================ syntax = "proto2"; package cmd0x3bb; message AnonyMsg { optional uint32 cmd = 1; optional C3BBReqBody anonyReq = 10; optional C3BBRspBody anonyRsp = 11; } message AnonyStatus { optional uint32 forbidTalking = 1; optional bytes errMsg = 10; } message C3BBReqBody { optional uint64 uin = 1; optional uint64 groupCode = 2; } message C3BBRspBody { optional int32 ret = 1; optional uint64 groupCode = 2; optional bytes anonyName = 3; optional uint32 portraitIndex = 4; optional uint32 bubbleIndex = 5; optional uint32 expiredTime = 6; optional AnonyStatus anonyStatus = 10; optional string color = 15; } ================================================ FILE: ricq-guild/src/protocol/core/cmd0x6ff/smbcmd0x519.proto ================================================ syntax = "proto2"; package cmd0x6ff; message C519CRMMsgHead { optional uint32 crmSubCmd = 1; optional uint32 headLen = 2; optional uint32 verNo = 3; optional uint64 kfUin = 4; optional uint32 seq = 5; optional uint32 packNum = 6; optional uint32 curPack = 7; optional string bufSig = 8; optional uint64 pubQq = 9; optional uint32 clienttype = 10; optional uint64 laborUin = 11; optional string laborName = 12; optional uint64 puin = 13; } message GetNavigationMenuReqBody { optional uint64 puin = 1; optional uint64 uin = 2; optional uint32 verNo = 3; } message GetNavigationMenuRspBody { optional C519RetInfo ret = 1; optional int32 isShow = 2; optional string uctMsg = 3; optional uint32 verNo = 4; } message C519ReqBody { optional uint32 subCmd = 1; optional C519CRMMsgHead crmCommonHead = 2; optional GetAddressDetailListReqBody getAddressDetailListReqBody = 33; optional GetNavigationMenuReqBody getNavigationMenuReq = 35; } message C519RetInfo { optional uint32 retCode = 1; optional string errorMsg = 2; } message C519RspBody { optional uint32 subCmd = 1; optional C519CRMMsgHead crmCommonHead = 2; optional GetAddressDetailListRspBody getAddressDetailListRspBody = 33; optional GetNavigationMenuRspBody getNavigationMenuRsp = 35; } message GetAddressDetailListReqBody { optional fixed32 timestamp = 1; optional fixed64 timestamp2 = 2; } message GetAddressDetailListRspBody { optional C519RetInfo ret = 1; optional fixed32 timestamp = 2; optional bool full = 3; repeated AddressDetail addressDetail = 4; optional fixed64 timestamp2 = 5; } message AddressDetail { optional uint32 aid = 1; optional fixed32 modifyTime = 2; optional fixed32 createTime = 3; optional uint32 status = 4; optional uint32 groupid = 5; optional bytes addGroupName = 6; optional bytes name = 7; optional uint32 gender = 8; optional fixed32 birthday = 9; optional bytes company0 = 10; optional bytes companyPosition0 = 11; optional bytes company1 = 12; optional bytes companyPosition1 = 13; optional bytes fixedPhone0 = 14; optional bytes fixedPhone1 = 15; optional bytes email0 = 16; optional bytes email1 = 17; optional bytes fax0 = 18; optional bytes fax1 = 19; optional bytes comment = 20; optional bytes headUrl = 21; repeated AddressMobileInfo mobilePhone = 22; optional bool mobilePhoneUpdated = 23; repeated AddressQQinfo qq = 24; optional bool qqPhoneUpdated = 25; optional fixed64 modifyTime2 = 26; optional NewBizClientRegion clientRegion = 27; optional NewBizClientRegionCode clientRegionCode = 28; } message AddressMobileInfo { optional uint32 index = 1; optional bytes account = 2; optional bytes formattedAccount = 5; } message AddressQQinfo { optional uint32 index = 1; optional uint64 account = 2; } message NewBizClientRegion { optional string clientNation = 1; optional string clientProvince = 2; optional string clientCity = 3; optional string clientRegion = 4; } message NewBizClientRegionCode { optional uint64 nationid = 1; optional uint64 provinceid = 2; optional uint64 cityid = 3; optional uint64 regionid = 4; } ================================================ FILE: ricq-guild/src/protocol/core/cmd0x6ff/subcmd0x501.proto ================================================ syntax = "proto2"; package cmd0x6ff; message C501ReqBody { optional SubCmd0x501ReqBody ReqBody = 1281; } message C501RspBody { optional SubCmd0x501RspBody RspBody = 1281; } message SubCmd0x501ReqBody { optional uint64 uin = 1; optional uint32 idcId = 2; optional uint32 appid = 3; optional uint32 loginSigType = 4; optional bytes loginSigTicket = 5; optional uint32 requestFlag = 6; repeated uint32 serviceTypes = 7; optional uint32 bid = 8; } message SubCmd0x501RspBody { optional bytes sigSession = 1; optional bytes sessionKey = 2; repeated SrvAddrs addrs = 3; } message SrvAddrs { optional uint32 serviceType = 1; repeated IpAddr addrs = 2; } message IpAddr { optional uint32 type = 1; optional fixed32 ip = 2; optional uint32 port = 3; optional uint32 area = 4; } ================================================ FILE: ricq-guild/src/protocol/core/cmd0x899/cmd0x899.proto ================================================ syntax = "proto2"; package cmd0x899; message ReqBody { optional uint64 groupCode = 1; optional uint64 startUin = 2; optional uint32 identifyFlag = 3; repeated uint64 uinList = 4; optional memberlist memberlistOpt = 5; optional uint32 memberNum = 6; optional uint32 filterMethod = 7; optional uint32 onlineFlag = 8; } message RspBody { optional uint64 groupCode = 1; optional uint64 startUin = 2; optional uint32 identifyFlag = 3; repeated memberlist memberlist = 4; optional bytes errorinfo = 5; } message memberlist { optional uint64 memberUin = 1; optional uint32 uinFlag = 2; optional uint32 uinFlagex = 3; optional uint32 uinMobileFlag = 4; optional uint32 uinArchFlag = 5; optional uint32 joinTime = 6; optional uint32 oldMsgSeq = 7; optional uint32 newMsgSeq = 8; optional uint32 lastSpeakTime = 9; optional uint32 level = 10; optional uint32 point = 11; optional uint32 shutupTimestap = 12; optional uint32 flagex2 = 13; optional bytes specialTitle = 14; optional uint32 specialTitleExpireTime = 15; optional uint32 activeDay = 16; optional bytes uinKey = 17; optional uint32 privilege = 18; optional bytes richInfo = 19; } message uin_key { optional uint64 groupCode = 1; optional uint64 memberUin = 2; optional uint64 genTime = 3; optional uint32 validTime = 4; optional uint32 randNum = 5; } ================================================ FILE: ricq-guild/src/protocol/core/data.proto ================================================ syntax = "proto3"; package pb; message DeviceInfo { string bootloader = 1; string procVersion = 2; string codename = 3; string incremental = 4; string fingerprint = 5; string bootId = 6; string androidId = 7; string baseBand = 8; string innerVersion = 9; } message RequestBody { repeated ConfigSeq rpt_config_list = 1; } message ConfigSeq { int32 type = 1; int32 version = 2; } message D50ReqBody { int64 appid = 1; int32 maxPkgSize = 2; int32 startTime = 3; int32 startIndex = 4; int32 reqNum = 5; repeated int64 uinList = 6; int32 reqMusicSwitch = 91001; int32 reqMutualmarkAlienation = 101001; int32 reqMutualmarkScore = 141001; int32 reqKsingSwitch = 151001; int32 reqMutualmarkLbsshare = 181001; } message D388ReqBody { int32 netType = 1; int32 subcmd = 2; repeated TryUpImgReq msgTryUpImgReq = 3; repeated TryUpPttReq msgTryUpPttReq = 5; repeated GetPttUrlReq msgGetPttReq = 6; int32 commandId = 7; bytes extension = 1001; } message D388RespBody { int32 clientIp = 1; int32 subCmd = 2; repeated TryUpImgResp msgTryUpImgRsp = 3; repeated TryUpPttResp msgTryUpPttRsp = 5; repeated GetPttUrlRsp msgGetPttUrlRsp = 6; } message GetPttUrlReq { int64 groupCode = 1; int64 dstUin = 2; int64 fileId = 3; bytes fileMd5 = 4; int32 reqTerm = 5; int32 reqPlatformType = 6; int32 innerIp = 7; int32 buType = 8; bytes buildVer = 9; //int64 fileId = 10; bytes fileKey = 11; int32 codec = 12; int32 buId = 13; int32 reqTransferType = 14; int32 isAuto = 15; } message GetPttUrlRsp { int64 fileId = 1; bytes fileMd5 = 2; int32 result = 3; bytes failMsg = 4; bytes bytesDownUrl = 5; repeated int32 uint32DownIp = 6; repeated int32 uint32DownPort = 7; bytes downDomain = 8; bytes downPara = 9; //int64 fileId = 10; int32 transferType = 11; int32 allowRetry = 12; //repeated IPv6Info msgDownIp6 = 26; bytes clientIp6 = 27; string strDomain = 28; } message ReqDataHighwayHead { DataHighwayHead msgBasehead = 1; SegHead msgSeghead = 2; bytes reqExtendinfo = 3; int64 timestamp = 4; //LoginSigHead? msgLoginSigHead = 5; } message RspDataHighwayHead { DataHighwayHead msgBasehead = 1; SegHead msgSeghead = 2; int32 errorCode = 3; int32 allowRetry = 4; int32 cachecost = 5; int32 htcost = 6; bytes rspExtendinfo = 7; int64 timestamp = 8; int64 range = 9; int32 isReset = 10; } message DataHighwayHead { int32 version = 1; string uin = 2; string command = 3; int32 seq = 4; int32 retryTimes = 5; int32 appid = 6; int32 dataflag = 7; int32 commandId = 8; string buildVer = 9; int32 localeId = 10; } message SegHead { int32 serviceid = 1; int64 filesize = 2; int64 dataoffset = 3; int32 datalength = 4; int32 rtcode = 5; bytes serviceticket = 6; int32 flag = 7; bytes md5 = 8; bytes fileMd5 = 9; int32 cacheAddr = 10; int32 queryTimes = 11; int32 updateCacheip = 12; } message TryUpImgReq { int64 groupCode = 1; int64 srcUin = 2; int64 fileId = 3; bytes fileMd5 = 4; int64 fileSize = 5; string fileName = 6; int32 srcTerm = 7; int32 platformType = 8; int32 buType = 9; int32 picWidth = 10; int32 picHeight = 11; int32 picType = 12; string buildVer = 13; int32 innerIp = 14; int32 appPicType = 15; int32 originalPic = 16; bytes fileIndex = 17; int64 dstUin = 18; int32 srvUpload = 19; bytes transferUrl = 20; } message TryUpImgResp { int64 fileId = 1; int32 result = 2; string failMsg = 3; bool boolFileExit = 4; ImgInfo msgImgInfo = 5; repeated uint32 uint32UpIp = 6; repeated uint32 uint32UpPort = 7; bytes upUkey = 8; int64 fid = 9; } message TryUpPttReq { int64 groupCode = 1; int64 srcUin = 2; int64 fileId = 3; bytes fileMd5 = 4; int64 fileSize = 5; bytes fileName = 6; int32 srcTerm = 7; int32 platformType = 8; int32 buType = 9; string buildVer = 10; int32 innerIp = 11; int32 voiceLength = 12; bool boolNewUpChan = 13; int32 codec = 14; int32 voiceType = 15; int32 buId = 16; } message TryUpPttResp { int64 fileId = 1; int32 result = 2; string failMsg = 3; bool boolFileExit = 4; repeated int32 uint32UpIp = 5; repeated int32 uint32UpPort = 6; bytes upUkey = 7; int64 fileId2 = 8; int64 upOffset = 9; int64 blockSize = 10; bytes fileKey = 11; int32 channelType = 12; // List? msgUpIp6 = 26; // bytes clientIp6 = 27; } message ImgInfo { bytes fileMd5 = 1; int32 fileType = 2; int64 fileSize = 3; int32 fileWidth = 4; int32 fileHeight = 5; } message DeleteMessageRequest { repeated MessageItem items = 1; } message MessageItem { int64 fromUin = 1; int64 toUin = 2; int32 msgType = 3; int32 msgSeq = 4; int64 msgUid = 5; bytes sig = 7; } message SubD4 { int64 uin = 1; } message Sub8A { repeated Sub8AMsgInfo msg_info = 1; int32 appId = 2; int32 instId = 3; int32 longMessageFlag = 4; bytes reserved = 5; } message Sub8AMsgInfo { int64 fromUin = 1; int64 toUin = 2; int32 msgSeq = 3; int64 msgUid = 4; int64 msgTime = 5; int32 msgRandom = 6; int32 pkgNum = 7; int32 pkgIndex = 8; int32 devSeq = 9; } message SubB3 { int32 type = 1; SubB3AddFrdNotify msgAddFrdNotify = 2; } message SubB3AddFrdNotify { int64 uin = 1; string nick = 5; } message Sub44 { Sub44FriendSyncMsg friendSyncMsg = 1; Sub44GroupSyncMsg groupSyncMsg = 2; } message Sub44FriendSyncMsg { int64 uin = 1; int64 fUin = 2; int32 processType = 3; int32 time = 4; int32 processFlag = 5; int32 sourceId = 6; int32 sourceSubId = 7; repeated string strWording = 8; } message Sub44GroupSyncMsg { int32 msgType = 1; int64 msgSeq = 2; int64 grpCode = 3; int64 gaCode = 4; int64 optUin1 = 5; int64 optUin2 = 6; bytes msgBuf = 7; bytes authKey = 8; int32 msgStatus = 9; int64 actionUin = 10; int64 actionTime = 11; int32 curMaxMemCount = 12; int32 nextMaxMemCount = 13; int32 curMemCount = 14; int32 reqSrcId = 15; int32 reqSrcSubId = 16; int32 inviterRole = 17; int32 extAdminNum = 18; int32 processFlag = 19; } message GroupMemberReqBody { int64 groupCode = 1; int64 uin = 2; bool newClient = 3; int32 clientType = 4; int32 richCardNameVer = 5; } message GroupMemberRspBody { int64 groupCode = 1; int32 selfRole = 2; GroupMemberInfo memInfo = 3; bool boolSelfLocationShared = 4; int32 groupType = 5; } message GroupMemberInfo { int64 uin = 1; int32 result = 2; bytes errmsg = 3; bool IsFriend = 4; bytes remark = 5; bool IsConcerned = 6; int32 credit = 7; bytes card = 8; int32 sex = 9; bytes location = 10; bytes nick = 11; int32 age = 12; bytes lev = 13; int64 join = 14; int64 lastSpeak = 15; //repeated CustomEntry customEnties = 16; //repeated GBarInfo gbarConcerned = 17; bytes gbarTitle = 18; bytes gbarUrl = 19; int32 gbarCnt = 20; bool isAllowModCard = 21; bool isVip = 22; bool isYearVip = 23; bool isSuperVip = 24; bool isSuperQq = 25; int32 vipLev = 26; int32 role = 27; bool locationShared = 28; int64 int64Distance = 29; int32 concernType = 30; bytes specialTitle = 31; int32 specialTitleExpireTime = 32; //FlowersEntry flowerEntry = 33; //TeamEntry teamEntry = 34; bytes phoneNum = 35; bytes job = 36; int32 medalId = 37; int32 level = 39; string honor = 41; } ================================================ FILE: ricq-guild/src/protocol/core/longmsg/longmsg.proto ================================================ syntax = "proto3"; package longmsg; message LongMsgDeleteReq { bytes msgResid = 1; int32 msgType = 2; } message LongMsgDeleteRsp { int32 result = 1; bytes msgResid = 2; } message LongMsgDownReq { int32 srcUin = 1; bytes msgResid = 2; int32 msgType = 3; int32 needCache = 4; } message LongMsgDownRsp { int32 result = 1; bytes msgResid = 2; bytes msgContent = 3; } message LongMsgUpReq { int32 msgType = 1; int64 dstUin = 2; int32 msgId = 3; bytes msgContent = 4; int32 storeType = 5; bytes msgUkey = 6; int32 needCache = 7; } message LongMsgUpRsp { int32 result = 1; int32 msgId = 2; bytes msgResid = 3; } message LongReqBody { int32 subcmd = 1; int32 termType = 2; int32 platformType = 3; repeated LongMsgUpReq msgUpReq = 4; repeated LongMsgDownReq msgDownReq = 5; repeated LongMsgDeleteReq msgDelReq = 6; int32 agentType = 10; } message LongRspBody { int32 subcmd = 1; repeated LongMsgUpRsp msgUpRsp = 2; repeated LongMsgDownRsp msgDownRsp = 3; repeated LongMsgDeleteRsp msgDelRsp = 4; } ================================================ FILE: ricq-guild/src/protocol/core/msf/register_proxy.proto ================================================ syntax = "proto2"; package msf; message DiscussList { optional uint64 discussCode = 1; optional uint64 discussSeq = 2; optional uint64 memberSeq = 3; optional uint64 infoSeq = 4; optional bool bHotGroup = 5; optional uint64 redpackTime = 6; optional bool hasMsg = 7; optional int64 dicussFlag = 8; } message GroupList { optional uint64 groupCode = 1; optional uint64 groupSeq = 2; optional uint64 memberSeq = 3; optional uint64 mask = 4; optional uint64 redpackTime = 5; optional bool hasMsg = 6; optional int64 groupFlag = 7; optional uint64 groupType = 8; optional uint32 groupNameSeq = 9; optional uint32 groupMemberSeq = 10; optional uint32 uinFlagEx2 = 11; optional uint32 importantMsgLatestSeq = 12; } message SvcPbResponsePullDisMsgProxy { optional uint64 memberSeq = 1; optional bytes content = 2; } message SvcRegisterProxyMsgResp { optional uint32 result = 1; optional bytes errMsg = 2; optional uint32 flag = 3; optional uint32 seq = 4; optional SvcResponseMsgInfo info = 5; repeated GroupList groupList = 6; repeated DiscussList discussList = 7; repeated SvcResponsePbPullGroupMsgProxy groupMsg = 8; repeated SvcPbResponsePullDisMsgProxy discussMsg = 9; optional bytes c2CMsg = 10; optional bytes pubAccountMsg = 11; optional uint32 discussListFlag = 12; } message SvcResponseMsgInfo { optional uint32 groupNum = 1; optional uint32 discussNum = 2; } message SvcResponsePbPullGroupMsgProxy { optional uint64 memberSeq = 1; optional bytes content = 2; } ================================================ FILE: ricq-guild/src/protocol/core/msg/TextMsgExt.proto ================================================ syntax = "proto2"; package msg; message ExtChannelInfo { optional uint64 guildId = 1; optional uint64 channelId = 2; } message TextResvAttr { optional bytes wording = 1; optional uint32 textAnalysisResult = 2; optional uint32 atType = 3; optional uint64 atMemberUin = 4; optional uint64 atMemberTinyid = 5; optional ExtRoleInfo atMemberRoleInfo = 6; optional ExtRoleInfo atRoleInfo = 7; optional ExtChannelInfo atChannelInfo = 8; } message ExtRoleInfo { optional uint64 id = 1; optional bytes info = 2; optional uint32 flag = 3; } ================================================ FILE: ricq-guild/src/protocol/core/msg/head.proto ================================================ syntax = "proto2"; package msg; message C2CHead { optional uint64 toUin = 1; optional uint64 fromUin = 2; optional uint32 ccType = 3; optional uint32 ccCmd = 4; optional bytes authPicSig = 5; optional bytes authSig = 6; optional bytes authBuf = 7; optional uint32 serverTime = 8; optional uint32 clientTime = 9; optional uint32 rand = 10; optional string phoneNumber = 11; } message CSHead { optional uint64 uin = 1; optional uint32 command = 2; optional uint32 seq = 3; optional uint32 version = 4; optional uint32 retryTimes = 5; optional uint32 clientType = 6; optional uint32 pubno = 7; optional uint32 localid = 8; optional uint32 timezone = 9; optional fixed32 clientIp = 10; optional uint32 clientPort = 11; optional fixed32 connIp = 12; optional uint32 connPort = 13; optional fixed32 interfaceIp = 14; optional uint32 interfacePort = 15; optional fixed32 actualIp = 16; optional uint32 flag = 17; optional fixed32 timestamp = 18; optional uint32 subcmd = 19; optional uint32 result = 20; optional uint32 appId = 21; optional uint32 instanceId = 22; optional uint64 sessionId = 23; optional uint32 idcId = 24; } message DeltaHead { optional uint64 totalLen = 1; optional uint64 offset = 2; optional uint64 ackOffset = 3; optional bytes cookie = 4; optional bytes ackCookie = 5; optional uint32 result = 6; optional uint32 flags = 7; } message IMHead { optional uint32 headType = 1; optional CSHead csHead = 2; optional S2CHead s2CHead = 3; optional HttpConnHead httpconnHead = 4; optional uint32 paintFlag = 5; optional LoginSig loginSig = 6; optional DeltaHead deltaHead = 7; optional C2CHead c2CHead = 8; } message HttpConnHead { optional uint64 uin = 1; optional uint32 command = 2; optional uint32 subCommand = 3; optional uint32 seq = 4; optional uint32 version = 5; optional uint32 retryTimes = 6; optional uint32 clientType = 7; optional uint32 pubNo = 8; optional uint32 localId = 9; optional uint32 timeZone = 10; optional fixed32 clientIp = 11; optional uint32 clientPort = 12; optional fixed32 qzhttpIp = 13; optional uint32 qzhttpPort = 14; optional fixed32 sppIp = 15; optional uint32 sppPort = 16; optional uint32 flag = 17; optional bytes key = 18; optional uint32 compressType = 19; optional uint32 originSize = 20; optional uint32 errorCode = 21; optional RedirectMsg redirect = 22; optional uint32 commandId = 23; optional uint32 serviceCmdid = 24; optional TransOidbHead oidbhead = 25; } message LoginSig { optional uint32 type = 1; optional bytes sig = 2; } message RedirectMsg { optional fixed32 lastRedirectIp = 1; optional uint32 lastRedirectPort = 2; optional fixed32 redirectIp = 3; optional uint32 redirectPort = 4; optional uint32 redirectCount = 5; } message S2CHead { optional uint32 subMsgtype = 1; optional uint32 msgType = 2; optional uint64 fromUin = 3; optional uint32 msgId = 4; optional fixed32 relayIp = 5; optional uint32 relayPort = 6; optional uint64 toUin = 7; } message TransOidbHead { optional uint32 command = 1; optional uint32 serviceType = 2; optional uint32 result = 3; optional string errorMsg = 4; } ================================================ FILE: ricq-guild/src/protocol/core/msg/msg.proto ================================================ syntax = "proto2"; package msg; message GetMessageRequest { optional SyncFlag syncFlag = 1; optional bytes syncCookie = 2; optional int32 rambleFlag = 3; optional int32 latestRambleNumber = 4; optional int32 otherRambleNumber = 5; optional int32 onlineSyncFlag = 6; optional int32 contextFlag = 7; optional int32 whisperSessionId = 8; optional int32 msgReqType = 9; optional bytes pubaccountCookie = 10; optional bytes msgCtrlBuf = 11; optional bytes serverBuf = 12; } message SendMessageRequest { optional RoutingHead routingHead = 1; optional ContentHead contentHead = 2; optional MessageBody msgBody = 3; optional int32 msgSeq = 4; optional int32 msgRand = 5; optional bytes syncCookie = 6; //MsgComm.AppShareInfo? appShare = 7; optional int32 msgVia = 8; optional int32 dataStatist = 9; //MultiMsgAssist? multiMsgAssist = 10; //PbInputNotifyInfo? inputNotifyInfo = 11; optional MsgCtrl msgCtrl = 12; //ImReceipt.ReceiptReq? receiptReq = 13; optional int32 multiSendSeq = 14; } message SendMessageResponse { optional int32 result = 1; optional string errMsg = 2; } message MsgWithDrawReq { repeated C2CMsgWithDrawReq c2cWithDraw = 1; repeated GroupMsgWithDrawReq groupWithDraw = 2; } message C2CMsgWithDrawReq{ repeated C2CMsgInfo msgInfo = 1; optional int32 longMessageFlag = 2; optional bytes reserved = 3; optional int32 subCmd = 4; } message GroupMsgWithDrawReq{ optional int32 subCmd = 1; optional int32 groupType = 2; optional int64 groupCode = 3; repeated GroupMsgInfo msgList = 4; optional bytes userDef = 5; } message MsgWithDrawResp { repeated C2CMsgWithDrawResp c2cWithDraw = 1; repeated GroupMsgWithDrawResp groupWithDraw = 2; } message C2CMsgWithDrawResp { optional int32 result = 1; optional string errMsg = 2; } message GroupMsgWithDrawResp { optional int32 result = 1; optional string errMsg = 2; } message GroupMsgInfo { optional int32 msgSeq = 1; optional int32 msgRandom = 2; optional int32 msgType = 3; } message C2CMsgInfo { optional int64 fromUin = 1; optional int64 toUin = 2; optional int32 msgSeq = 3; optional int64 msgUid = 4; optional int64 msgTime = 5; optional int32 msgRandom = 6; optional int32 pkgNum = 7; optional int32 pkgIndex = 8; optional int32 divSeq = 9; optional int32 msgType = 10; optional RoutingHead routingHead = 20; } message RoutingHead { oneof RoutingHead{ C2C c2c = 1; Grp grp = 2; GrpTmp grpTmp = 3; WPATmp wpaTmp = 6; } /* Dis dis = 4; DisTmp disTmp = 5; SecretFileHead? secretFile = 7; PublicPlat? publicPlat = 8; TransMsg? transMsg = 9; AddressListTmp? addressList = 10; RichStatusTmp? richStatusTmp = 11; TransCmd? transCmd = 12; AccostTmp? accostTmp = 13; PubGroupTmp? pubGroupTmp = 14; Trans0x211? trans0x211 = 15; BusinessWPATmp? businessWpaTmp = 16; AuthTmp? authTmp = 17; BsnsTmp? bsnsTmp = 18; QQQueryBusinessTmp? qqQuerybusinessTmp = 19; NearByDatingTmp? nearbyDatingTmp = 20; NearByAssistantTmp? nearbyAssistantTmp = 21; CommTmp? commTmp = 22; */ } message WPATmp { optional uint64 toUin = 1; optional bytes sig = 2; } message C2C { optional int64 toUin = 1; } message Grp { optional int64 groupCode = 1; } message GrpTmp { optional int64 groupUin = 1; optional int64 toUin = 2; } message MsgCtrl { optional int32 msgFlag = 1; } message GetMessageResponse { optional int32 result = 1; optional string errorMessage = 2; optional bytes syncCookie = 3; optional SyncFlag syncFlag = 4; repeated UinPairMessage uinPairMsgs = 5; optional int64 bindUin = 6; optional int32 msgRspType = 7; optional bytes pubAccountCookie = 8; optional bool isPartialSync = 9; optional bytes msgCtrlBuf = 10; } message PushMessagePacket { optional Message message = 1; optional int32 svrip = 2; optional bytes pushToken = 3; optional int32 pingFLag = 4; optional int32 generalFlag = 9; } message UinPairMessage { optional int32 lastReadTime = 1; optional int64 peerUin = 2; optional int32 msgCompleted = 3; repeated Message messages = 4; } message Message { optional MessageHead head = 1; optional ContentHead content = 2; optional MessageBody body = 3; } message MessageBody { optional RichText richText = 1; optional bytes msgContent = 2; optional bytes msgEncryptContent = 3; } message RichText { optional Attr attr = 1; repeated Elem elems = 2; optional NotOnlineFile notOnlineFile = 3; optional Ptt ptt = 4; } message Elem { oneof elem { Text text = 1; Face face = 2; OnlineImage onlineImage = 3; NotOnlineImage notOnlineImage = 4; TransElem transElemInfo = 5; MarketFace marketFace = 6; //ElemFlags elemFlags = 7; CustomFace customFace = 8; ElemFlags2 elemFlags2 = 9; //FunFace funFace = 10; //SecretFileMsg secretFile = 11; RichMsg richMsg = 12; GroupFile groupFile = 13; //PubGroup pubGroup = 14; //MarketTrans marketTrans = 15; ExtraInfo extraInfo = 16; //ShakeWindow? shakeWindow = 17; //PubAccount? pubAccount = 18; VideoFile videoFile = 19; //TipsInfo? tipsInfo = 20; AnonymousGroupMessage anonGroupMsg = 21; //QQLiveOld? qqLiveOld = 22; //LifeOnlineAccount? lifeOnline = 23; QQWalletMsg QQWalletMsg = 24; //CrmElem? crmElem = 25; //ConferenceTipsInfo? conferenceTipsInfo = 26; //RedBagInfo? redbagInfo = 27; //LowVersionTips? lowVersionTips = 28; //bytes bankcodeCtrlInfo = 29; //NearByMessageType? nearByMsg = 30; CustomElem customElem = 31; //LocationInfo? locationInfo = 32; //PubAccInfo? pubAccInfo = 33; //SmallEmoji? smallEmoji = 34; //FSJMessageElem? fsjMsgElem = 35; //ArkAppElem? arkApp = 36; GeneralFlags generalFlags = 37; //CustomFace? hcFlashPic = 38; //DeliverGiftMsg? deliverGiftMsg = 39; //BitAppMsg? bitappMsg = 40; //OpenQQData? openQqData = 41; //ApolloActMsg? apolloMsg = 42; //GroupPubAccountInfo? groupPubAccInfo = 43; //BlessingMessage? blessMsg = 44; SourceMsg srcMsg = 45; //LolaMsg? lolaMsg = 46; //GroupBusinessMsg? groupBusinessMsg = 47; //WorkflowNotifyMsg? msgWorkflowNotify = 48; //PatsElem? patElem = 49; //GroupPostElem? groupPostElem = 50; LightApp lightApp = 51; //EIMInfo? eimInfo = 52; CommonElem commonElem = 53; } } message MarketFace { optional bytes faceName = 1; optional uint32 itemType = 2; optional uint32 faceInfo = 3; optional bytes faceId = 4; optional uint32 tabId = 5; optional uint32 subType = 6; optional bytes key = 7; optional bytes param = 8; optional uint32 mediaType = 9; optional uint32 imageWidth = 10; optional uint32 imageHeight = 11; optional bytes mobileparam = 12; optional bytes pbReserve = 13; } message ElemFlags2 { optional uint32 colorTextId = 1; optional uint64 msgId = 2; optional uint32 whisperSessionId = 3; optional uint32 pttChangeBit = 4; optional uint32 vipStatus = 5; optional uint32 compatibleId = 6; repeated Inst insts = 7; optional uint32 msgRptCnt = 8; optional Inst srcInst = 9; optional uint32 longtitude = 10; optional uint32 latitude = 11; optional uint32 customFont = 12; optional PcSupportDef pcSupportDef = 13; optional uint32 crmFlags = 14; message Inst { optional uint32 appId = 1; optional uint32 instId = 2; } } message PcSupportDef { optional uint32 pcPtlBegin = 1; optional uint32 pcPtlEnd = 2; optional uint32 macPtlBegin = 3; optional uint32 macPtlEnd = 4; repeated uint32 ptlsSupport = 5; repeated uint32 ptlsNotSupport = 6; } message CommonElem { optional int32 serviceType = 1; optional bytes pbElem = 2; optional int32 businessType = 3; } message QQWalletMsg { optional QQWalletAioBody aioBody = 1; } message QQWalletAioBody { optional uint64 sendUin = 1; optional QQWalletAioElem sender = 2; optional QQWalletAioElem receiver = 3; optional sint32 ChannelId = 4; optional sint32 templateId = 5; optional uint32 resend = 6; optional uint32 msgPriority = 7; optional sint32 redType = 8; optional bytes billNo = 9; optional bytes authKey = 10; optional sint32 sessionType = 11; optional sint32 msgType = 12; optional sint32 envelOpeId = 13; optional bytes name = 14; optional sint32 confType = 15; optional sint32 msgFrom = 16; optional bytes pcBody = 17; optional bytes index = 18; optional uint32 redChannel = 19; repeated uint64 grapUin = 20; optional bytes pbReserve = 21; } message QQWalletAioElem{ optional uint32 background = 1; optional uint32 icon = 2; optional string title = 3; optional string subtitle = 4; optional string content = 5; optional bytes linkUrl = 6; optional bytes blackStripe = 7; optional bytes notice = 8; optional uint32 titleColor = 9; optional uint32 subtitleColor = 10; optional bytes actionsPriority = 11; optional bytes jumpUrl = 12; optional bytes nativeIos = 13; optional bytes nativeAndroid = 14; optional bytes iconUrl = 15; optional uint32 contentColor = 16; optional uint32 contentBgColor = 17; optional bytes aioImageLeft = 18; optional bytes aioImageRight = 19; optional bytes cftImage = 20; optional bytes pbReserve = 21; } message RichMsg { optional bytes template1 = 1; optional int32 serviceId = 2; optional bytes msgResId = 3; optional int32 rand = 4; optional int32 seq = 5; } message CustomElem { optional bytes desc = 1; optional bytes data = 2; optional int32 enumType = 3; optional bytes ext = 4; optional bytes sound = 5; } message Text { optional string str = 1; optional string link = 2; optional bytes attr6Buf = 3; optional bytes attr7Buf = 4; optional bytes buf = 11; optional bytes pbReserve = 12; } message Attr { optional int32 codePage = 1; optional int32 time = 2; optional int32 random = 3; optional int32 color = 4; optional int32 size = 5; optional int32 effect = 6; optional int32 charSet = 7; optional int32 pitchAndFamily = 8; optional string fontName = 9; optional bytes reserveData = 10; } message Ptt { optional int32 fileType = 1; optional int64 srcUin = 2; optional bytes fileUuid = 3; optional bytes fileMd5 = 4; optional string fileName = 5; optional int32 fileSize = 6; optional bytes reserve = 7; optional int32 fileId = 8; optional int32 serverIp = 9; optional int32 serverPort = 10; optional bool boolValid = 11; optional bytes signature = 12; optional bytes shortcut = 13; optional bytes fileKey = 14; optional int32 magicPttIndex = 15; optional int32 voiceSwitch = 16; optional bytes pttUrl = 17; optional bytes groupFileKey = 18; optional int32 time = 19; optional bytes downPara = 20; optional int32 format = 29; optional bytes pbReserve = 30; repeated bytes bytesPttUrls = 31; optional int32 downloadFlag = 32; } message OnlineImage { optional bytes guid = 1; optional bytes filePath = 2; optional bytes oldVerSendFile = 3; } message NotOnlineImage { optional string filePath = 1; optional uint32 fileLen = 2; optional string downloadPath = 3; optional bytes oldVerSendFile = 4; optional int32 imgType = 5; optional bytes previewsImage = 6; optional bytes picMd5 = 7; optional uint32 picHeight = 8; optional uint32 picWidth = 9; optional string resId = 10; optional bytes flag = 11; optional string thumbUrl = 12; optional int32 original = 13; optional string bigUrl = 14; optional string origUrl = 15; optional int32 bizType = 16; optional int32 result = 17; optional int32 index = 18; optional bytes opFaceBuf = 19; optional bool oldPicMd5 = 20; optional int32 thumbWidth = 21; optional int32 thumbHeight = 22; optional int32 fileId = 23; optional int32 showLen = 24; optional int32 downloadLen = 25; optional bytes pbReserve = 29; } message NotOnlineFile { optional int32 fileType = 1; optional bytes sig = 2; optional bytes fileUuid = 3; optional bytes fileMd5 = 4; optional bytes fileName = 5; optional int64 fileSize = 6; optional bytes note = 7; optional int32 reserved = 8; optional int32 subcmd = 9; optional int32 microCloud = 10; repeated bytes bytesFileUrls = 11; optional int32 downloadFlag = 12; optional int32 dangerEvel = 50; optional int32 lifeTime = 51; optional int32 uploadTime = 52; optional int32 absFileType = 53; optional int32 clientType = 54; optional int32 expireTime = 55; optional bytes pbReserve = 56; } message TransElem { optional int32 elemType = 1; optional bytes elemValue = 2; } message ExtraInfo { optional bytes nick = 1; optional bytes groupCard = 2; optional int32 level = 3; optional int32 flags = 4; optional int32 groupMask = 5; optional int32 msgTailId = 6; optional bytes senderTitle = 7; optional bytes apnsTips = 8; optional int64 uin = 9; optional int32 msgStateFlag = 10; optional int32 apnsSoundType = 11; optional int32 newGroupFlag = 12; } message GroupFile { optional bytes filename = 1; optional int64 fileSize = 2; optional bytes fileId = 3; optional bytes batchId = 4; optional bytes fileKey = 5; optional bytes mark = 6; optional int64 sequence = 7; optional bytes batchItemId = 8; optional int32 feedMsgTime = 9; optional bytes pbReserve = 10; } message AnonymousGroupMessage { optional int32 flags = 1; optional bytes anonId = 2; optional bytes anonNick = 3; optional int32 headPortrait = 4; optional int32 expireTime = 5; optional int32 bubbleId = 6; optional bytes rankColor = 7; } message VideoFile { optional bytes fileUuid = 1; optional bytes fileMd5 = 2; optional string fileName = 3; optional int32 fileFormat = 4; optional int32 fileTime = 5; optional int32 fileSize = 6; optional int32 thumbWidth = 7; optional int32 thumbHeight = 8; optional bytes thumbFileMd5 = 9; optional bytes source = 10; optional int32 thumbFileSize = 11; optional int32 busiType = 12; optional int32 fromChatType = 13; optional int32 toChatType = 14; optional bool boolSupportProgressive = 15; optional int32 fileWidth = 16; optional int32 fileHeight = 17; optional int32 subBusiType = 18; optional int32 videoAttr = 19; repeated bytes bytesThumbFileUrls = 20; repeated bytes bytesVideoFileUrls = 21; optional int32 thumbDownloadFlag = 22; optional int32 videoDownloadFlag = 23; optional bytes pbReserve = 24; } message SourceMsg { repeated int32 origSeqs = 1; optional int64 senderUin = 2; optional int32 time = 3; optional int32 flag = 4; repeated Elem elems = 5; optional int32 type = 6; optional bytes richMsg = 7; optional bytes pbReserve = 8; optional bytes srcMsg = 9; optional int64 toUin = 10; optional bytes troopName = 11; } message Face { optional int32 index = 1; optional bytes old = 2; optional bytes buf = 11; } message LightApp { optional bytes data = 1; optional bytes msgResid = 2; } message CustomFace { optional bytes guid = 1; optional string filePath = 2; optional string shortcut = 3; optional bytes buffer = 4; optional bytes flag = 5; optional bytes oldData = 6; optional int32 fileId = 7; optional uint32 serverIp = 8; optional uint32 serverPort = 9; optional int32 fileType = 10; optional bytes signature = 11; optional int32 useful = 12; optional bytes md5 = 13; optional string thumbUrl = 14; optional string bigUrl = 15; optional string origUrl = 16; optional int32 bizType = 17; optional int32 repeatIndex = 18; optional int32 repeatImage = 19; optional int32 imageType = 20; optional int32 index = 21; optional uint32 width = 22; optional uint32 height = 23; optional int32 source = 24; optional uint32 size = 25; optional int32 origin = 26; optional int32 thumbWidth = 27; optional int32 thumbHeight = 28; optional int32 showLen = 29; optional int32 downloadLen = 30; optional string x400Url = 31;//x optional int32 x400Width = 32;//x optional int32 x400Height = 33;//x optional bytes pbReserve = 34; } message ContentHead { optional int32 pkgNum = 1; optional int32 pkgIndex = 2; optional int32 divSeq = 3; optional int32 autoReply = 4; } message MessageHead { optional int64 fromUin = 1; optional int64 toUin = 2; optional int32 msgType = 3; optional int32 c2cCmd = 4; optional int32 msgSeq = 5; optional int32 msgTime = 6; optional int64 msgUid = 7; optional C2CTempMessageHead c2cTmpMsgHead = 8; optional GroupInfo groupInfo = 9; optional int32 fromAppid = 10; optional int32 fromInstid = 11; optional int32 userActive = 12; optional DiscussInfo discussInfo = 13; optional string fromNick = 14; optional int64 authUin = 15; optional string authNick = 16; optional int32 msgFlag = 17; optional string authRemark = 18; optional string groupName = 19; optional MutilTransHead mutiltransHead = 20; optional InstCtrl msgInstCtrl = 21; optional int32 publicAccountGroupSendFlag = 22; optional int32 wseqInC2cMsghead = 23; optional int64 cpid = 24; optional ExtGroupKeyInfo extGroupKeyInfo = 25; optional string multiCompatibleText = 26; optional int32 authSex = 27; optional bool isSrcMsg = 28; } message GroupInfo { optional int64 groupCode = 1; optional int32 groupType = 2; optional int64 groupInfoSeq = 3; optional bytes groupCard = 4; optional bytes groupRank = 5; optional int32 groupLevel = 6; optional int32 groupCardType = 7; optional bytes groupName = 8; } message DiscussInfo { optional int64 discussUin = 1; optional int32 discussType = 2; optional int64 discussInfoSeq = 3; optional bytes discussRemark = 4; optional bytes discussName = 5; } message MutilTransHead{ optional int32 status = 1; optional int32 msgId = 2; } message C2CTempMessageHead { optional int32 c2cType = 1; optional int32 serviceType = 2; optional int64 groupUin = 3; optional int64 groupCode = 4; optional bytes sig = 5; optional int32 sigType = 6; optional string fromPhone = 7; optional string toPhone = 8; optional int32 lockDisplay = 9; optional int32 directionFlag = 10; optional bytes reserved = 11; } message InstCtrl { repeated InstInfo msgSendToInst = 1; repeated InstInfo msgExcludeInst = 2; optional InstInfo msgFromInst = 3; } message InstInfo { optional int32 apppid = 1; optional int32 instid = 2; optional int32 platform = 3; optional int32 enumDeviceType = 10; } message ExtGroupKeyInfo { optional int32 curMaxSeq = 1; optional int64 curTime = 2; } message SyncCookie { optional int64 time1 = 1; optional int64 time = 2; optional int64 ran1 = 3; optional int64 ran2 = 4; optional int64 const1 = 5; optional int64 const2 = 11; optional int64 const3 = 12; optional int64 lastSyncTime = 13; optional int64 const4 = 14; } message TransMsgInfo { optional int64 fromUin = 1; optional int64 toUin = 2; optional int32 msgType = 3; optional int32 msgSubtype = 4; optional int32 msgSeq = 5; optional int64 msgUid = 6; optional int32 msgTime = 7; optional int32 realMsgTime = 8; optional string nickName = 9; optional bytes msgData = 10; optional int32 svrIp = 11; optional ExtGroupKeyInfo extGroupKeyInfo = 12; optional int32 generalFlag = 17; } message GeneralFlags { optional int32 bubbleDiyTextId = 1; optional int32 groupFlagNew = 2; optional int64 uin = 3; optional bytes rpId = 4; optional int32 prpFold = 5; optional int32 longTextFlag = 6; optional string longTextResid = 7; optional int32 groupType = 8; optional int32 toUinFlag = 9; optional int32 glamourLevel = 10; optional int32 memberLevel = 11; optional int64 groupRankSeq = 12; optional int32 olympicTorch = 13; optional bytes babyqGuideMsgCookie = 14; optional int32 uin32ExpertFlag = 15; optional int32 bubbleSubId = 16; optional int64 pendantId = 17; optional bytes rpIndex = 18; optional bytes pbReserve = 19; } message PbMultiMsgItem { optional string fileName = 1; optional PbMultiMsgNew buffer = 2; } message PbMultiMsgNew { repeated Message msg = 1; } message PbMultiMsgTransmit { repeated Message msg = 1; repeated PbMultiMsgItem pbItemList = 2; } message MsgElemInfo_servtype3 { optional CustomFace flash_troop_pic = 1; optional NotOnlineImage flash_c2c_pic = 2; } message MsgElemInfo_servtype33 { optional uint32 index = 1; optional bytes text = 2; optional bytes compat = 3; optional bytes buf = 4; } message SubMsgType0x4Body { optional NotOnlineFile notOnlineFile = 1; optional uint32 msgTime = 2; optional uint32 onlineFileForPolyToOffline = 3; // fileImageInfo } enum SyncFlag { START = 0; CONTINUME = 1; STOP = 2; } message ResvAttr { optional uint32 imageBizType = 1; optional AnimationImageShow image_show = 7; } message AnimationImageShow { optional int32 effect_id = 1; optional bytes animation_param = 2; } message UinTypeUserDef { optional int32 fromUinType = 1; optional int64 fromGroupCode = 2; optional string fileUuid = 3; } message GetGroupMsgReq { optional uint64 groupCode = 1; optional uint64 beginSeq = 2; optional uint64 endSeq = 3; optional uint32 filter = 4; optional uint64 memberSeq = 5; optional bool publicGroup = 6; optional uint32 shieldFlag = 7; optional uint32 saveTrafficFlag = 8; } message GetGroupMsgResp { optional uint32 result = 1; optional string errmsg = 2; optional uint64 groupCode = 3; optional uint64 returnBeginSeq = 4; optional uint64 returnEndSeq = 5; repeated Message msg = 6; } message PbGetOneDayRoamMsgReq { optional uint64 peerUin = 1; optional uint64 lastMsgTime = 2; optional uint64 random = 3; optional uint32 readCnt = 4; } message PbGetOneDayRoamMsgResp { optional uint32 result = 1; optional string errMsg = 2; optional uint64 peerUin = 3; optional uint64 lastMsgTime = 4; optional uint64 random = 5; repeated Message msg = 6; optional uint32 isComplete = 7; } message PbPushMsg { optional Message msg = 1; optional int32 svrip = 2; optional bytes pushToken = 3; optional uint32 pingFlag = 4; optional uint32 generalFlag = 9; optional uint64 bindUin = 10; } message MsgElemInfo_servtype37 { optional bytes packid = 1; optional bytes stickerid = 2; optional uint32 qsid = 3; optional uint32 sourcetype = 4; optional uint32 stickertype = 5; optional bytes resultid = 6; optional bytes text = 7; optional bytes surpriseid = 8; optional uint32 randomtype = 9; } ================================================ FILE: ricq-guild/src/protocol/core/msg/objmsg.proto ================================================ syntax = "proto3"; package msg; message MsgPic { bytes smallPicUrl = 1; bytes originalPicUrl = 2; int32 localPicId = 3; } message ObjMsg { int32 msgType = 1; bytes title = 2; bytes bytesAbstact = 3; bytes titleExt = 5; repeated MsgPic msgPic = 6; repeated MsgContentInfo msgContentInfo = 7; int32 reportIdShow = 8; } message MsgContentInfo { bytes contentInfoId = 1; MsgFile msgFile = 2; } message MsgFile { int32 busId = 1; bytes filePath = 2; int64 fileSize = 3; string fileName = 4; int64 int64DeadTime = 5; bytes fileSha1 = 6; bytes ext = 7; } ================================================ FILE: ricq-guild/src/protocol/core/msg/report.proto ================================================ syntax = "proto2"; package msg; message PbMsgReadedReportReq { repeated PbGroupReadedReportReq grpReadReport = 1; repeated PbDiscussReadedReportReq disReadReport = 2; optional PbC2CReadedReportReq c2CReadReport = 3; //optional PbBindUinMsgReadedConfirmReq bindUinReadReport = 4; } message PbMsgReadedReportResp { repeated PbGroupReadedReportResp grpReadReport = 1; repeated PbDiscussReadedReportResp disReadReport = 2; optional PbC2CReadedReportResp c2CReadReport = 3; //optional PbBindUinMsgReadedConfirmResp bindUinReadReport = 4; } message PbGroupReadedReportReq { optional uint64 groupCode = 1; optional uint64 lastReadSeq = 2; } message PbDiscussReadedReportReq { optional uint64 confUin = 1; optional uint64 lastReadSeq = 2; } message PbC2CReadedReportReq { optional bytes syncCookie = 1; repeated UinPairReadInfo pairInfo = 2; } message UinPairReadInfo { optional uint64 peerUin = 1; optional uint32 lastReadTime = 2; optional bytes crmSig = 3; optional uint32 peerType = 4; optional uint32 chatType = 5; optional uint64 cpid = 6; optional uint32 aioType = 7; optional uint64 toTinyId = 9; } message PbGroupReadedReportResp { optional uint32 result = 1; optional string errmsg = 2; optional uint64 groupCode = 3; optional uint64 memberSeq = 4; optional uint64 groupMsgSeq = 5; } message PbDiscussReadedReportResp { optional uint32 result = 1; optional string errmsg = 2; optional uint64 confUin = 3; optional uint64 memberSeq = 4; optional uint64 confSeq = 5; } message PbC2CReadedReportResp { optional uint32 result = 1; optional string errmsg = 2; optional bytes syncCookie = 3; } ================================================ FILE: ricq-guild/src/protocol/core/msgtype0x210/subMsgType0x27.proto ================================================ syntax = "proto2"; package msgtype0x210; message AddGroup { optional uint32 groupid = 1; optional uint32 sortid = 2; optional bytes groupname = 3; } message AppointmentNotify { optional uint64 fromUin = 1; optional string appointId = 2; optional uint32 notifytype = 3; optional string tipsContent = 4; optional uint32 unreadCount = 5; optional string joinWording = 6; optional string viewWording = 7; optional bytes sig = 8; optional bytes eventInfo = 9; optional bytes nearbyEventInfo = 10; optional bytes feedEventInfo = 11; } message BinaryMsg { optional uint32 opType = 1; optional bytes opValue = 2; } message ChatMatchInfo { optional bytes sig = 1; optional uint64 uin = 2; optional uint64 matchUin = 3; optional bytes tipsWording = 4; optional uint32 leftChatTime = 5; optional uint64 timeStamp = 6; optional uint32 matchExpiredTime = 7; optional uint32 c2CExpiredTime = 8; optional uint32 matchCount = 9; optional bytes nick = 10; } message ConfMsgRoamFlag { optional uint64 confid = 1; optional uint32 flag = 2; optional uint64 timestamp = 3; } message DaRenNotify { optional uint64 uin = 1; optional uint32 loginDays = 2; optional uint32 days = 3; optional uint32 isYestodayLogin = 4; optional uint32 isTodayLogin = 5; } message DelFriend { repeated uint64 uins = 1; } message DelGroup { optional uint32 groupid = 1; } message FanpaiziNotify { optional uint64 fromUin = 1; optional string fromNick = 2; optional bytes tipsContent = 3; optional bytes sig = 4; } message ForwardBody { optional uint32 notifyType = 1; optional uint32 opType = 2; optional AddGroup addGroup = 3; optional DelGroup delGroup = 4; optional ModGroupName modGroupName = 5; optional ModGroupSort modGroupSort = 6; optional ModFriendGroup modFriendGroup = 7; optional ModProfile modProfile = 8; optional ModFriendRemark modFriendRemark = 9; optional ModLongNick modLongNick = 10; optional ModCustomFace modCustomFace = 11; optional ModGroupProfile modGroupProfile = 12; optional ModGroupMemberProfile modGroupMemberProfile = 13; optional DelFriend delFriend = 14; optional ModFrdRoamPriv roamPriv = 15; optional GrpMsgRoamFlag grpMsgRoamFlag = 16; optional ConfMsgRoamFlag confMsgRoamFlag = 17; optional ModLongNick modRichLongNick = 18; optional BinaryMsg binPkg = 19; optional ModSnsGeneralInfo modFriendRings = 20; optional ModConfProfile modConfProfile = 21; optional SnsUpdateFlag modFriendFlag = 22; optional AppointmentNotify appointmentNotify = 23; optional DaRenNotify darenNotify = 25; optional NewComeinUserNotify newComeinUserNotify = 26; optional PushSearchDev pushSearchDev = 200; optional PushReportDev pushReportDev = 201; optional QQPayPush qqPayPush = 202; optional bytes redpointInfo = 203; optional HotFriendNotify hotFriendNotify = 204; optional PraiseRankNotify praiseRankNotify = 205; optional MQQCampusNotify campusNotify = 210; optional ModLongNick modRichLongNickEx = 211; optional ChatMatchInfo chatMatchInfo = 212; optional FrdCustomOnlineStatusChange frdCustomOnlineStatusChange = 214; optional FanpaiziNotify fanpanziNotify = 2000; } message FrdCustomOnlineStatusChange { optional uint64 uin = 1; } message FriendGroup { optional uint64 fuin = 1; repeated uint32 oldGroupId = 2; repeated uint32 newGroupId = 3; } message FriendRemark { optional uint32 type = 1; optional uint64 fuin = 2; optional bytes rmkName = 3; optional uint64 groupCode = 4; } message GPS { optional int32 lat = 1; optional int32 lon = 2; optional int32 alt = 3; optional int32 type = 4; } message GroupMemberProfileInfo { optional uint32 field = 1; optional bytes value = 2; } message GroupProfileInfo { optional uint32 field = 1; optional bytes value = 2; } message GroupSort { optional uint32 groupid = 1; optional uint32 sortid = 2; } message GrpMsgRoamFlag { optional uint64 groupcode = 1; optional uint32 flag = 2; optional uint64 timestamp = 3; } message HotFriendNotify { optional uint64 dstUin = 1; optional uint32 praiseHotLevel = 2; optional uint32 chatHotLevel = 3; optional uint32 praiseHotDays = 4; optional uint32 chatHotDays = 5; optional uint32 closeLevel = 6; optional uint32 closeDays = 7; optional uint32 praiseFlag = 8; optional uint32 chatFlag = 9; optional uint32 closeFlag = 10; optional uint64 notifyTime = 11; optional uint64 lastPraiseTime = 12; optional uint64 lastChatTime = 13; optional uint32 qzoneHotLevel = 14; optional uint32 qzoneHotDays = 15; optional uint32 qzoneFlag = 16; optional uint64 lastQzoneTime = 17; } message MQQCampusNotify { optional uint64 fromUin = 1; optional string wording = 2; optional string target = 3; optional uint32 type = 4; optional string source = 5; } message ModConfProfile { optional uint64 uin = 1; optional uint32 confUin = 2; repeated ProfileInfo profileInfos = 3; } message ModCustomFace { optional uint32 type = 1; optional uint64 uin = 2; optional uint64 groupCode = 3; optional uint64 cmdUin = 4; } message ModFrdRoamPriv { repeated OneRoamPriv roamPriv = 1; } message ModFriendGroup { repeated FriendGroup frdGroup = 1; } message ModFriendRemark { repeated FriendRemark frdRmk = 1; } message ModGroupMemberProfile { optional uint64 groupUin = 1; optional uint64 uin = 2; repeated GroupMemberProfileInfo groupMemberProfileInfos = 3; optional uint64 groupCode = 4; } message ModGroupName { optional uint32 groupid = 1; optional bytes groupname = 2; } message ModGroupProfile { optional uint64 groupUin = 1; repeated GroupProfileInfo groupProfileInfos = 2; optional uint64 groupCode = 3; optional uint64 cmdUin = 4; } message ModGroupSort { repeated GroupSort groupsort = 1; } message ModLongNick { optional uint64 uin = 1; optional bytes value = 2; } message ModProfile { optional uint64 uin = 1; repeated ProfileInfo profileInfos = 2; } message ModSnsGeneralInfo { repeated SnsUpateBuffer snsGeneralInfos = 1; } message SubMsg0x27Body { repeated ForwardBody modInfos = 1; } message NewComeinUser { optional uint64 uin = 1; optional uint32 isFrd = 2; optional bytes remark = 3; optional bytes nick = 4; } message NewComeinUserNotify { optional uint32 msgType = 1; optional bool ongNotify = 2; optional uint32 pushTime = 3; optional NewComeinUser newComeinUser = 4; optional NewGroup newGroup = 5; optional NewGroupUser newGroupUser = 6; } message NewGroup { optional uint64 groupCode = 1; optional bytes groupName = 2; optional uint64 ownerUin = 3; optional bytes ownerNick = 4; optional bytes distance = 5; } message NewGroupUser { optional uint64 uin = 1; optional int32 sex = 2; optional int32 age = 3; optional string nick = 4; optional bytes distance = 5; } message OneRoamPriv { optional uint64 fuin = 1; optional uint32 privTag = 2; optional uint32 privValue = 3; } message PraiseRankNotify { optional uint32 isChampion = 11; optional uint32 rankNum = 12; optional string msg = 13; } message ProfileInfo { optional uint32 field = 1; optional bytes value = 2; } message PushReportDev { optional uint32 msgType = 1; optional bytes cookie = 4; optional uint32 reportMaxNum = 5; optional bytes sn = 6; } message PushSearchDev { optional uint32 msgType = 1; optional GPS gpsInfo = 2; optional uint32 devTime = 3; optional uint32 pushTime = 4; optional uint64 din = 5; optional string data = 6; } message QQPayPush { optional uint64 uin = 1; optional bool payOk = 2; } message SnsUpateBuffer { optional uint64 uin = 1; optional uint64 code = 2; optional uint32 result = 3; repeated SnsUpdateItem snsUpdateItem = 400; repeated uint32 idlist = 401; } message SnsUpdateFlag { repeated SnsUpdateOneFlag updateSnsFlag = 1; } message SnsUpdateItem { optional uint32 updateSnsType = 1; optional bytes value = 2; } message SnsUpdateOneFlag { optional uint64 XUin = 1; optional uint64 id = 2; optional uint32 flag = 3; } ================================================ FILE: ricq-guild/src/protocol/core/multimsg/multimsg.proto ================================================ syntax = "proto3"; package multimsg; message ExternMsg { int32 channelType = 1; } message MultiMsgApplyDownReq { bytes msgResid = 1; int32 msgType = 2; int64 srcUin = 3; } message MultiMsgApplyDownRsp { int32 result = 1; bytes thumbDownPara = 2; bytes msgKey = 3; repeated uint32 downIp = 4; repeated uint32 downPort = 5; bytes msgResid = 6; ExternMsg msgExternInfo = 7; repeated bytes bytesDownIpV6 = 8; repeated int32 uint32DownV6Port = 9; } message MultiMsgApplyUpReq { int64 dstUin = 1; int64 msgSize = 2; bytes msgMd5 = 3; int32 msgType = 4; int32 applyId = 5; } message MultiMsgApplyUpRsp { int32 result = 1; string msgResid = 2; bytes msgUkey = 3; repeated int32 uint32UpIp = 4; repeated int32 uint32UpPort = 5; int64 blockSize = 6; int64 upOffset = 7; int32 applyId = 8; bytes msgKey = 9; bytes msgSig = 10; ExternMsg msgExternInfo = 11; repeated bytes bytesUpIpV6 = 12; repeated int32 uint32UpV6Port = 13; } message MultiReqBody { int32 subcmd = 1; int32 termType = 2; int32 platformType = 3; int32 netType = 4; string buildVer = 5; repeated MultiMsgApplyUpReq multimsgApplyupReq = 6; repeated MultiMsgApplyDownReq multimsgApplydownReq = 7; int32 buType = 8; int32 reqChannelType = 9; } message MultiRspBody { int32 subcmd = 1; repeated MultiMsgApplyUpRsp multimsgApplyupRsp = 2; repeated MultiMsgApplyDownRsp multimsgApplydownRsp = 3; } ================================================ FILE: ricq-guild/src/protocol/core/notify/group0x857.proto ================================================ syntax = "proto3"; package notify; message NotifyMsgBody { AIOGrayTipsInfo optMsgGrayTips = 5; RedGrayTipsInfo optMsgRedTips = 9; MessageRecallReminder optMsgRecall = 11; GeneralGrayTipInfo optGeneralGrayTip = 26; QQGroupDigestMsg qqGroupDigestMsg = 33; int32 serviceType = 13; } message AIOGrayTipsInfo{ uint32 showLatest = 1; bytes content = 2; uint32 remind = 3; bytes brief = 4; uint64 receiverUin = 5; uint32 reliaoAdminOpt = 6; } message GeneralGrayTipInfo { uint64 busiType = 1; uint64 busiId = 2; uint32 ctrlFlag = 3; uint32 c2cType = 4; uint32 serviceType = 5; uint64 templId = 6; repeated TemplParam msgTemplParam = 7; string content = 8; } message TemplParam { string name = 1; string value = 2; } message MessageRecallReminder { int64 uin = 1; bytes nickname = 2; repeated RecalledMessageMeta recalledMsgList = 3; bytes reminderContent = 4; bytes userdef = 5; int32 groupType = 6; int32 opType = 7; } message RecalledMessageMeta { int32 seq = 1; int32 time = 2; int32 msgRandom = 3; int32 msgType = 4; int32 msgFlag = 5; int64 authorUin = 6; } message RedGrayTipsInfo { uint32 showLatest = 1; uint64 senderUin = 2; uint64 receiverUin = 3; string senderRichContent = 4; string receiverRichContent = 5; bytes authKey = 6; sint32 msgType = 7; uint32 luckyFlag = 8; uint32 hideFlag = 9; uint64 luckyUin = 12; } message QQGroupDigestMsg { uint64 groupCode = 1; uint32 seq = 2; uint32 random = 3; int32 opType = 4; uint64 sender = 5; uint64 digestOper = 6; uint32 opTime = 7; uint32 lastestMsgSeq = 8; bytes operNick = 9; bytes senderNick = 10; int32 extInfo = 11; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb.proto ================================================ syntax = "proto3"; package oidb; message OIDBSSOPkg { int32 command = 1; int32 serviceType = 2; int32 result = 3; bytes bodybuffer = 4; string errorMsg = 5; string clientVersion = 6; } message D8A0RspBody { int64 optUint64GroupCode = 1; repeated D8A0KickResult msgKickResult = 2; } message D8A0KickResult { int32 optUint32Result = 1; int64 optUint64MemberUin = 2; } message D8A0KickMemberInfo { int32 optUint32Operate = 1; int64 optUint64MemberUin = 2; int32 optUint32Flag = 3; bytes optBytesMsg = 4; } message D8A0ReqBody { int64 optUint64GroupCode = 1; repeated D8A0KickMemberInfo msgKickList = 2; repeated int64 kickList = 3; int32 kickFlag = 4; bytes kickMsg = 5; } message D89AReqBody { int64 groupCode = 1; D89AGroupinfo stGroupInfo = 2; int64 originalOperatorUin = 3; int32 reqGroupOpenAppid = 4; } message D89AGroupinfo { int32 groupExtAdmNum = 1; int32 flag = 2; bytes ingGroupName = 3; bytes ingGroupMemo = 4; bytes ingGroupFingerMemo = 5; bytes ingGroupAioSkinUrl = 6; bytes ingGroupBoardSkinUrl = 7; bytes ingGroupCoverSkinUrl = 8; int32 groupGrade = 9; int32 activeMemberNum = 10; int32 certificationType = 11; bytes ingCertificationText = 12; bytes ingGroupRichFingerMemo = 13; D89AGroupNewGuidelinesInfo stGroupNewguidelines = 14; int32 groupFace = 15; int32 addOption = 16; oneof shutupTime { int32 val = 17; } int32 groupTypeFlag = 18; bytes stringGroupTag = 19; D89AGroupGeoInfo msgGroupGeoInfo = 20; int32 groupClassExt = 21; bytes ingGroupClassText = 22; int32 appPrivilegeFlag = 23; int32 appPrivilegeMask = 24; D89AGroupExInfoOnly stGroupExInfo = 25; int32 groupSecLevel = 26; int32 groupSecLevelInfo = 27; int64 subscriptionUin = 28; int32 allowMemberInvite = 29; bytes ingGroupQuestion = 30; bytes ingGroupAnswer = 31; int32 groupFlagext3 = 32; int32 groupFlagext3Mask = 33; int32 groupOpenAppid = 34; int32 noFingerOpenFlag = 35; int32 noCodeFingerOpenFlag = 36; int64 rootId = 37; int32 msgLimitFrequency = 38; } message D89AGroupNewGuidelinesInfo { bool boolEnabled = 1; bytes ingContent = 2; } message D89AGroupExInfoOnly { int32 tribeId = 1; int32 moneyForAddGroup = 2; } message D89AGroupGeoInfo { int32 cityId = 1; int64 longtitude = 2; int64 latitude = 3; bytes ingGeoContent = 4; int64 poiId = 5; } message DED3ReqBody { int64 toUin = 1; int64 groupCode = 2; int32 msgSeq = 3; int32 msgRand = 4; int64 aioUin = 5; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0x6d6.proto ================================================ syntax = "proto2"; package oidb; message DeleteFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string parentFolderId = 4; optional string fileId = 5; } message DeleteFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; } message DownloadFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string fileId = 4; optional bool boolThumbnailReq = 5; optional int32 urlType = 6; optional bool boolPreviewReq = 7; } message DownloadFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional string downloadIp = 4; optional bytes downloadDns = 5; optional bytes downloadUrl = 6; optional bytes sha = 7; optional bytes sha3 = 8; optional bytes md5 = 9; optional bytes cookieVal = 10; optional string saveFileName = 11; optional int32 previewPort = 12; } message MoveFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string fileId = 4; optional string parentFolderId = 5; optional string destFolderId = 6; } message MoveFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional string parentFolderId = 4; } message RenameFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string fileId = 4; optional string parentFolderId = 5; optional string newFileName = 6; } message RenameFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; } message D6D6ReqBody { optional UploadFileReqBody uploadFileReq = 1; optional ResendReqBody resendFileReq = 2; optional DownloadFileReqBody downloadFileReq = 3; optional DeleteFileReqBody deleteFileReq = 4; optional RenameFileReqBody renameFileReq = 5; optional MoveFileReqBody moveFileReq = 6; } message ResendReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional string fileId = 4; optional bytes sha = 5; } message ResendRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional string uploadIp = 4; optional bytes fileKey = 5; optional bytes checkKey = 6; } message D6D6RspBody { optional UploadFileRspBody uploadFileRsp = 1; optional ResendRspBody resendFileRsp = 2; optional DownloadFileRspBody downloadFileRsp = 3; optional DeleteFileRspBody deleteFileRsp = 4; optional RenameFileRspBody renameFileRsp = 5; optional MoveFileRspBody moveFileRsp = 6; } message UploadFileReqBody { optional int64 groupCode = 1; optional int32 appId = 2; optional int32 busId = 3; optional int32 entrance = 4; optional string parentFolderId = 5; optional string fileName = 6; optional string localPath = 7; optional int64 int64FileSize = 8; optional bytes sha = 9; optional bytes sha3 = 10; optional bytes md5 = 11; optional bool supportMultiUpload = 15; } message UploadFileRspBody { optional int32 retCode = 1; optional string retMsg = 2; optional string clientWording = 3; optional string uploadIp = 4; optional string serverDns = 5; optional int32 busId = 6; optional string fileId = 7; optional bytes fileKey = 8; optional bytes checkKey = 9; optional bool boolFileExist = 10; repeated string uploadIpLanV4 = 12; repeated string uploadIpLanV6 = 13; optional int32 uploadPort = 14; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0x758.proto ================================================ syntax = "proto2"; package oidb; message InviteUinInfo { optional uint64 uin = 1; optional uint64 judgeGroupCode = 2; optional uint64 judgeConfCode = 3; } message D758ReqBody { optional uint64 joinGroupCode = 1; repeated InviteUinInfo beInvitedUinInfo = 2; optional string msg = 3; optional uint32 mainSourceId = 4; optional uint32 subSourceId = 5; optional string verifyToken = 6; optional uint32 verifyType = 7; } message D758RspBody { optional uint64 groupCode = 1; optional uint64 currentMaxMsgseq = 2; optional string verifyUrl = 3; optional uint32 verifyType = 4; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0x769.proto ================================================ syntax = "proto2"; package oidb; message CPU { optional string model = 1; optional uint32 cores = 2; optional uint32 frequency = 3; } message Camera { optional uint64 primary = 1; optional uint64 secondary = 2; optional bool flash = 3; } message D769ConfigSeq { optional uint32 type = 1; optional uint32 version = 2; } message Content { optional uint32 taskId = 1; optional uint32 compress = 2; optional bytes content = 10; } message D769DeviceInfo { optional string brand = 1; optional string model = 2; optional C41219OS os = 3; optional CPU cpu = 4; optional Memory memory = 5; optional Storage storage = 6; optional Screen screen = 7; optional Camera camera = 8; } message Memory { optional uint64 total = 1; optional uint64 process = 2; } message C41219OS { optional uint32 type = 1; optional string version = 2; optional string sdk = 3; optional string kernel = 4; optional string rom = 5; } message QueryUinPackageUsageReq { optional uint32 type = 1; optional uint64 uinFileSize = 2; } message QueryUinPackageUsageRsp { optional uint32 status = 1; optional uint64 leftUinNum = 2; optional uint64 maxUinNum = 3; optional uint32 proportion = 4; repeated UinPackageUsedInfo uinPackageUsedList = 10; } message D769ReqBody { repeated D769ConfigSeq configList = 1; optional D769DeviceInfo deviceInfo = 2; optional string info = 3; optional string province = 4; optional string city = 5; optional int32 reqDebugMsg = 6; optional QueryUinPackageUsageReq queryUinPackageUsageReq = 101; } message D769RspBody { optional uint32 result = 1; repeated D769ConfigSeq configList = 2; optional QueryUinPackageUsageRsp queryUinPackageUsageRsp = 101; } message Screen { optional string model = 1; optional uint32 width = 2; optional uint32 height = 3; optional uint32 dpi = 4; optional bool multiTouch = 5; } message Storage { optional uint64 builtin = 1; optional uint64 external = 2; } message UinPackageUsedInfo { optional uint32 ruleId = 1; optional string author = 2; optional string url = 3; optional uint64 uinNum = 4; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0x88d.proto ================================================ syntax = "proto2"; // 似乎查询服务端是通过 exists flag 来返回 group info 的 这地方只能用 proto2 package oidb; message D88DGroupHeadPortraitInfo { optional uint32 picId = 1; } message D88DGroupHeadPortrait { optional uint32 picCount = 1; repeated D88DGroupHeadPortraitInfo msgInfo = 2; optional uint32 defaultId = 3; optional uint32 verifyingPicCnt = 4; repeated D88DGroupHeadPortraitInfo msgVerifyingPicInfo = 5; } message D88DGroupExInfoOnly { optional uint32 tribeId = 1; optional uint32 moneyForAddGroup = 2; }; message D88DGroupInfo { optional uint64 groupOwner = 1; optional uint32 groupCreateTime = 2; optional uint32 groupFlag = 3; optional uint32 groupFlagExt = 4; optional uint32 groupMemberMaxNum = 5; optional uint32 groupMemberNum = 6; optional uint32 groupOption = 7; optional uint32 groupClassExt = 8; optional uint32 groupSpecialClass = 9; optional uint32 groupLevel = 10; optional uint32 groupFace = 11; optional uint32 groupDefaultPage = 12; optional uint32 groupInfoSeq = 13; optional uint32 groupRoamingTime = 14; optional bytes groupName = 15; optional bytes groupMemo = 16; optional bytes groupFingerMemo = 17; optional bytes groupClassText = 18; repeated uint32 groupAllianceCode = 19; optional uint32 groupExtraAadmNum = 20; optional uint64 groupUin = 21; optional uint32 groupCurMsgSeq = 22; optional uint32 groupLastMsgTime = 23; optional bytes groupQuestion = 24; optional bytes groupAnswer = 25; optional uint32 groupVisitorMaxNum = 26; optional uint32 groupVisitorCurNum = 27; optional uint32 levelNameSeq = 28; optional uint32 groupAdminMaxNum = 29; optional uint32 groupAioSkinTimestamp = 30; optional uint32 groupBoardSkinTimestamp = 31; optional bytes groupAioSkinUrl = 32; optional bytes groupBoardSkinUrl = 33; optional uint32 groupCoverSkinTimestamp = 34; optional bytes groupCoverSkinUrl = 35; optional uint32 groupGrade = 36; optional uint32 activeMemberNum = 37; optional uint32 certificationType = 38; optional bytes certificationText = 39; optional bytes groupRichFingerMemo = 40; repeated D88DTagRecord tagRecord = 41; optional D88DGroupGeoInfo groupGeoInfo = 42; optional uint32 headPortraitSeq = 43; optional D88DGroupHeadPortrait msgHeadPortrait = 44; optional uint32 shutupTimestamp = 45 ; optional uint32 shutupTimestampMe = 46 ; optional uint32 createSourceFlag = 47 ; optional uint32 cmduinMsgSeq = 48; optional uint32 cmduinJoinTime = 49; optional uint32 cmduinUinFlag = 50; optional uint32 cmduinFlagEx = 51; optional uint32 cmduinNewMobileFlag = 52; optional uint32 cmduinReadMsgSeq = 53; optional uint32 cmduinLastMsgTime = 54; optional uint32 groupTypeFlag = 55; optional uint32 appPrivilegeFlag = 56; optional D88DGroupExInfoOnly stGroupExInfo = 57; optional uint32 groupSecLevel = 58; optional uint32 groupSecLevelInfo = 59; optional uint32 cmduinPrivilege = 60; optional bytes poidInfo = 61; optional uint32 cmduinFlagEx2 = 62; optional uint64 confUin = 63; optional uint32 confMaxMsgSeq = 64; optional uint32 confToGroupTime = 65; optional uint32 passwordRedbagTime = 66; optional uint64 subscriptionUin = 67; optional uint32 memberListChangeSeq = 68; optional uint32 membercardSeq = 69; optional uint64 rootId = 70; optional uint64 parentId = 71; optional uint32 teamSeq = 72; optional uint64 historyMsgBeginTime = 73; optional uint64 inviteNoAuthNumLimit = 74; optional uint32 cmduinHistoryMsgSeq = 75; optional uint32 cmduinJoinMsgSeq = 76; optional uint32 groupFlagext3 = 77; optional uint32 groupOpenAppid = 78; optional uint32 isConfGroup = 79; optional uint32 isModifyConfGroupFace = 80; optional uint32 isModifyConfGroupName = 81; optional uint32 noFingerOpenFlag = 82; optional uint32 noCodeFingerOpenFlag = 83; }; message ReqGroupInfo { optional uint64 groupCode = 1; optional D88DGroupInfo stgroupinfo = 2; optional uint32 lastGetGroupNameTime = 3; }; message D88DReqBody { optional uint32 appId = 1; repeated ReqGroupInfo reqGroupInfo = 2; optional uint32 pcClientVersion = 3; }; message RspGroupInfo { optional uint64 groupCode = 1; optional uint32 result = 2; optional D88DGroupInfo groupInfo = 3; }; message D88DRspBody { repeated RspGroupInfo rspGroupInfo = 1; optional bytes strErrorInfo = 2; }; message D88DTagRecord { optional uint64 fromUin = 1; optional uint64 groupCode = 2; optional bytes tagId = 3; optional uint64 setTime = 4; optional uint32 goodNum = 5; optional uint32 badNum = 6; optional uint32 tagLen = 7; optional bytes tagValue = 8; }; message D88DGroupGeoInfo { optional uint64 owneruin = 1; optional uint32 settime = 2; optional uint32 cityid = 3; optional int64 longitude = 4; optional int64 latitude = 5; optional bytes geocontent = 6; optional uint64 poiId = 7; }; ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0x8a7.proto ================================================ syntax = "proto2"; package oidb; message D8A7ReqBody { optional uint32 subCmd = 1; optional uint32 limitIntervalTypeForUin = 2; optional uint32 limitIntervalTypeForGroup = 3; optional uint64 uin = 4; optional uint64 groupCode = 5; } message D8A7RspBody { optional bool canAtAll = 1; optional uint32 remainAtAllCountForUin = 2; optional uint32 remainAtAllCountForGroup = 3; optional bytes promptMsg1 = 4; optional bytes promptMsg2 = 5; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0x8fc.proto ================================================ syntax = "proto2"; package oidb; message D8FCReqBody { optional int64 groupCode = 1; optional int32 showFlag = 2; repeated D8FCMemberInfo memLevelInfo = 3; repeated D8FCLevelName levelName = 4; optional int32 updateTime = 5; optional int32 officeMode = 6; optional int32 groupOpenAppid = 7; optional D8FCClientInfo msgClientInfo = 8; optional bytes authKey = 9; } message D8FCMemberInfo { optional int64 uin = 1; optional int32 point = 2; optional int32 activeDay = 3; optional int32 level = 4; optional bytes specialTitle = 5; optional int32 specialTitleExpireTime = 6; optional bytes uinName = 7; optional bytes memberCardName = 8; optional bytes phone = 9; optional bytes email = 10; optional bytes remark = 11; optional int32 gender = 12; optional bytes job = 13; optional int32 tribeLevel = 14; optional int32 tribePoint = 15; repeated D8FCCardNameElem richCardName = 16; optional bytes commRichCardName = 17; } message D8FCCardNameElem { optional int32 enumCardType = 1; optional bytes value = 2; } message D8FCLevelName { optional int32 level = 1; optional string name = 2; } message D8FCClientInfo { optional int32 implat = 1; optional string ingClientver = 2; } message D8FCCommCardNameBuf { repeated D8FCRichCardNameElem richCardName = 1; } message D8FCRichCardNameElem { optional bytes ctrl = 1; optional bytes text = 2; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0x990.proto ================================================ syntax = "proto3"; package oidb; message TranslateReqBody { // TranslateReq translate_req = 1; BatchTranslateReq batch_translate_req = 2; } message TranslateRspBody { // TranslateRsp translate_rsp = 1; BatchTranslateRsp batch_translate_rsp = 2; } message BatchTranslateReq { string src_language = 1; string dst_language = 2; repeated string src_text_list = 3; } message BatchTranslateRsp { int32 error_code = 1; bytes error_msg = 2; string src_language = 3; string dst_language = 4; repeated string src_text_list = 5; repeated string dst_text_list = 6; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0xb77.proto ================================================ syntax = "proto3"; package oidb; message DB77ReqBody { uint64 appId = 1; uint32 appType = 2; uint32 msgStyle = 3; uint64 senderUin = 4; DB77ClientInfo clientInfo = 5; string textMsg = 6; DB77ExtInfo extInfo = 7; uint32 sendType = 10; uint64 recvUin = 11; DB77RichMsgBody richMsgBody = 12; uint64 recvGuildId = 19; } message DB77ClientInfo { uint32 platform = 1; string sdkVersion = 2; string androidPackageName = 3; string androidSignature = 4; string iosBundleId = 5; string pcSign = 6; } message DB77ExtInfo { repeated uint32 customFeatureId = 11; string apnsWording = 12; uint32 groupSaveDbFlag = 13; uint32 receiverAppId = 14; uint64 msgSeq = 15; } message DB77RichMsgBody { string title = 10; string summary = 11; string brief = 12; string url = 13; string pictureUrl = 14; string action = 15; string musicUrl = 16; //ImageInfo imageInfo = 17; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0xe07.proto ================================================ syntax = "proto3"; package oidb; message DE07ReqBody { int32 version = 1; int32 client = 2; int32 entrance = 3; OCRReqBody ocrReqBody = 10; } message OCRReqBody { string imageUrl = 1; string languageType = 2; string scene = 3; string originMd5 = 10; string afterCompressMd5 = 11; int32 afterCompressFileSize = 12; int32 afterCompressWeight = 13; int32 afterCompressHeight = 14; bool isCut = 15; } message DE07RspBody { int32 retCode = 1; string errMsg = 2; string wording = 3; OCRRspBody ocrRspBody = 10; } message TextDetection { string detectedText = 1; int32 confidence = 2; Polygon polygon = 3; string advancedInfo = 4; } message Polygon { repeated Coordinate coordinates = 1; } message Coordinate { int32 X = 1; int32 Y = 2; } message Language { string language = 1; string languageDesc = 2; } message OCRRspBody { repeated TextDetection textDetections = 1; string language = 2; string requestId = 3; repeated string ocrLanguageList = 101; repeated string dstTranslateLanguageList = 102; repeated Language languageList = 103; int32 afterCompressWeight = 111; int32 afterCompressHeight = 112; } ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0xeac.proto ================================================ syntax = "proto3"; package oidb; /* message ArkMsg { optional string appName = 1; optional string json = 2; } message BatchReqBody { optional uint64 groupCode = 1; repeated MsgInfo msgs = 2; } message BatchRspBody { optional string wording = 1; optional uint32 errorCode = 2; optional int32 succCnt = 3; repeated MsgProcessInfo procInfos = 4; optional uint32 digestTime = 5; } message DigestMsg { optional uint64 groupCode = 1; optional uint32 seq = 2; optional uint32 random = 3; repeated MsgElem content = 4; optional uint64 textSize = 5; optional uint64 picSize = 6; optional uint64 videoSize = 7; optional uint64 senderUin = 8; optional uint32 senderTime = 9; optional uint64 addDigestUin = 10; optional uint32 addDigestTime = 11; optional uint32 startTime = 12; optional uint32 latestMsgSeq = 13; optional uint32 opType = 14; } message FaceMsg { optional uint32 index = 1; optional string text = 2; } message GroupFileMsg { optional bytes fileName = 1; optional uint32 busId = 2; optional string fileId = 3; optional uint64 fileSize = 4; optional uint64 deadTime = 5; optional bytes fileSha1 = 6; optional bytes ext = 7; optional bytes fileMd5 = 8; } message ImageMsg { optional string md5 = 1; optional string uuid = 2; optional uint32 imgType = 3; optional uint32 fileSize = 4; optional uint32 width = 5; optional uint32 height = 6; optional uint32 fileId = 101; optional uint32 serverIp = 102; optional uint32 serverPort = 103; optional string filePath = 104; optional string thumbUrl = 201; optional string originalUrl = 202; optional string resaveUrl = 203; } message MsgElem { optional uint32 type = 1; optional TextMsg textMsg = 11; optional FaceMsg faceMsg = 12; optional ImageMsg imageMsg = 13; optional GroupFileMsg groupFileMsg = 14; optional ShareMsg shareMsg = 15; optional RichMsg richMsg = 16; optional ArkMsg arkMsg = 17; } message MsgInfo { optional uint32 seq = 1; optional uint32 random = 2; } message MsgProcessInfo { optional MsgInfo msg = 1; optional uint32 errorCode = 2; optional uint64 digestUin = 3; optional uint32 digestTime = 4; } */ message EACReqBody { optional uint64 groupCode = 1; optional uint32 seq = 2; optional uint32 random = 3; } /* message RichMsg { optional uint32 serviceId = 1; optional string xml = 2; optional string longMsgResid = 3; } */ message EACRspBody { optional string wording = 1; optional uint64 digestUin = 2; optional uint32 digestTime = 3; //optional DigestMsg msg = 4; optional uint32 errorCode = 10; } /* message ShareMsg { optional string type = 1; optional string title = 2; optional string summary = 3; optional string brief = 4; optional string url = 5; optional string pictureUrl = 6; optional string action = 7; optional string source = 8; optional string sourceUrl = 9; } message TextMsg { optional bytes str = 1; } */ ================================================ FILE: ricq-guild/src/protocol/core/oidb/oidb0xeb7.proto ================================================ syntax = "proto2"; package oidb; // DEB7 prefix message DEB7ReqBody { optional StSignInStatusReq signInStatusReq = 1; optional StSignInWriteReq signInWriteReq = 2; } message DEB7Ret { optional uint32 code = 1; optional string msg = 2; } message DEB7RspBody { optional StSignInStatusRsp signInStatusRsp = 1; optional StSignInWriteRsp signInWriteRsp = 2; } message SignInStatusBase { optional uint32 status = 1; optional int64 currentTimeStamp = 2; } message SignInStatusDoneInfo { optional string leftTitleWrod = 1; optional string rightDescWord = 2; repeated string belowPortraitWords = 3; optional string recordUrl = 4; } message SignInStatusGroupScore { optional string groupScoreWord = 1; optional string scoreUrl = 2; } message SignInStatusNotInfo { optional string buttonWord = 1; optional string signDescWordLeft = 2; optional string signDescWordRight = 3; } message SignInStatusYesterdayFirst { optional string yesterdayFirstUid = 1; optional string yesterdayWord = 2; optional string yesterdayNick = 3; } message StDaySignedInfo { optional string uid = 1; optional string uidGroupNick = 2; optional int64 signedTimeStamp = 3; optional int32 signInRank = 4; } message StDaySignedListReq { optional string dayYmd = 1; optional string uid = 2; optional string groupId = 3; optional int32 offset = 4; optional int32 limit = 5; } message StDaySignedListRsp { optional DEB7Ret ret = 1; repeated StDaySignedPage page = 2; } message StDaySignedPage { repeated StDaySignedInfo infos = 1; optional int32 offset = 2; optional int32 total = 3; } message StKingSignedInfo { optional string uid = 1; optional string groupNick = 2; optional int64 signedTimeStamp = 3; optional int32 signedCount = 4; } message StKingSignedListReq { optional string uid = 1; optional string groupId = 2; } message StKingSignedListRsp { optional DEB7Ret ret = 1; optional StKingSignedInfo yesterdayFirst = 2; repeated StKingSignedInfo topSignedTotal = 3; repeated StKingSignedInfo topSignedContinue = 4; } message StSignInRecordDaySigned { optional float daySignedRatio = 1; optional int32 dayTotalSignedUid = 2; optional StDaySignedPage daySignedPage = 3; optional string daySignedUrl = 4; } message StSignInRecordKing { optional StKingSignedInfo yesterdayFirst = 1; repeated StKingSignedInfo topSignedTotal = 2; repeated StKingSignedInfo topSignedContinue = 3; optional string kingUrl = 4; } message StSignInRecordReq { optional string dayYmd = 1; optional string uid = 2; optional string groupId = 3; } message StSignInRecordRsp { optional DEB7Ret ret = 1; optional SignInStatusBase base = 2; optional StSignInRecordUser userRecord = 3; optional StSignInRecordDaySigned daySigned = 4; optional StSignInRecordKing kingRecord = 5; optional StViewGroupLevel level = 6; } message StSignInRecordUser { optional int32 totalSignedDays = 2; optional int64 earliestSignedTimeStamp = 3; optional int64 continueSignedDays = 4; repeated string historySignedDays = 5; optional string groupName = 6; } message StSignInStatusReq { optional string uid = 1; optional string groupId = 2; optional uint32 scene = 3; optional string clientVersion = 4; } message StSignInStatusRsp { optional DEB7Ret ret = 1; optional SignInStatusBase base = 2; optional SignInStatusYesterdayFirst yesterday = 3; optional SignInStatusNotInfo notInfo = 4; optional SignInStatusDoneInfo doneInfo = 5; optional SignInStatusGroupScore groupScore = 6; optional string mantleUrl = 7; optional string backgroundUrl = 8; } message StSignInWriteReq { optional string uid = 1; optional string groupId = 2; optional string clientVersion = 3; } message StSignInWriteRsp { optional DEB7Ret ret = 1; optional SignInStatusDoneInfo doneInfo = 2; optional SignInStatusGroupScore groupScore = 3; } message StViewGroupLevel { optional string title = 1; optional string url = 2; } ================================================ FILE: ricq-guild/src/protocol/core/online_status/OnlineStatusExtInfo.java.proto ================================================ syntax = "proto2"; package online_status; message AutoStateBizInfo { optional uint64 updateTime = 1; } message CustomStatus { optional uint64 faceIndex = 1; optional string wording = 2; optional uint64 faceType = 3; } message WeatherBizInfo { optional string weatherType = 1; optional string weatherTypeId = 2; optional uint32 adcode = 3; optional uint64 updateTime = 4; optional string city = 5; optional string area = 6; optional string temper = 7; optional uint32 flag = 8; optional string weatherDesc = 9; } message ZodiacBizInfo { optional string todayTrend = 1; optional string tomorrowTrend = 2; optional string miniapp = 3; optional string todayDate = 4; optional string luckyColor = 5; optional string luckyNumber = 6; } ================================================ FILE: ricq-guild/src/protocol/core/profilecard/busi.proto ================================================ syntax = "proto2"; package profilecard; message BusiColor { optional int32 r = 1; optional int32 g = 2; optional int32 b = 3; } message BusiComm { optional int32 ver = 1; optional int32 seq = 2; optional int64 fromuin = 3; optional int64 touin = 4; optional int32 service = 5; optional int32 sessionType = 6; optional bytes sessionKey = 7; optional int32 clientIp = 8; optional BusiUi display = 9; optional int32 result = 10; optional string errMsg = 11; optional int32 platform = 12; optional string qqver = 13; optional int32 build = 14; optional BusiLoginSig msgLoginSig = 15; optional int32 version = 17; optional BusiUinInfo msgUinInfo = 18; optional BusiRichUi msgRichDisplay = 19; } message BusiCommonReq { optional string serviceCmd = 1; optional BusiVisitorCountReq vcReq = 2; optional BusiHideRecordsReq hrReq = 3; } message BusiDetailRecord { optional int32 fuin = 1; optional int32 source = 2; optional int32 vtime = 3; optional int32 mod = 4; optional int32 hideFlag = 5; } message BusiHideRecordsReq { optional int32 huin = 1; optional int32 fuin = 2; repeated BusiDetailRecord records = 3; } message BusiLabel { optional bytes name = 1; optional int32 enumType = 2; optional BusiColor textColor = 3; optional BusiColor edgingColor = 4; optional int32 labelAttr = 5; optional int32 labelType = 6; } message BusiLoginSig { optional int32 type = 1; optional bytes sig = 2; optional int32 appid = 3; } message BusiRichUi { optional string name = 1; optional string serviceUrl = 2; //repeated UiInfo uiList = 3; } message BusiUi { optional string url = 1; optional string title = 2; optional string content = 3; optional string jumpUrl = 4; } message BusiUinInfo { optional int64 int64Longitude = 1; optional int64 int64Latitude = 2; } message BusiVisitorCountReq { optional int32 requireuin = 1; optional int32 operuin = 2; optional int32 mod = 3; optional int32 reportFlag = 4; } message BusiVisitorCountRsp { optional int32 requireuin = 1; optional int32 totalLike = 2; optional int32 totalView = 3; optional int32 hotValue = 4; optional int32 redValue = 5; optional int32 hotDiff = 6; } ================================================ FILE: ricq-guild/src/protocol/core/profilecard/gate.proto ================================================ syntax = "proto2"; package profilecard; message GateCommTaskInfo { optional int32 appid = 1; optional bytes taskData = 2; } message GateGetGiftListReq { optional int32 uin = 1; } message GateGetGiftListRsp { repeated string giftUrl = 1; optional string customUrl = 2; optional string desc = 3; optional bool isOn = 4; } message GateGetVipCareReq { optional int64 uin = 1; } message GateGetVipCareRsp { optional int32 buss = 1; optional int32 notice = 2; } message GateOidbFlagInfo { optional int32 fieled = 1; optional bytes byetsValue = 2; } message GatePrivilegeBaseInfoReq { optional int64 uReqUin = 1; } message GatePrivilegeBaseInfoRsp { optional bytes msg = 1; optional bytes jumpUrl = 2; repeated GatePrivilegeInfo vOpenPriv = 3; repeated GatePrivilegeInfo vClosePriv = 4; optional int32 uIsGrayUsr = 5; } message GatePrivilegeInfo { optional int32 iType = 1; optional int32 iSort = 2; optional int32 iFeeType = 3; optional int32 iLevel = 4; optional int32 iFlag = 5; optional bytes iconUrl = 6; optional bytes deluxeIconUrl = 7; optional bytes jumpUrl = 8; optional int32 iIsBig = 9; } message GateVaProfileGateReq { optional int32 uCmd = 1; optional GatePrivilegeBaseInfoReq stPrivilegeReq = 2; optional GateGetGiftListReq stGiftReq = 3; repeated GateCommTaskInfo taskItem = 4; repeated GateOidbFlagInfo oidbFlag = 5; optional GateGetVipCareReq stVipCare = 6; } message GateQidInfoItem { optional string qid = 1; optional string url = 2; optional string color = 3; optional string logoUrl = 4; } message GateVaProfileGateRsp { optional int32 iRetCode = 1; optional bytes sRetMsg = 2; optional GatePrivilegeBaseInfoRsp stPrivilegeRsp = 3; optional GateGetGiftListRsp stGiftRsp = 4; repeated GateCommTaskInfo taskItem = 5; repeated GateOidbFlagInfo oidbFlag = 6; optional GateGetVipCareRsp stVipCare = 7; optional GateQidInfoItem qidInfo = 9; } ================================================ FILE: ricq-guild/src/protocol/core/short_video/short_video.proto ================================================ syntax = "proto3"; package short_video; message ShortVideoReqBody { int32 cmd = 1; int32 seq = 2; ShortVideoUploadReq pttShortVideoUploadReq = 3; ShortVideoDownloadReq pttShortVideoDownloadReq = 4; repeated ShortVideoExtensionReq extensionReq = 100; } message ShortVideoRspBody { int32 cmd = 1; int32 seq = 2; ShortVideoUploadRsp pttShortVideoUploadRsp = 3; ShortVideoDownloadRsp pttShortVideoDownloadRsp = 4; } message ShortVideoUploadReq { int64 fromUin = 1; int64 toUin = 2; int32 chatType = 3; int32 clientType = 4; ShortVideoFileInfo info = 5; int64 groupCode = 6; int32 agentType = 7; int32 businessType = 8; int32 supportLargeSize = 20; } message ShortVideoDownloadReq { int64 fromUin = 1; int64 toUin = 2; int32 chatType = 3; int32 clientType = 4; string fileId = 5; int64 groupCode = 6; int32 agentType = 7; bytes fileMd5 = 8; int32 businessType = 9; int32 fileType = 10; int32 downType = 11; int32 sceneType = 12; } message ShortVideoDownloadRsp { int32 retCode = 1; string retMsg = 2; repeated ShortVideoIpList sameAreaOutAddr = 3; repeated ShortVideoIpList diffAreaOutAddr = 4; bytes downloadKey = 5; bytes fileMd5 = 6; repeated ShortVideoIpList sameAreaInnerAddr = 7; repeated ShortVideoIpList diffAreaInnerAddr = 8; ShortVideoAddr downloadAddr = 9; bytes encryptKey = 10; } message ShortVideoUploadRsp { int32 retCode = 1; string retMsg = 2; repeated ShortVideoIpList sameAreaOutAddr = 3; repeated ShortVideoIpList diffAreaOutAddr = 4; bytes fileId = 5; bytes uKey = 6; int32 fileExists = 7; repeated ShortVideoIpList sameAreaInnerAddr = 8; repeated ShortVideoIpList diffAreaInnerAddr = 9; repeated DataHole dataHole = 10; } message ShortVideoFileInfo { string fileName = 1; bytes fileMd5 = 2; bytes thumbFileMd5 = 3; int64 fileSize = 4; int32 fileResLength = 5; int32 fileResWidth = 6; int32 fileFormat = 7; int32 fileTime = 8; int64 thumbFileSize = 9; } message DataHole { int64 begin = 1; int64 end = 2; } message ShortVideoIpList { int32 ip = 1; int32 port = 2; } message ShortVideoAddr { repeated string host = 10; string urlArgs = 11; //repeated string domain = 13; } message ShortVideoExtensionReq { int32 subBusiType = 1; int32 userCnt = 2; } ================================================ FILE: ricq-guild/src/protocol/core/sig_act/sig_act.proto ================================================ syntax = "proto2"; package sig_act; message Platform { optional int64 platform = 1; optional string osver = 2; optional string mqqver = 3; } message ReqBody { optional uint32 cmd = 1; optional uint64 seq = 2; optional Platform plf = 3; optional SigactReq req = 4; optional SigauthReq authReq = 5; optional uint32 source = 6; } message RspBody { optional int32 ret = 1; optional string desc = 2; optional uint32 cmd = 3; optional uint64 seq = 4; optional SigactRsp rsp = 5; optional SigauthRsp authRsp = 6; } message SigactReq { optional uint64 uinDisable = 1; optional int32 actid = 2; optional int32 acttype = 3; } message SigactRsp { optional uint64 uin = 1; optional uint32 rank = 2; } message SigauthReq { optional uint64 uinDisable = 1; optional int32 itemid = 2; optional int32 len = 3; optional bytes data = 4; optional int32 fontid = 5; } message SigauthRsp { optional bytes result = 1; optional string url = 2; optional TipsInfo tipsInfo = 3; optional int32 authfailedAppid = 4; message TipsInfo { optional bool valid = 1; optional int32 ret = 2; optional uint32 type = 3; optional string titleWording = 4; optional string wording = 5; optional string rightBtnWording = 6; optional string leftBtnWording = 7; optional string vipType = 8; optional uint32 vipMonth = 9; optional string url = 10; } } ================================================ FILE: ricq-guild/src/protocol/core/structmsg/structmsg.proto ================================================ syntax = "proto3"; //option go_package = "./;structmsg"; package structmsg; message AddFrdSNInfo { int32 notSeeDynamic = 1; int32 setSn = 2; } message FlagInfo { int32 grpMsgKickAdmin = 1; int32 grpMsgHiddenGrp = 2; int32 grpMsgWordingDown = 3; int32 frdMsgGetBusiCard = 4; int32 grpMsgGetOfficialAccount = 5; int32 grpMsgGetPayInGroup = 6; int32 frdMsgDiscuss2ManyChat = 7; int32 grpMsgNotAllowJoinGrpInviteNotFrd = 8; int32 frdMsgNeedWaitingMsg = 9; int32 frdMsgUint32NeedAllUnreadMsg = 10; int32 grpMsgNeedAutoAdminWording = 11; int32 grpMsgGetTransferGroupMsgFlag = 12; int32 grpMsgGetQuitPayGroupMsgFlag = 13; int32 grpMsgSupportInviteAutoJoin = 14; int32 grpMsgMaskInviteAutoJoin = 15; int32 grpMsgGetDisbandedByAdmin = 16; int32 grpMsgGetC2cInviteJoinGroup = 17; } message FriendInfo { string msgJointFriend = 1; string msgBlacklist = 2; } message SGroupInfo { int32 groupAuthType = 1; int32 displayAction = 2; string msgAlert = 3; string msgDetailAlert = 4; string msgOtherAdminDone = 5; int32 appPrivilegeFlag = 6; } message MsgInviteExt { int32 srcType = 1; int64 srcCode = 2; int32 waitState = 3; } message MsgPayGroupExt { int64 joinGrpTime = 1; int64 quitGrpTime = 2; } message ReqNextSystemMsg { int32 msgNum = 1; int64 followingFriendSeq = 2; int64 followingGroupSeq = 3; int32 checktype = 4; FlagInfo flag = 5; int32 language = 6; int32 version = 7; int64 friendMsgTypeFlag = 8; } message ReqSystemMsg { int32 msgNum = 1; int64 latestFriendSeq = 2; int64 latestGroupSeq = 3; int32 version = 4; int32 language = 5; } message ReqSystemMsgAction { int32 msgType = 1; int64 msgSeq = 2; int64 reqUin = 3; int32 subType = 4; int32 srcId = 5; int32 subSrcId = 6; int32 groupMsgType = 7; SystemMsgActionInfo actionInfo = 8; int32 language = 9; } message ReqSystemMsgNew { int32 msgNum = 1; int64 latestFriendSeq = 2; int64 latestGroupSeq = 3; int32 version = 4; int32 checktype = 5; FlagInfo flag = 6; int32 language = 7; bool isGetFrdRibbon = 8; bool isGetGrpRibbon = 9; int64 friendMsgTypeFlag = 10; int32 reqMsgType = 11; } message ReqSystemMsgRead { int64 latestFriendSeq = 1; int64 latestGroupSeq = 2; int32 type = 3; int32 checktype = 4; } message RspHead { int32 result = 1; string msgFail = 2; } message RspNextSystemMsg { RspHead head = 1; repeated StructMsg msgs = 2; int64 followingFriendSeq = 3; int64 followingGroupSeq = 4; int32 checktype = 5; string gameNick = 100; bytes undecidForQim = 101; int32 unReadCount3 = 102; } message RspSystemMsg { RspHead head = 1; repeated StructMsg msgs = 2; int32 unreadCount = 3; int64 latestFriendSeq = 4; int64 latestGroupSeq = 5; int64 followingFriendSeq = 6; int64 followingGroupSeq = 7; string msgDisplay = 8; } message RspSystemMsgAction { RspHead head = 1; string msgDetail = 2; int32 type = 3; string msgInvalidDecided = 5; int32 remarkResult = 6; } message RspSystemMsgNew { RspHead head = 1; int32 unreadFriendCount = 2; int32 unreadGroupCount = 3; int64 latestFriendSeq = 4; int64 latestGroupSeq = 5; int64 followingFriendSeq = 6; int64 followingGroupSeq = 7; repeated StructMsg friendmsgs = 9; repeated StructMsg groupmsgs = 10; StructMsg msgRibbonFriend = 11; StructMsg msgRibbonGroup = 12; string msgDisplay = 13; string grpMsgDisplay = 14; int32 over = 15; int32 checktype = 20; string gameNick = 100; bytes undecidForQim = 101; int32 unReadCount3 = 102; } message RspSystemMsgRead { RspHead head = 1; int32 type = 2; int32 checktype = 3; } message StructMsg { int32 version = 1; int32 msgType = 2; int64 msgSeq = 3; int64 msgTime = 4; int64 reqUin = 5; int32 unreadFlag = 6; SystemMsg msg = 50; } message SystemMsg { int32 subType = 1; string msgTitle = 2; string msgDescribe = 3; string msgAdditional = 4; string msgSource = 5; string msgDecided = 6; int32 srcId = 7; int32 subSrcId = 8; repeated SystemMsgAction actions = 9; int64 groupCode = 10; int64 actionUin = 11; int32 groupMsgType = 12; int32 groupInviterRole = 13; FriendInfo friendInfo = 14; SGroupInfo groupInfo = 15; int64 actorUin = 16; string msgActorDescribe = 17; string msgAdditionalList = 18; int32 relation = 19; int32 reqsubtype = 20; int64 cloneUin = 21; int64 discussUin = 22; int64 eimGroupId = 23; MsgInviteExt msgInviteExtinfo = 24; MsgPayGroupExt msgPayGroupExtinfo = 25; int32 sourceFlag = 26; bytes gameNick = 27; bytes gameMsg = 28; int32 groupFlagext3 = 29; int64 groupOwnerUin = 30; int32 doubtFlag = 31; bytes warningTips = 32; bytes nameMore = 33; int32 reqUinFaceid = 50; string reqUinNick = 51; string groupName = 52; string actionUinNick = 53; string msgQna = 54; string msgDetail = 55; int32 groupExtFlag = 57; string actorUinNick = 58; string picUrl = 59; string cloneUinNick = 60; string reqUinBusinessCard = 61; string eimGroupIdName = 63; string reqUinPreRemark = 64; string actionUinQqNick = 65; string actionUinRemark = 66; int32 reqUinGender = 67; int32 reqUinAge = 68; int32 c2cInviteJoinGroupFlag = 69; int32 cardSwitch = 101; } message SystemMsgAction { string name = 1; string result = 2; int32 action = 3; SystemMsgActionInfo actionInfo = 4; string detailName = 5; } message SystemMsgActionInfo { int32 type = 1; int64 groupCode = 2; bytes sig = 3; string msg = 50; int32 groupId = 51; string remark = 52; bool blacklist = 53; AddFrdSNInfo addFrdSNInfo = 54; } ================================================ FILE: ricq-guild/src/protocol/mod.rs ================================================ use crate::protocol::protobuf::{ChannelMsg, GuildNode}; use bytes::Bytes; use dynamic_protobuf::{dynamic_message, DynamicMessage}; use ricq_core::common::RQAddr; use ricq_core::msg::{MessageChainBuilder, MessageElem}; #[derive(Clone, Debug, Default)] pub struct FirstViewResponse { pub guild_count: u32, pub self_tinyid: u64, pub direct_message_switch: u32, pub direct_message_guild_count: u32, } #[derive(Clone, Debug, Default)] pub struct FirstViewMessage { pub push_flag: u32, pub guild_nodes: Vec, pub channel_msgs: Vec, pub get_msg_time: u64, pub direct_message_guild_nodes: Vec, } #[derive(Clone, Debug, Default)] pub struct FirstView { pub response: FirstViewResponse, pub message: FirstViewMessage, } #[derive(Clone, Debug, Default)] pub struct GuildUserProfile { pub tiny_id: u64, pub nickname: String, pub avatar_url: String, pub join_time: i64, } #[derive(Clone, Debug, Default)] pub struct GuildSelfProfile { pub tiny_id: u64, pub nickname: String, pub avatar_url: String, } #[derive(Clone, Debug, Default)] pub struct GuildImage { pub file_id: u64, pub file_name: String, pub size: u32, pub width: u32, pub height: u32, pub image_type: i32, pub download_index: Vec, pub md5: Vec, pub server_ip: u32, pub server_port: u16, } impl ricq_core::msg::PushElem for GuildImage { fn push_to(img: Self, vec: &mut Vec) { vec.push(MessageElem::CustomFace(ricq_core::pb::msg::CustomFace { file_path: Some(img.file_name), file_id: Some(img.file_id as _), server_ip: Some(img.server_ip), server_port: Some(img.server_port as _), file_type: Some(66), signature: None, useful: Some(1), md5: Some(img.md5), biz_type: Some(0), image_type: Some(img.image_type), width: Some(img.width), height: Some(img.height), source: Some(200), size: Some(img.size), origin: Some(1), thumb_width: Some((img.width * 10 / 3) as _), thumb_height: Some((img.height * 10 / 3) as _), show_len: Some(0), download_len: Some(0), pb_reserve: { let m = dynamic_message! { 1 => 0u32, 2 => 0u32, 6 => DynamicMessage::new(), 10 => 0u32, 15 => 1u32, // or 8? 20 => Bytes::from(img.download_index) } .encode_to_vec(); Some(m) }, ..Default::default() })); } } impl ricq_core::msg::PushBuilder for GuildImage { fn push_builder(elem: Self, builder: &mut MessageChainBuilder) { ricq_core::msg::PushElem::push_to(elem, &mut builder.elems); } } #[derive(Debug, Clone)] pub enum GuildImageStoreResp { Exist { file_id: u64, addrs: Vec, download_index: Vec, }, NotExist { file_id: u64, upload_key: Vec, upload_addrs: Vec, download_index: Vec, }, } #[allow(clippy::all)] pub mod protobuf { include!(concat!(env!("OUT_DIR"), "/", "guild.rs")); } ================================================ FILE: ricq-guild/src/protocol/protobuf/GuildChannelBase.proto ================================================ syntax = "proto2"; package guild; import "MsgResponsesSvr.proto"; message ChannelUserInfo { optional ClientIdentity clientIdentity = 1; optional uint32 memberType = 2; optional ChannelUserPermission permission = 3; repeated BaseRoleGroupInfo roleGroups = 4; } message ChannelUserPermission { optional bool allowReadFeed = 1; optional bool allowWriteFeed = 2; } message ClientIdentity { optional uint32 clientId = 1; optional string desc = 2; } message BaseGuildInfo { optional uint64 guildId = 1; optional string name = 2; optional uint64 joinTime = 3; } message BaseRoleGroupInfo { optional uint64 roleId = 1; optional string name = 2; optional uint32 color = 3; } message StChannelInfo { optional StChannelSign sign = 1; optional string name = 2; optional string iconUrl = 3; } message StChannelSign { optional uint64 guildId = 1; optional uint64 channelId = 2; } /* message StEmojiReaction { optional string emojiId = 1; optional uint64 emojiType = 2; optional uint64 cnt = 3; optional bool isClicked = 4; optional bool isDefaultEmoji = 10001; } */ message StEmotionReactionInfo { optional string id = 1; repeated EmojiReaction emojiReactionList = 2; } message StCommonExt { repeated CommonEntry mapInfo = 1; optional string attachInfo = 2; repeated BytesEntry mapBytesInfo = 3; } message BytesEntry { optional string key = 1; optional bytes value = 2; } message CommonEntry { optional string key = 1; optional string value = 2; } ================================================ FILE: ricq-guild/src/protocol/protobuf/GuildFeedCloudMeta.proto ================================================ syntax = "proto2"; package guild; import "GuildChannelBase.proto"; message ContentMetaData { optional RichTextContentCount count = 1; optional int64 ContentID = 2; } message FeedMetaData { optional ContentMetaData content = 1; optional uint64 lastModifiedTime = 2; } message FeedRedTouchTransInfo { optional string feedId = 1; optional string author = 2; optional int64 createTs = 3; optional int32 msgType = 4; optional int32 pageType = 5; optional int32 redType = 6; optional int32 insertPageType = 7; } message NoticeOperation { optional uint32 type = 1; optional string schema = 2; } message RichTextContentCount { optional uint64 textWord = 1; optional uint64 at = 2; optional uint64 url = 3; optional uint64 emoji = 4; optional uint64 image = 5; optional uint64 video = 6; } message StAnimation { optional uint32 width = 1; optional uint32 height = 2; optional string animationUrl = 3; optional bytes busiData = 4; } message StBusiReportInfo { optional StRecomReportInfo recomReport = 1; optional string traceID = 2; } message StChannelShareInfo { optional string feedID = 1; optional string posterID = 2; optional uint64 feedPublishAt = 3; optional StChannelSign channelSign = 4; optional uint64 updateDurationMs = 5; optional StChannelShareSign sign = 6; } message StChannelShareSign { optional uint64 createAt = 1; optional string token = 2; } message StCircleRankItem { optional int32 rankNo = 1; optional string circleName = 2; optional int64 fuelValue = 3; optional int64 feedNum = 4; optional string circleID = 5; } message StClientInfo { optional string feedclientkey = 1; repeated CommonEntry clientMap = 2; } message StComment { optional string id = 1; optional StUser postUser = 2; optional uint64 createTime = 3; optional string content = 4; optional uint32 replyCount = 5; repeated StReply vecReply = 6; optional bytes busiData = 7; optional StLike likeInfo = 8; optional uint32 typeFlag = 9; repeated string atUinList = 10; optional uint32 typeFlag2 = 11; optional uint64 createTimeNs = 12; repeated CommonEntry storeExtInfo = 13; optional string thirdId = 14; optional uint32 sourceType = 15; optional StRichText richContents = 16; } message StDebugInfo { repeated CommonEntry debugMap = 1; } message StDittoFeed { optional uint32 dittoId = 1; optional uint32 dittoPatternId = 2; optional bytes dittoData = 3; optional bytes dittoDataNew = 4; } message StExifInfo { repeated CommonEntry kvs = 1; } message StExternalMedalWallInfo { optional bool needRedPoint = 1; repeated StMedalInfo medalInfos = 2; optional string medalWallJumpUrl = 3; optional bool needShowEntrance = 4; } message StFeed { optional string id = 1; optional StRichText title = 2; optional StRichText subtitle = 3; optional StUser poster = 4; repeated StVideo videos = 5; optional StRichText contents = 6; optional uint64 createTime = 7; optional StEmotionReactionInfo emotionReaction = 8; optional uint32 commentCount = 9; repeated StComment vecComment = 10; optional StShare share = 11; optional StVisitor visitorInfo = 12; repeated StImage images = 13; optional StPoiInfoV2 poiInfo = 14; repeated StTagInfo tagInfos = 15; optional bytes busiReport = 16; repeated uint32 opMask = 17; optional StOpinfo opinfo = 18; repeated CommonEntry extInfo = 19; optional string patternInfo = 20; optional StChannelInfo channelInfo = 21; optional uint64 createTimeNs = 22; optional StFeedSummary summary = 23; optional StRecomInfo recomInfo = 24; optional FeedMetaData meta = 25; } message StFeedAbstract { optional string id = 1; optional string title = 2; optional StUser poster = 3; optional StImage pic = 4; optional uint32 type = 5; optional uint64 createTime = 6; optional StVideo video = 7; optional uint32 fuelNum = 8; optional string content = 9; repeated StImage images = 10; optional StFeedCount countInfo = 11; } message StFeedCount { optional int64 liked = 1; optional int64 push = 2; optional int64 comment = 3; optional int64 visitor = 4; } message StFeedSummary { optional uint32 layoutType = 1; } message StFollowRecomInfo { optional string followText = 1; repeated StFollowUser followUsers = 4; optional string commFriendText = 6; optional string commGroupText = 7; } message StFollowUser { optional uint64 uid = 1; optional string nick = 2; } message StGPSV2 { optional int64 lat = 1; optional int64 lon = 2; optional int64 eType = 3; optional int64 alt = 4; } message StGuidePublishBubble { optional string id = 1; optional StImage backgroundImage = 2; optional string jumpUrl = 3; } message StIconInfo { optional string iconUrl40 = 1; optional string iconUrl100 = 2; optional string iconUrl140 = 3; optional string iconUrl640 = 4; optional string iconUrl = 5; } message StImage { optional uint32 width = 1; optional uint32 height = 2; optional string picUrl = 3; repeated StImageUrl vecImageUrl = 4; optional string picId = 5; optional bytes busiData = 6; optional string imageMD5 = 7; optional string layerPicUrl = 8; optional string patternId = 9; optional uint32 displayIndex = 10; } message StImageUrl { optional uint32 levelType = 1; optional string url = 2; optional uint32 width = 3; optional uint32 height = 4; optional bytes busiData = 5; } message StLightInteractInfo { optional StUser user = 1; optional StRelationInfo relation = 2; optional uint32 count = 3; optional bytes busiData = 4; } message StLike { optional string id = 1; optional uint32 count = 2; optional uint32 status = 3; repeated StUser vecUser = 4; optional bytes busiData = 5; optional StUser postUser = 6; optional uint32 hasLikedCount = 7; optional uint32 ownerStatus = 8; optional string jumpUrl = 9; } message StLiteBanner { optional StImage icon = 1; optional string title = 2; optional string jumpUrl = 3; optional string activityID = 4; optional string jsonStyle = 5; repeated CommonEntry extInfo = 6; } message StMaterialDataNew { optional string materialType = 1; repeated StSingleMaterial materialList = 2; } message StMedalInfo { optional int32 type = 1; optional string medalName = 2; optional string medalID = 3; optional int32 rank = 4; optional bool isHighLight = 5; optional bool isNew = 6; optional string jumpUrl = 7; optional string iconUrl = 8; optional string backgroundUrl = 9; optional string describe = 10; optional int32 reportValue = 11; } message StNotice { optional StFeed psvFeed = 1; optional StFeed origineFeed = 2; optional StNoticePattonInfo pattonInfo = 3; } message StNoticePattonInfo { optional uint32 pattonType = 1; optional StPlainTxtInfo plainTxt = 2; } message StNoticeTxtInfo { optional StRichText content = 1; optional StRichText contentOfReference = 2; } message StOpinfo { repeated uint64 createTime = 1; } message StPlainTxtInfo { optional StNoticeTxtInfo txtInfo = 1; optional NoticeOperation operation = 2; } message StPoiInfoV2 { optional string poiId = 1; optional string name = 2; optional int32 poiType = 3; optional string typeName = 4; optional string address = 5; optional int32 districtCode = 6; optional StGPSV2 gps = 7; optional int32 distance = 8; optional int32 hotValue = 9; optional string phone = 10; optional string country = 11; optional string province = 12; optional string city = 13; optional int32 poiNum = 14; optional int32 poiOrderType = 15; optional string defaultName = 16; optional string district = 17; optional string dianPingId = 18; optional string distanceText = 19; optional string displayName = 20; } message StPrePullCacheFeed { optional string id = 1; optional StUser poster = 2; optional uint64 createTime = 3; repeated BytesEntry busiTranparent = 4; } message StProxyInfo { optional int32 cmdId = 1; optional int32 subCmdId = 2; optional string appProtocol = 3; optional bytes reqBody = 4; } message StRankingItem { optional StUser user = 1; optional StRelationInfo relation = 2; optional int64 score = 3; optional int32 grade = 4; optional bytes busiData = 5; optional int32 rankNo = 6; optional int32 inTopicList = 7; } message StRecomForward { optional string id = 1; optional string title = 2; optional string subtitle = 3; optional StUser poster = 4; optional uint64 createTime = 5; optional uint32 type = 6; optional bytes busiData = 7; } message StRecomInfo { optional string recomReason = 1; optional bytes recomAttachInfo = 2; optional string recomTrace = 3; optional bytes clientSealData = 4; optional string iconUrl = 5; optional int32 recomReasonType = 6; } message StRecomReportInfo { repeated StSingleRecomReportInfo recomInfos = 1; } message StRelationInfo { optional string id = 1; optional uint32 relation = 2; optional bytes busiData = 3; optional uint32 relationState = 4; optional uint32 score = 5; optional bool isBlock = 6; optional bool isBlocked = 7; optional bool isFriend = 8; optional bool isUncare = 9; optional uint64 imBitMap = 10; } message StReply { optional string id = 1; optional StUser postUser = 2; optional uint64 createTime = 3; optional string content = 4; optional StUser targetUser = 5; optional bytes busiData = 6; optional StLike likeInfo = 7; optional uint32 typeFlag = 8; optional uint32 modifyflag = 9; repeated string atUinList = 10; optional uint32 typeFlag2 = 11; optional uint64 createTimeNs = 12; repeated CommonEntry storeExtInfo = 13; optional string thirdId = 14; optional string targetReplyID = 15; optional uint32 sourceType = 16; optional StRichText richContents = 17; } message StReportInfo { optional string id = 1; optional bytes busiReport = 2; } message StRichText { repeated StRichTextContent contents = 1; } message StRichTextAtContent { optional uint32 type = 1; optional GuildChannelBaseGuildInfo guildInfo = 2; optional GuildChannelBaseRoleGroupInfo roleGroupId = 3; optional StUser user = 4; } message GuildChannelBaseGuildInfo { optional uint64 guildId = 1; optional string name = 2; optional uint64 joinTime = 3; } message GuildChannelBaseRoleGroupInfo { optional uint64 roleId = 1; optional string name = 2; optional uint32 color = 3; } message StRichTextChannelContent { optional StChannelInfo channelInfo = 1; } message StRichTextContent { optional uint32 type = 1; optional string patternId = 2; optional StRichTextTextContent textContent = 3; optional StRichTextAtContent atContent = 4; optional StRichTextURLContent urlContent = 5; optional StRichTextEmojiContent emojiContent = 6; optional StRichTextChannelContent channelContent = 7; } message StRichTextEmojiContent { optional string id = 1; optional string type = 2; optional string name = 3; optional string url = 4; } message StRichTextTextContent { optional string text = 1; } message StRichTextURLContent { optional string url = 1; optional string displayText = 2; } message StSameTopicGuideInfo { optional uint32 isSameTopicGuide = 1; optional int64 stayShowTime = 2; optional string hashTag = 3; optional string words = 4; optional string jumpUrl = 5; optional string reportExt = 6; } message StShare { optional string title = 1; optional string desc = 2; optional uint32 type = 3; optional string url = 4; optional StUser author = 5; optional StUser poster = 6; repeated StVideo videos = 7; optional string shorturl = 8; optional string shareCardInfo = 9; optional StShareQzoneInfo shareQzoneInfo = 10; repeated StImage images = 11; optional uint32 publishTotalUser = 12; optional uint32 sharedCount = 13; optional StChannelShareInfo channelShareInfo = 14; } message StShareQzoneInfo { repeated CommonEntry entrys = 1; } message StSingleMaterial { optional string materialId = 1; } message StSingleRecomReportInfo { optional string reportID = 1; optional bytes reportData = 2; } message StTagInfo { optional string tagId = 1; optional string tagName = 2; optional string tagDec = 3; repeated StUser userList = 4; repeated StFeedAbstract feedList = 5; optional uint32 tagTotalUser = 6; optional uint32 tagTotalFeed = 7; optional string tagWording = 8; optional uint32 tagType = 9; optional uint32 followState = 10; optional StShare shareInfo = 11; optional uint32 isTop = 12; optional uint32 isSelected = 13; optional int64 userViewHistory = 14; optional StTagMedalInfo medal = 15; optional uint32 status = 16; optional StTagOperateInfo optInfo = 17; optional uint32 tagBaseStatus = 18; optional int32 isRecommend = 19; optional int64 tagViewHistory = 20; optional string operateIconUrl = 21; optional string tagReport = 99; optional string tagIconUrl = 100; } message StTagMedalInfo { optional string tagID = 1; optional string tagName = 2; optional uint64 rank = 3; } message StTagOperateInfo { optional string createUser = 1; optional string coverURL = 2; optional string desc = 3; optional string backgroundURL = 4; optional string bannerURL = 5; optional string bannerSkipLink = 6; optional int64 activityStartTime = 7; optional int64 activityEndTime = 8; optional string recommendReason = 9; optional int32 isWhite = 10; optional int64 beWhiteStartTime = 11; optional int64 beWhiteEndTime = 12; optional string publishSchema = 13; } message StUnifiedTag { optional string unifiedType = 1; optional string unifiedId = 2; } message StUser { optional string id = 1; optional string nick = 2; optional StIconInfo icon = 3; optional string desc = 4; optional uint32 followState = 5; optional uint32 type = 6; optional uint32 sex = 7; optional uint64 birthday = 8; optional string school = 9; optional string location = 11; optional bytes busiData = 12; optional uint32 frdState = 13; optional uint32 relationState = 14; optional uint32 blackState = 15; optional StTagMedalInfo medal = 16; optional int32 constellation = 17; optional string jumpUrl = 18; optional string locationCode = 19; optional string thirdId = 20; optional string company = 21; optional string certificationDesc = 22; optional uint32 descType = 23; optional GuildChannelBaseChannelUserInfo channelUserInfo = 24; optional string loginId = 25; } message GuildChannelBaseChannelUserInfo { optional ClientIdentity clientIdentity = 1; optional uint32 memberType = 2; // optional ChannelUserPermission permission = 3; repeated GuildChannelBaseRoleGroupInfo roleGroups = 4; } message StUserGroupInfo { optional string id = 1; optional string name = 2; repeated StUser userList = 3; } message StUserRecomInfo { optional StUser user = 1; repeated StFeedAbstract feedList = 2; optional bytes busiData = 3; } message StVideo { optional string fileId = 1; optional uint32 fileSize = 2; optional uint32 duration = 3; optional uint32 width = 4; optional uint32 height = 5; optional string playUrl = 6; optional uint32 transStatus = 7; optional uint32 videoPrior = 8; optional uint32 videoRate = 9; repeated StVideoUrl vecVideoUrl = 10; optional bytes busiData = 11; optional uint32 approvalStatus = 12; optional uint32 videoSource = 13; optional uint32 mediaQualityRank = 14; optional float mediaQualityScore = 15; optional string videoMD5 = 16; optional uint32 isQuic = 17; optional uint32 orientation = 18; optional StImage cover = 19; optional string patternId = 20; optional uint32 displayIndex = 21; } message StVideoUrl { optional uint32 levelType = 1; optional string playUrl = 2; optional uint32 videoPrior = 3; optional uint32 videoRate = 4; optional uint32 transStatus = 5; optional bytes busiData = 6; optional bool hasWatermark = 7; } message StVisitor { optional uint32 viewCount = 1; optional bytes busiData = 2; optional uint32 recomCount = 3; optional string viewDesc = 4; } message StWearingMedal { repeated StWearingMedalInfo medalInfos = 1; } message StWearingMedalInfo { optional int32 type = 1; optional string medalName = 2; optional string medalID = 3; } ================================================ FILE: ricq-guild/src/protocol/protobuf/GuildFeedCloudRead.proto ================================================ syntax = "proto2"; package guild; import "GuildFeedCloudMeta.proto"; import "GuildChannelBase.proto"; message GetNoticesReq { optional StCommonExt extInfo = 1; optional uint32 pageNum = 2; optional string attachInfo = 3; } message GetNoticesRsp { optional StCommonExt extInfo = 1; repeated StNotice notices = 2; optional uint32 totalNum = 3; optional bool isFinish = 4; optional string attachInfo = 5; } message NeedInsertCommentInfo { optional string commentID = 1; } message RefreshToast { optional string text = 1; } message StGetChannelFeedsReq { optional StCommonExt extInfo = 1; optional uint32 count = 2; optional uint32 from = 3; optional StChannelSign channelSign = 4; optional string feedAttchInfo = 5; } message StGetChannelFeedsRsp { optional StCommonExt extInfo = 1; repeated StFeed vecFeed = 2; optional uint32 isFinish = 3; optional StUser user = 4; optional string feedAttchInfo = 5; optional RefreshToast refreshToast = 6; } message StGetChannelShareFeedReq { optional StCommonExt extInfo = 1; optional uint32 from = 2; optional StChannelShareInfo channelShareInfo = 3; } message StGetChannelShareFeedRsp { optional StCommonExt extInfo = 1; optional StFeed feed = 2; } message StGetFeedCommentsReq { optional StCommonExt extInfo = 1; optional string userId = 2; optional string feedId = 3; optional uint32 listNum = 4; optional uint32 from = 5; optional string attchInfo = 6; optional string entrySchema = 7; } message StGetFeedCommentsRsp { optional StCommonExt extInfo = 1; repeated StComment vecComment = 2; optional uint32 totalNum = 3; optional uint32 isFinish = 4; optional string attchInfo = 5; } message StGetFeedDetailReq { optional StCommonExt extInfo = 1; optional uint32 from = 2; optional string userId = 3; optional string feedId = 4; optional uint64 createTime = 5; optional uint32 detailType = 6; optional StChannelSign channelSign = 7; } message StGetFeedDetailRsp { optional StCommonExt extInfo = 1; optional StFeed feed = 2; optional StUser loginUser = 3; } ================================================ FILE: ricq-guild/src/protocol/protobuf/GuildWriter.proto ================================================ syntax = "proto2"; package guild; import "GuildFeedCloudMeta.proto"; import "GuildChannelBase.proto"; message StAlterFeedReq { optional StCommonExt extInfo = 1; optional StFeed feed = 2; optional bytes busiReqData = 3; optional uint64 mBitmap = 4; optional int32 from = 5; optional int32 src = 6; repeated CommonEntry alterFeedExtInfo = 7; optional string jsonFeed = 8; optional StClientContent clientContent = 9; } message StAlterFeedRsp { optional StCommonExt extInfo = 1; optional StFeed feed = 2; optional bytes busiRspData = 3; } message StClientContent { repeated StClientImageContent clientImageContents = 1; repeated StClientVideoContent clientVideoContents = 2; } message StClientImageContent { optional string taskId = 1; optional string picId = 2; optional string url = 3; } message StClientVideoContent { optional string taskId = 1; optional string videoId = 2; optional string videoUrl = 3; optional string coverUrl = 4; } message StDelFeedReq { optional StCommonExt extInfo = 1; optional StFeed feed = 2; optional int32 from = 3; optional int32 src = 4; } message StDelFeedRsp { optional StCommonExt extInfo = 1; } message StDoCommentReq { optional StCommonExt extInfo = 1; optional uint32 commentType = 2; optional StComment comment = 3; optional StFeed feed = 4; optional int32 from = 5; optional bytes busiReqData = 6; optional int32 src = 7; } message StDoCommentRsp { optional StCommonExt extInfo = 1; optional StComment comment = 2; optional bytes busiRspData = 3; } message StDoLikeReq { optional StCommonExt extInfo = 1; optional uint32 likeType = 2; optional StLike like = 3; optional StFeed feed = 4; optional bytes busiReqData = 5; optional StComment comment = 6; optional StReply reply = 7; optional int32 from = 8; optional int32 src = 9; optional StEmotionReactionInfo emotionReaction = 10; } message StDoLikeRsp { optional StCommonExt extInfo = 1; optional StLike like = 2; optional bytes busiRspData = 3; optional StEmotionReactionInfo emotionReaction = 4; } message StDoReplyReq { optional StCommonExt extInfo = 1; optional uint32 replyType = 2; optional StReply reply = 3; optional StComment comment = 4; optional StFeed feed = 5; optional int32 from = 6; optional bytes busiReqData = 7; optional int32 src = 8; } message StDoReplyRsp { optional StCommonExt extInfo = 1; optional StReply reply = 2; optional bytes busiRspData = 3; } message StDoSecurityReq { optional StCommonExt extInfo = 1; optional StFeed feed = 2; optional StComment comment = 3; optional StReply reply = 4; optional StUser poster = 5; optional int32 secType = 6; } message StDoSecurityRsp { optional StCommonExt extInfo = 1; } message StModifyFeedReq { optional StCommonExt extInfo = 1; optional StFeed feed = 2; optional uint64 mBitmap = 3; optional int32 from = 4; optional int32 src = 5; repeated CommonEntry modifyFeedExtInfo = 6; } message StModifyFeedRsp { optional StCommonExt extInfo = 1; optional StFeed feed = 2; optional bytes busiRspData = 3; } message StPublishFeedReq { optional StCommonExt extInfo = 1; optional StFeed feed = 2; optional bytes busiReqData = 3; optional int32 from = 4; optional int32 src = 5; repeated CommonEntry storeFeedExtInfo = 6; optional string jsonFeed = 7; optional StClientContent clientContent = 8; } message StPublishFeedRsp { optional StCommonExt extInfo = 1; optional StFeed feed = 2; optional bytes busiRspData = 3; } ================================================ FILE: ricq-guild/src/protocol/protobuf/MsgResponsesSvr.proto ================================================ syntax = "proto2"; package guild; message BatchGetMsgRspCountReq { repeated GuildMsg guildMsgList = 1; } message BatchGetMsgRspCountRsp { repeated GuildMsgInfo guildMsgInfoList = 1; } message SvrChannelMsg { optional uint64 channelId = 1; repeated MsgId id = 2; } message ChannelMsgInfo { optional uint64 channelId = 1; repeated MsgRespData respData = 2; } message EmojiReaction { optional string emojiId = 1; optional uint64 emojiType = 2; optional uint64 cnt = 3; optional bool isClicked = 4; optional bool isDefaultEmoji = 10001; } message GuildMsg { optional uint64 guildId = 1; repeated SvrChannelMsg channelMsgList = 2; } message GuildMsgInfo { optional uint64 guildId = 1; repeated ChannelMsgInfo channelMsgInfoList = 2; } message MsgCnt { optional MsgId id = 1; repeated EmojiReaction emojiReaction = 2; } message MsgId { optional uint64 version = 1; optional uint64 seq = 2; } message MsgRespData { optional MsgId id = 1; optional bytes cnt = 2; } ================================================ FILE: ricq-guild/src/protocol/protobuf/common.proto ================================================ syntax = "proto2"; package guild; import "msg/msg.proto"; message ChannelContentHead { optional uint64 type = 1; optional uint64 subType = 2; optional uint64 random = 3; optional uint64 seq = 4; optional uint64 cntSeq = 5; optional uint64 time = 6; optional bytes meta = 7; } message DirectMessageMember { optional uint64 uin = 1; optional uint64 tinyid = 2; optional uint64 sourceGuildId = 3; optional bytes sourceGuildName = 4; optional bytes nickName = 5; optional bytes memberName = 6; optional uint32 notifyType = 7; } message ChannelEvent { optional uint64 type = 1; optional uint64 version = 2; optional ChannelMsgOpInfo opInfo = 3; } message ChannelExtInfo { optional bytes fromNick = 1; optional bytes guildName = 2; optional bytes channelName = 3; optional uint32 visibility = 4; optional uint32 notifyType = 5; optional uint32 offlineFlag = 6; optional uint32 nameType = 7; optional bytes memberName = 8; optional uint32 timestamp = 9; optional uint64 eventVersion = 10; repeated ChannelEvent events = 11; optional ChannelRole fromRoleInfo = 12; optional ChannelFreqLimitInfo freqLimitInfo = 13; repeated DirectMessageMember directMessageMember = 14; } message ChannelFreqLimitInfo { optional uint32 isLimited = 1; optional uint32 leftCount = 2; optional uint64 limitTimestamp = 3; } message ChannelInfo { optional uint64 id = 1; optional bytes name = 2; optional uint32 color = 3; optional uint32 hoist = 4; } message ChannelLoginSig { optional uint32 type = 1; optional bytes sig = 2; optional uint32 appid = 3; } message ChannelMeta { optional uint64 fromUin = 1; optional ChannelLoginSig loginSig = 2; } message ChannelMsgContent { optional ChannelMsgHead head = 1; optional ChannelMsgCtrlHead ctrlHead = 2; optional msg.MessageBody body = 3; optional ChannelExtInfo extInfo = 4; } message ChannelMsgCtrlHead { repeated bytes includeUin = 1; // repeated uint64 excludeUin = 2; // bytes? // repeated uint64 featureid = 3; optional uint32 offlineFlag = 4; optional uint32 visibility = 5; optional uint64 ctrlFlag = 6; repeated ChannelEvent events = 7; optional uint64 level = 8; repeated PersonalLevel personalLevels = 9; optional uint64 guildSyncSeq = 10; optional uint32 memberNum = 11; optional uint32 channelType = 12; optional uint32 privateType = 13; } message ChannelMsgHead { optional ChannelRoutingHead routingHead = 1; optional ChannelContentHead contentHead = 2; } message ChannelMsgMeta { optional uint64 atAllSeq = 1; } message ChannelMsgOpInfo { optional uint64 operatorTinyid = 1; optional uint64 operatorRole = 2; optional uint64 reason = 3; optional uint64 timestamp = 4; optional uint64 atType = 5; } message PersonalLevel { optional uint64 toUin = 1; optional uint64 level = 2; } message ChannelRole { optional uint64 id = 1; optional bytes info = 2; optional uint32 flag = 3; } message ChannelRoutingHead { optional uint64 guildId = 1; optional uint64 channelId = 2; optional uint64 fromUin = 3; optional uint64 fromTinyid = 4; optional uint64 guildCode = 5; optional uint64 fromAppid = 6; optional uint32 directMessageFlag = 7; } ================================================ FILE: ricq-guild/src/protocol/protobuf/msgpush.proto ================================================ syntax = "proto2"; package guild; import "common.proto"; message FocusInfo { repeated uint64 channelIdList = 1; } message MsgOnlinePush { repeated ChannelMsgContent msgs = 1; optional uint32 generalFlag = 2; optional uint32 needResp = 3; optional bytes serverBuf = 4; optional uint32 compressFlag = 5; optional bytes compressMsg = 6; optional FocusInfo focusInfo = 7; optional uint32 hugeFlag = 8; } message MsgPushResp { optional bytes serverBuf = 1; } message PressMsg { repeated ChannelMsgContent msgs = 1; } message ServerBuf { optional uint32 svrIp = 1; optional uint32 svrPort = 2; optional bytes echoKey = 3; } ================================================ FILE: ricq-guild/src/protocol/protobuf/oidb0xf62.proto ================================================ syntax = "proto2"; package guild; import "common.proto"; import "msg/msg.proto"; message DF62ReqBody { optional ChannelMsgContent msg = 1; } message DF62RspBody { optional uint32 result = 1; optional bytes errmsg = 2; optional uint32 sendTime = 3; optional ChannelMsgHead head = 4; optional uint32 errType = 5; optional TransSvrInfo transSvrInfo = 6; optional ChannelFreqLimitInfo freqLimitInfo = 7; optional msg.MessageBody body = 8; } message TransSvrInfo { optional uint32 subType = 1; optional int32 retCode = 2; optional bytes errMsg = 3; optional bytes transInfo = 4; } ================================================ FILE: ricq-guild/src/protocol/protobuf/servtype.proto ================================================ syntax = "proto2"; package guild; message AppChannelMsg { optional string summary = 1; optional string msg = 2; optional uint64 expireTimeMs = 3; optional uint32 schemaType = 4; optional string schema = 5; } message CategoryChannelInfo { optional uint32 channelIndex = 1; optional uint64 channelId = 2; } message CategoryInfo { optional uint32 categoryIndex = 1; repeated CategoryChannelInfo channelInfo = 2; optional bytes categoryName = 3; optional uint64 categoryId = 4; } message ChanInfoFilter { optional uint32 channelName = 2; optional uint32 creatorId = 3; optional uint32 createTime = 4; optional uint32 guildId = 5; optional uint32 msgNotifyType = 6; optional uint32 channelType = 7; optional uint32 speakPermission = 8; optional uint32 lastMsgSeq = 11; optional uint32 lastCntMsgSeq = 12; optional VoiceChannelInfoFilter voiceChannelInfoFilter = 14; optional LiveChannelInfoFilter liveChannelInfoFilter = 15; optional uint32 bannedSpeak = 16; } message ChangeChanInfo { optional uint64 guildId = 1; optional uint64 chanId = 2; optional uint64 operatorId = 3; optional MsgSeq infoSeq = 4; optional uint32 updateType = 5; optional ChanInfoFilter chanInfoFilter = 6; optional ServChannelInfo chanInfo = 7; } message ChangeGuildInfo { optional uint64 guildId = 1; optional uint64 operatorId = 2; optional MsgSeq infoSeq = 3; optional MsgSeq faceSeq = 4; optional uint32 updateType = 5; optional GuildInfoFilter guildInfoFilter = 6; optional GuildInfo guildInfo = 7; } message ChannelID { optional uint64 chanId = 1; } message ServChannelInfo { optional uint64 channelId = 1; optional bytes channelName = 2; optional uint64 creatorId = 3; optional uint64 createTime = 4; optional uint64 guildId = 5; optional uint32 msgNotifyType = 6; optional uint32 channelType = 7; optional uint32 speakPermission = 8; optional MsgSeq lastMsgSeq = 11; optional MsgSeq lastCntMsgSeq = 12; optional VoiceChannelInfo voiceChannelInfo = 14; optional LiveChannelInfo liveChannelInfo = 15; optional uint32 bannedSpeak = 16; } message CommGrayTips { optional uint64 busiType = 1; optional uint64 busiId = 2; optional uint32 ctrlFlag = 3; optional uint64 templId = 4; repeated TemplParam templParam = 5; optional bytes content = 6; optional uint64 tipsSeqId = 10; optional bytes pbReserv = 100; message TemplParam { optional bytes name = 1; optional bytes value = 2; } } message CreateChan { optional uint64 guildId = 1; optional uint64 operatorId = 3; repeated ChannelID createId = 4; } message CreateGuild { optional uint64 operatorId = 1; optional uint64 guildId = 2; } message DestroyChan { optional uint64 guildId = 1; optional uint64 operatorId = 3; repeated ChannelID deleteId = 4; } message DestroyGuild { optional uint64 operatorId = 1; optional uint64 guildId = 2; } message EventBody { optional ReadNotify readNotify = 1; optional CommGrayTips commGrayTips = 2; optional CreateGuild createGuild = 3; optional DestroyGuild destroyGuild = 4; optional JoinGuild joinGuild = 5; optional KickOffGuild kickOffGuild = 6; optional QuitGuild quitGuild = 7; optional ChangeGuildInfo changeGuildInfo = 8; optional CreateChan createChan = 9; optional DestroyChan destroyChan = 10; optional ChangeChanInfo changeChanInfo = 11; optional SetAdmin setAdmin = 12; optional SetMsgRecvType setMsgRecvType = 13; optional UpdateMsg updateMsg = 14; optional SetTop setTop = 17; optional SwitchVoiceChannel switchChannel = 18; optional UpdateCategory updateCategory = 21; optional UpdateVoiceBlockList updateVoiceBlockList = 22; optional SetMute setMute = 23; optional LiveRoomStatusChangeMsg liveStatusChangeRoom = 24; optional SwitchLiveRoom switchLiveRoom = 25; repeated MsgEvent events = 39; optional SchedulerMsg scheduler = 40; optional AppChannelMsg appChannel = 41; optional FeedEvent feedEvent = 44; optional AppChannelMsg weakMsgAppChannel = 46; optional ReadFeedNotify readFeedNotify = 48; } message FeedEvent { optional uint64 guildId = 1; optional uint64 channelId = 2; optional string feedId = 3; optional string msgSummary = 4; optional uint64 eventTime = 5; } message ReadFeedNotify { optional uint64 reportTime = 2; } message GroupProStatus { optional uint32 isEnable = 1; optional uint32 isBanned = 2; optional uint32 isFrozen = 3; } message GuildInfo { optional uint64 guildCode = 2; optional uint64 ownerId = 3; optional uint64 createTime = 4; optional uint32 memberMaxNum = 5; optional uint32 memberNum = 6; optional uint32 guildType = 7; optional bytes guildName = 8; repeated uint64 robotList = 9; repeated uint64 adminList = 10; optional uint32 robotMaxNum = 11; optional uint32 adminMaxNum = 12; optional bytes profile = 13; optional uint64 faceSeq = 14; optional GroupProStatus guildStatus = 15; optional uint32 channelNum = 16; optional MsgSeq memberChangeSeq = 5002; optional MsgSeq guildInfoChangeSeq = 5003; optional MsgSeq channelChangeSeq = 5004; } message GuildInfoFilter { optional uint32 guildCode = 2; optional uint32 ownerId = 3; optional uint32 createTime = 4; optional uint32 memberMaxNum = 5; optional uint32 memberNum = 6; optional uint32 guildType = 7; optional uint32 guildName = 8; optional uint32 robotList = 9; optional uint32 adminList = 10; optional uint32 robotMaxNum = 11; optional uint32 adminMaxNum = 12; optional uint32 profile = 13; optional uint32 faceSeq = 14; optional uint32 guildStatus = 15; optional uint32 channelNum = 16; optional uint32 memberChangeSeq = 5002; optional uint32 guildInfoChangeSeq = 5003; optional uint32 channelChangeSeq = 5004; } message JoinGuild { optional uint64 memberId = 3; optional uint32 memberType = 4; optional uint64 memberTinyid = 5; } message KickOffGuild { optional uint64 memberId = 3; optional uint32 setBlack = 4; optional uint64 memberTinyid = 5; } message LiveChannelInfo { optional uint64 roomId = 1; optional uint64 anchorUin = 2; optional bytes name = 3; } message LiveChannelInfoFilter { optional uint32 isNeedRoomId = 1; optional uint32 isNeedAnchorUin = 2; optional uint32 isNeedName = 3; } message LiveRoomStatusChangeMsg { optional uint64 guildId = 1; optional uint64 channelId = 2; optional uint64 roomId = 3; optional uint64 anchorTinyid = 4; optional uint32 action = 5; } message MsgEvent { optional uint64 seq = 1; optional uint64 eventType = 2; optional uint64 eventVersion = 3; } message MsgSeq { optional uint64 seq = 1; optional uint64 time = 2; } message QuitGuild {} message ReadNotify { optional uint64 channelId = 1; optional uint64 guildId = 2; optional MsgSeq readMsgSeq = 3; optional MsgSeq readCntMsgSeq = 4; optional bytes readMsgMeta = 5; } message SchedulerMsg { optional bytes creatorHeadUrl = 1; optional string wording = 2; optional uint64 expireTimeMs = 3; } message SetAdmin { optional uint64 guildId = 1; optional uint64 chanId = 2; optional uint64 operatorId = 3; optional uint64 adminId = 4; optional uint64 adminTinyid = 5; optional uint32 operateType = 6; } message SetMsgRecvType { optional uint64 guildId = 1; optional uint64 chanId = 2; optional uint64 operatorId = 3; optional uint32 msgNotifyType = 4; } message SetMute { optional uint32 action = 1; optional uint64 tinyID = 2; } message SetTop { optional uint32 action = 1; } message SwitchDetail { optional uint64 guildId = 1; optional uint64 channelId = 2; optional uint32 platform = 3; } message SwitchLiveRoom { optional uint64 guildId = 1; optional uint64 channelId = 2; // optional uint64 roomId = 3; // optional uint64 tinyid = 4; optional SwitchLiveRoomUserInfo userInfo = 3; optional uint32 action = 4; // JOIN = 1 QUIT = 2 } message SwitchLiveRoomUserInfo { optional uint64 tinyId = 1; optional string nickname = 2; } message SwitchVoiceChannel { optional uint64 memberId = 1; optional SwitchDetail enterDetail = 2; optional SwitchDetail leaveDetail = 3; } message UpdateCategory { repeated CategoryInfo categoryInfo = 1; optional CategoryInfo noClassifyCategoryInfo = 2; } message UpdateMsg { optional uint64 msgSeq = 1; optional bool origMsgUncountable = 2; optional uint64 eventType = 3; optional uint64 eventVersion = 4; optional uint64 operatorTinyid = 5; optional uint64 operatorRole = 6; optional uint64 reason = 7; optional uint64 timestamp = 8; } message UpdateVoiceBlockList { optional uint32 action = 1; optional uint64 objectTinyid = 2; } message VoiceChannelInfo { optional uint32 memberMaxNum = 1; } message VoiceChannelInfoFilter { optional uint32 memberMaxNum = 1; } ================================================ FILE: ricq-guild/src/protocol/protobuf/synclogic.proto ================================================ syntax = "proto2"; package guild; import "common.proto"; message ChannelMsg { optional uint64 guildId = 1; optional uint64 channelId = 2; optional uint32 result = 3; optional uint64 rspBeginSeq = 4; optional uint64 rspEndSeq = 5; repeated ChannelMsgContent msgs = 6; } message ChannelMsgReq { optional ChannelParam channelParam = 1; optional uint32 withVersionFlag = 2; optional uint32 directMessageFlag = 3; } message ChannelMsgRsp { optional uint32 result = 1; optional bytes errMsg = 2; optional ChannelMsg channelMsg = 3; optional uint32 withVersionFlag = 4; optional uint64 getMsgTime = 5; } message ChannelNode { optional uint64 channelId = 1; optional uint64 seq = 2; optional uint64 cntSeq = 3; optional uint64 time = 4; optional uint64 memberReadMsgSeq = 5; optional uint64 memberReadCntSeq = 6; optional uint32 notifyType = 7; optional bytes channelName = 8; optional uint32 channelType = 9; optional bytes meta = 10; optional bytes readMsgMeta = 11; optional uint32 eventTime = 12; } message ChannelParam { optional uint64 guildId = 1; optional uint64 channelId = 2; optional uint64 beginSeq = 3; optional uint64 endSeq = 4; optional uint64 time = 5; repeated uint64 version = 6; repeated MsgCond seqs = 7; } message DirectMessageSource { optional uint64 tinyId = 1; optional uint64 guildId = 2; optional bytes guildName = 3; optional bytes memberName = 4; optional bytes nickName = 5; } message FirstViewMsg { optional uint32 pushFlag = 1; optional uint32 seq = 2; repeated GuildNode guildNodes = 3; repeated ChannelMsg channelMsgs = 4; optional uint64 getMsgTime = 5; repeated GuildNode directMessageGuildNodes = 6; } message FirstViewReq { optional uint64 lastMsgTime = 1; optional uint32 udcFlag = 2; optional uint32 seq = 3; optional uint32 directMessageFlag = 4; } message FirstViewRsp { optional uint32 result = 1; optional bytes errMsg = 2; optional uint32 seq = 3; optional uint32 udcFlag = 4; optional uint32 guildCount = 5; optional uint64 selfTinyid = 6; optional uint32 directMessageSwitch = 7; optional uint32 directMessageGuildCount = 8; } message GuildNode { optional uint64 guildId = 1; optional uint64 guildCode = 2; repeated ChannelNode channelNodes = 3; optional bytes guildName = 4; optional DirectMessageSource peerSource = 5; } message MsgCond { optional uint64 seq = 1; optional uint64 eventVersion = 2; } message MultiChannelMsg { optional uint32 pushFlag = 1; optional uint32 seq = 2; repeated ChannelMsg channelMsgs = 3; optional uint64 getMsgTime = 4; } message MultiChannelMsgReq { repeated ChannelParam channelParams = 1; optional uint32 seq = 2; optional uint32 directMessageFlag = 3; } message MultiChannelMsgRsp { optional uint32 result = 1; optional bytes errMsg = 2; optional uint32 seq = 3; } message ReqBody { optional ChannelParam channelParam = 1; optional uint32 directMessageFlag = 2; } message RspBody { optional uint32 result = 1; optional bytes errMsg = 2; optional ChannelMsg channelMsg = 3; } ================================================ FILE: ricq-guild/src/protocol/protobuf/unknown.proto ================================================ // 存放所有未知的结构体, 均为手动分析复原 syntax = "proto2"; package guild; // see sub_37628C message ChannelOidb0xf5bRsp { optional uint64 guildId = 1; repeated GuildMemberInfo bots = 4; repeated GuildMemberInfo members = 5; optional uint32 nextIndex = 10; optional uint32 finished = 9; optional string nextQueryParam = 24; repeated GuildGroupMembersInfo memberWithRoles = 25; optional uint64 nextRoleIdIndex = 26; } message ChannelOidb0xf88Rsp { optional GuildUserProfile profile = 1; } message ChannelOidb0xfc9Rsp { optional GuildUserProfile profile = 1; } message ChannelOidb0xf57Rsp { optional GuildMetaRsp rsp = 1; } message ChannelOidb0xf55Rsp { optional GuildChannelInfo info = 1; } message ChannelOidb0xf5dRsp { optional ChannelListRsp rsp = 1; } message ChannelOidb0x1017Rsp { optional P10x1017 p1 = 1; } message P10x1017 { optional uint64 tinyId = 1; repeated GuildUserRole roles = 3; } message ChannelOidb0x1019Rsp { optional uint64 guildId = 1; repeated GuildRole roles = 2; // 3: ? // 4: } /* message ChannelOidb0x100dReq { // 修改身份组 optional uint64 guildId = 1; repeated uint64 roleId = 2; repeated int32 unkonwn = 3; // 3: ? 三个1 repeated ModifyGuildRole role = 4; }*/ /* message ChannelOidb0x1016Req { // 新建身份组 optional uint64 guildId = 1; repeated int32 unknown = 2; // 2: ? 三个1 optional ModifyGuildRole role = 3; repeated uint64 initialUsers = 4; }*/ message ChannelOidb0x1016Rsp { optional uint64 roleId = 2; } /* message ChannelOidb0x101aReq { // 修改身份组 optional uint64 guildId = 1; repeated SetGuildRole setRoles = 2; repeated SetGuildRole removeRoles = 3; }*/ message GuildMetaRsp { optional uint64 guildId = 3; optional GuildMeta meta = 4; } message ChannelListRsp { optional uint64 guildId = 1; repeated GuildChannelInfo channels = 2; // 5: Category infos } message GuildGroupMembersInfo { optional uint64 roleId = 1; repeated GuildMemberInfo members = 2; optional string roleName = 3; optional uint32 color = 4; } // see sub_374334 message GuildMemberInfo { optional string title = 2; optional string nickname = 3; optional int64 lastSpeakTime = 4; // uncertainty optional int32 role = 5; // uncertainty optional uint64 tinyId = 8; } // 频道系统用户资料 message GuildUserProfile { optional uint64 tinyId = 2; optional string nickname = 3; optional string avatarUrl = 6; // 15: avatar url info optional int64 joinTime = 16; // uncertainty // 22 cards // 23 display cards // 25 current cards *uncertainty } message GuildRole { optional uint64 roleId = 1; optional string name = 2; optional uint32 argbColor = 3; optional int32 independent = 4; optional int32 num = 5; optional int32 owned = 6; // 是否拥有 存疑 optional int32 disabled = 7; // 权限不足或不显示 optional int32 maxNum = 8; // 9: ? } message GuildUserRole { optional uint64 roleId = 1; optional string name = 2; optional uint32 argbColor = 3; optional int32 independent = 4; } /* message SetGuildRole { optional uint64 roleId = 1; optional uint64 targetId = 2; }*/ /* message ModifyGuildRole { optional string roleName = 1; optional uint32 color = 2; optional int32 independent = 3; // 身份组单独显示 }*/ message GuildMeta { optional uint64 guildCode = 2; optional int64 createTime = 4; optional int64 maxMemberCount = 5; optional int64 memberCount = 6; optional string name = 8; optional int32 robotMaxNum = 11; optional int32 adminMaxNum = 12; optional string profile = 13; optional int64 avatarSeq = 14; optional uint64 ownerId = 18; optional int64 coverSeq = 19; optional int32 clientId = 20; } message GuildChannelInfo { optional uint64 channelId = 1; optional string channelName = 2; optional int64 creatorUin = 3; optional int64 createTime = 4; optional uint64 guildId = 5; optional int32 finalNotifyType = 6; optional int32 channelType = 7; optional int32 talkPermission = 8; // 11 - 14 : MsgInfo optional uint64 creatorTinyId = 15; // 16: Member info ? optional int32 visibleType = 22; optional GuildChannelTopMsgInfo topMsg = 28; optional int32 currentSlowModeKey = 31; repeated GuildChannelSlowModeInfo slowModeInfos = 32; } message GuildChannelSlowModeInfo { optional int32 slowModeKey = 1; optional int32 speakFrequency = 2; optional int32 slowModeCircle = 3; optional string slowModeText = 4; } message GuildChannelTopMsgInfo { optional uint64 topMsgSeq = 1; optional int64 topMsgTime = 2; optional uint64 topMsgOperatorTinyId = 3; } /* // 个性档案卡片 message GuildMemberProfileCard { optional int32 appid = 1; optional string name = 2; } */ ================================================ FILE: rust-toolchain.toml ================================================ [toolchain] channel = "nightly"