├── .editorconfig ├── .gitignore ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── README.md ├── access.go ├── compiler ├── build.sh ├── build_py.sh ├── scheme-71.tl ├── tl-schema-71.json └── tl2go.go ├── config.go ├── conn.go ├── convs.tl.go ├── credentials.go ├── crypto.go ├── dump.go ├── examples └── simpleshell │ └── main.go ├── manager.go ├── mtprotod ├── Dockerfile ├── README.md ├── build.sh ├── cacert.pem ├── cmd │ ├── root.go │ ├── start.go │ └── test.go └── main.go ├── procs.tl.go ├── proxy ├── proxy.go ├── proxy_test.go ├── tl_update.pb.go └── tl_update.proto ├── py ├── README.md ├── main.py ├── tl_pb2.py └── tl_pb2_grpc.py ├── session.go ├── tl.go ├── tools ├── access │ └── main.go ├── dumplayer │ └── main.go └── keygen │ └── main.go ├── types.go ├── types.tl.pb.go └── types.tl.proto /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | end_of_line = lf 9 | # editorconfig-tools is unable to ignore longs strings or urls 10 | max_line_length = 100 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore all 2 | * 3 | # unignore all with extensions 4 | !Dockerfile 5 | !*.* 6 | # unignore all dirs 7 | !*/ 8 | 9 | *.log 10 | *.bch 11 | *.diff 12 | .shell 13 | *.iml 14 | .idea 15 | vendor 16 | 17 | # Created by https://www.gitignore.io/api/go,git,intellij 18 | 19 | ### Git ### 20 | *.orig 21 | 22 | ### Go ### 23 | # Binaries for programs and plugins 24 | *.exe 25 | *.dll 26 | *.so 27 | *.dylib 28 | 29 | # Test binary, build with `go test -c` 30 | *.test 31 | 32 | # Output of the go coverage tool, specifically when used with LiteIDE 33 | *.out 34 | 35 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 36 | .glide/ 37 | 38 | ### Intellij ### 39 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 40 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 41 | 42 | # User-specific stuff: 43 | .idea/**/workspace.xml 44 | .idea/**/tasks.xml 45 | .idea/dictionaries 46 | 47 | # Sensitive or high-churn files: 48 | .idea/**/dataSources/ 49 | .idea/**/dataSources.ids 50 | .idea/**/dataSources.xml 51 | .idea/**/dataSources.local.xml 52 | .idea/**/sqlDataSources.xml 53 | .idea/**/dynamic.xml 54 | .idea/**/uiDesigner.xml 55 | 56 | # Gradle: 57 | .idea/**/gradle.xml 58 | .idea/**/libraries 59 | 60 | # CMake 61 | cmake-build-debug/ 62 | 63 | # Mongo Explorer plugin: 64 | .idea/**/mongoSettings.xml 65 | 66 | ## File-based project format: 67 | *.iws 68 | 69 | ## Plugin-specific files: 70 | 71 | # IntelliJ 72 | /out/ 73 | 74 | # mpeltonen/sbt-idea plugin 75 | .idea_modules/ 76 | 77 | # JIRA plugin 78 | atlassian-ide-plugin.xml 79 | 80 | # Cursive Clojure plugin 81 | .idea/replstate.xml 82 | 83 | # Ruby plugin and RubyMine 84 | /.rakeTasks 85 | 86 | # Crashlytics plugin (for Android Studio and IntelliJ) 87 | com_crashlytics_export_strings.xml 88 | crashlytics.properties 89 | crashlytics-build.properties 90 | fabric.properties 91 | 92 | ### Intellij Patch ### 93 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 94 | 95 | # *.iml 96 | # modules.xml 97 | # .idea/misc.xml 98 | # *.ipr 99 | 100 | # Sonarlint plugin 101 | .idea/sonarlint 102 | 103 | # End of https://www.gitignore.io/api/go,git,intellij -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/cjongseok/slog" 6 | packages = ["."] 7 | revision = "75c16fd472f1712bb3197d9949f3e5be56758c0d" 8 | version = "v0.1.2" 9 | 10 | [[projects]] 11 | name = "github.com/fsnotify/fsnotify" 12 | packages = ["."] 13 | revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" 14 | version = "v1.4.7" 15 | 16 | [[projects]] 17 | name = "github.com/golang/protobuf" 18 | packages = [ 19 | "proto", 20 | "ptypes", 21 | "ptypes/any", 22 | "ptypes/duration", 23 | "ptypes/timestamp" 24 | ] 25 | revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" 26 | version = "v1.1.0" 27 | 28 | [[projects]] 29 | branch = "master" 30 | name = "github.com/hashicorp/hcl" 31 | packages = [ 32 | ".", 33 | "hcl/ast", 34 | "hcl/parser", 35 | "hcl/printer", 36 | "hcl/scanner", 37 | "hcl/strconv", 38 | "hcl/token", 39 | "json/parser", 40 | "json/scanner", 41 | "json/token" 42 | ] 43 | revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168" 44 | 45 | [[projects]] 46 | name = "github.com/inconshreveable/mousetrap" 47 | packages = ["."] 48 | revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" 49 | version = "v1.0" 50 | 51 | [[projects]] 52 | name = "github.com/magiconair/properties" 53 | packages = ["."] 54 | revision = "c2353362d570a7bfa228149c62842019201cfb71" 55 | version = "v1.8.0" 56 | 57 | [[projects]] 58 | branch = "master" 59 | name = "github.com/mitchellh/mapstructure" 60 | packages = ["."] 61 | revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b" 62 | 63 | [[projects]] 64 | name = "github.com/pelletier/go-toml" 65 | packages = ["."] 66 | revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194" 67 | version = "v1.2.0" 68 | 69 | [[projects]] 70 | name = "github.com/spf13/afero" 71 | packages = [ 72 | ".", 73 | "mem" 74 | ] 75 | revision = "787d034dfe70e44075ccc060d346146ef53270ad" 76 | version = "v1.1.1" 77 | 78 | [[projects]] 79 | name = "github.com/spf13/cast" 80 | packages = ["."] 81 | revision = "8965335b8c7107321228e3e3702cab9832751bac" 82 | version = "v1.2.0" 83 | 84 | [[projects]] 85 | name = "github.com/spf13/cobra" 86 | packages = ["."] 87 | revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" 88 | version = "v0.0.3" 89 | 90 | [[projects]] 91 | branch = "master" 92 | name = "github.com/spf13/jwalterweatherman" 93 | packages = ["."] 94 | revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" 95 | 96 | [[projects]] 97 | name = "github.com/spf13/pflag" 98 | packages = ["."] 99 | revision = "583c0c0531f06d5278b7d917446061adc344b5cd" 100 | version = "v1.0.1" 101 | 102 | [[projects]] 103 | name = "github.com/spf13/viper" 104 | packages = ["."] 105 | revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736" 106 | version = "v1.0.2" 107 | 108 | [[projects]] 109 | branch = "master" 110 | name = "golang.org/x/net" 111 | packages = [ 112 | "context", 113 | "http/httpguts", 114 | "http2", 115 | "http2/hpack", 116 | "idna", 117 | "internal/timeseries", 118 | "trace" 119 | ] 120 | revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196" 121 | 122 | [[projects]] 123 | branch = "master" 124 | name = "golang.org/x/sys" 125 | packages = ["unix"] 126 | revision = "56ede360ec1c541828fb88741b3f1049406d28f5" 127 | 128 | [[projects]] 129 | name = "golang.org/x/text" 130 | packages = [ 131 | "collate", 132 | "collate/build", 133 | "internal/colltab", 134 | "internal/gen", 135 | "internal/tag", 136 | "internal/triegen", 137 | "internal/ucd", 138 | "language", 139 | "secure/bidirule", 140 | "transform", 141 | "unicode/bidi", 142 | "unicode/cldr", 143 | "unicode/norm", 144 | "unicode/rangetable" 145 | ] 146 | revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" 147 | version = "v0.3.0" 148 | 149 | [[projects]] 150 | branch = "master" 151 | name = "google.golang.org/genproto" 152 | packages = ["googleapis/rpc/status"] 153 | revision = "32ee49c4dd805befd833990acba36cb75042378c" 154 | 155 | [[projects]] 156 | name = "google.golang.org/grpc" 157 | packages = [ 158 | ".", 159 | "balancer", 160 | "balancer/base", 161 | "balancer/roundrobin", 162 | "channelz", 163 | "codes", 164 | "connectivity", 165 | "credentials", 166 | "encoding", 167 | "encoding/proto", 168 | "grpclb/grpc_lb_v1/messages", 169 | "grpclog", 170 | "internal", 171 | "keepalive", 172 | "metadata", 173 | "naming", 174 | "peer", 175 | "resolver", 176 | "resolver/dns", 177 | "resolver/passthrough", 178 | "stats", 179 | "status", 180 | "tap", 181 | "transport" 182 | ] 183 | revision = "7a6a684ca69eb4cae85ad0a484f2e531598c047b" 184 | version = "v1.12.2" 185 | 186 | [[projects]] 187 | name = "gopkg.in/yaml.v2" 188 | packages = ["."] 189 | revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" 190 | version = "v2.2.1" 191 | 192 | [solve-meta] 193 | analyzer-name = "dep" 194 | analyzer-version = 1 195 | inputs-digest = "975dd5292a515b738520c38eedf78516611f122549f219c1ddb30ffce1f51bed" 196 | solver-name = "gps-cdcl" 197 | solver-version = 1 198 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/cjongseok/slog" 30 | version = "0.1.2" 31 | 32 | [[constraint]] 33 | name = "github.com/golang/protobuf" 34 | version = "1.0.0" 35 | 36 | [[constraint]] 37 | name = "github.com/spf13/cobra" 38 | version = "0.0.2" 39 | 40 | [[constraint]] 41 | name = "github.com/spf13/viper" 42 | version = "1.0.2" 43 | 44 | [[constraint]] 45 | branch = "master" 46 | name = "golang.org/x/net" 47 | 48 | [[constraint]] 49 | name = "google.golang.org/grpc" 50 | version = "1.11.3" 51 | 52 | [prune] 53 | go-tests = true 54 | unused-packages = true 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MTProto 2 | === 3 | Telegram MTProto and proxy (over gRPC) in Go (golang). 4 | Telegram API layer: ***71*** 5 | 6 | ## Quick start 7 | ```sh 8 | # It is vendored in 'dep'. Refer to https://github.com/golang/dep for 'dep' installation. 9 | dep ensure 10 | 11 | # Run simple shell with your Telegram API id, hash, and, server address with your phone number. 12 | # If you don't have Telegram API stuffs, get them from 'https://my.telegram.org/apps'. 13 | go run examples/simpleshell/main.go 14 | 15 | # Then you can see 'Enter code:' message 16 | # Telegram sends you an authentication code. Check it on your mobile or desktop app and put it. 17 | Enter code: 18 | 19 | # Now signed-in. Let's get your recent dialogs. 20 | # You can see them in JSON. 21 | $ dialogs 22 | .... 23 | 24 | # Quit the shell. 25 | $ exit 26 | 27 | # You can find 'credentials.json' file which keeps your MTProto secerets. 28 | ls -al credentials.json 29 | 30 | # You can check if the scerets correct by sign-in with it. 31 | go run examples/simpleshell/main.go credentials.json 32 | ``` 33 | 34 | ## Usage 35 | You can find the real code at [simpleshell](https://github.com/cjongseok/mtproto/blob/master/examples/simpleshell/main.go). 36 | ### Sign-in with key 37 | ```go 38 | // Mew MTProto manager 39 | config, _ := mtproto.NewConfiguration(appVersion, deviceModel, systemVersion, language, 0, 0, "credentials.json") 40 | manager, _ := mtproto.NewManager(config) 41 | 42 | // Sign-in by key 43 | mconn, _ := manager.LoadAuthentication() 44 | ``` 45 | ### Sign-in without key 46 | ```go 47 | // New MTProto manager 48 | config, _ := mtproto.NewConfiguration(appVersion, deviceModel, systemVersion, language, 0, 0, "new-credentials.json") 49 | manager, _ := mtproto.NewManager(config) 50 | 51 | // Request to send an authentication code 52 | // It needs your phone number and Telegram API stuffs you can check in https://my.telegram.org/apps 53 | mconn, sentCode, err := manager.NewAuthentication(phoneNumber, apiID, apiHash, ip, port) 54 | 55 | // Get the code from user input 56 | fmt.Scanf("%s", &code) 57 | 58 | // Sign-in and generate the new key 59 | _, err = mconn.SignIn(phoneNumber, code, sentCode.GetValue().PhoneCodeHash) 60 | ``` 61 | ### Telegram RPC in Protocol Buffer 62 | cjongseok/mtproto implements [TL-schema](https://core.telegram.org/schema) [functions](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/compiler/scheme-71.tl#L951) in [Protocol Buffer](https://developers.google.com/protocol-buffers/). These are declared in [types.tl.proto](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/types.tl.proto#L6385) as RPCs, and implemented in Go at [procs.tl.go](https://github.com/cjongseok/mtproto/blob/master/procs.tl.go). With the same interface, you can call functions not only in direct connection to the Telegram server, but also over a mtproto proxy. 63 | 64 | Let's have two direct call examples, [messages.getDialogs](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/compiler/scheme-71.tl#L1025) and [messages.sendMessage](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/compiler/scheme-71.tl#L1033). 65 | #### Get dialogs 66 | ```go 67 | // New RPC caller with a connection to Telegram server. 68 | // By alternating mconn with a proxy connection, you can call same functions over proxy. It is covered later. 69 | caller := mtproto.RPCaller{mconn} 70 | 71 | // New input peer 72 | // In Telegram DSL, Predicates inherit a Type. 73 | // Here we create a Predicate, InputPeerEmpty whose parent is InputPeer. 74 | // More details about these types are covered later. 75 | emptyPeer := &mtproto.TypeInputPeer{&mtproto.TypeInputPeer_InputPeerEmpty{&mtproto.PredInputPeerEmpty{}} 76 | 77 | // Query to Telegram 78 | dialogs, _ := caller.MessagesGetDialogs(context.Background(), &mtproto.ReqMessagesGetDialogs{ 79 | OffsetDate: 0, 80 | OffsetId: 0, 81 | OffsetPeer: emptyPeer, 82 | Limit: 1, 83 | }) 84 | ``` 85 | #### Send a message to a channel 86 | ```go 87 | // New RPC caller with a connection to Telegram server. 88 | caller := mtproto.RPCaller{mconn} 89 | 90 | // New input peer 91 | // Create a Predicate, InputPeerChannel, wraped by its parent Type, InputPeer. 92 | channelPeer := &mtproto.TypeInputPeer{&mtproto.TypeInputPeer_InputPeerChannel{ 93 | &mtproto.PredInputPeerChannel{ 94 | yourChannelId, yourChannelHash, 95 | }}} 96 | 97 | // Send a request to Telegram 98 | caller.MessagesSendMessage(context.Background(), &mtproto.ReqMessagesSendMessage{ 99 | Peer: channelPeer, 100 | Message: "Hello MTProto", 101 | RandomId: rand.Int63(), 102 | }) 103 | ``` 104 | 105 | ## How mtproto is impelemented in Protocol Buffer 106 | ### Types 107 | Telegram's mtproto has three kinds of types, Type, Predicate, and Method. A Type is a kind of a data structure interface which has no fields, and a Predicate implements a Type. In the above case, mtproto.[PredInputPeerChannel](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/types.tl.proto#L3046) is a Predicate of a Type mtproto.[TypeInputPeer](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/types.tl.proto#L19). gRPC recommends to implement this kind of polymorphism with [Oneof](https://developers.google.com/protocol-buffers/docs/proto3#oneof), so [InputPeer](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/compiler/scheme-71.tl#L38) is defined in Protocol Buffer as below: 108 | ```protobuf 109 | // types.tl.proto:19 110 | message TypeInputPeer { 111 | oneof Value { 112 | PredInputPeerEmpty InputPeerEmpty = 1; 113 | PredInputPeerSelf InputPeerSelf = 2; 114 | PredInputPeerChat InputPeerChat = 3; 115 | PredInputPeerUser InputPeerUser = 4; 116 | PredInputPeerChannel InputPeerChannel = 5; 117 | } 118 | } 119 | ``` 120 | The use of gRPC Oneof in Go is complex, because Go does not allow hierarchical relations among types, e.g., inheritance. I believe, however, gRPC guys did their best and it would be the best implementation of such polymorphism in Go with RPC. For more details about the use of OneOf in Go, please refer to [this document](https://developers.google.com/protocol-buffers/docs/reference/go-generated#oneof). 121 | 122 | ### Methods 123 | Mtproto methods have a namespace as you can see in [TL-schema](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/compiler/scheme-71.tl#L951), e.g., auth, account, users, ... . Instead of managing these namespaces as separate [Protocol Buffer services](https://developers.google.com/protocol-buffers/docs/proto3#services), these are integrated into one Protocol Buffer Service, [Mtproto](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/types.tl.proto#L6385), and namesapces are remained as method name prefixes. In the above example, the original name of [MessagesSendMessage](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/types.tl.proto#L6425) is [messages.sendMessage](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/compiler/scheme-71.tl#L1033). 124 | 125 | In the original schema, a method can have multiple parameters. These paremeters are declared into a new data structure in Protocol Buffer whose name starts with 'Req'. For example, [messages.sendMessage](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/compiler/scheme-71.tl#L1033) requires many parameters, and these are transformed into [ReqMessagesSendMessage](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/types.tl.proto#L5165) in [MessagesSendMessage](https://github.com/cjongseok/mtproto/blob/142b7f31f963a074dac9dd6759e0ae054e4a894c/types.tl.proto#L6425). 126 | 127 | # Proxy 128 | You can use the proxy in two purposes: 129 | * MTProto session sharing: Many proxy clients can use the same MTProto session on the proxy server. 130 | * MTProto in other languages: The proxy enables various languages on its client side, since it uses gRPC. 131 | 132 | ## Server 133 | ### As a stand-alone daemon 134 | [mtprotod](https://github.com/cjongseok/mtproto/tree/master/mtprotod) is a stand-alone proxy daemon containing Telegram MTProto implementation in Go. 135 | 136 | #### Quick Start 137 | ```bash 138 | # start mtprotod at port 11011 139 | docker run \ 140 | -p 11011: 11011 \ 141 | -v /your/mtproto/secrets/directory:/opt \ 142 | cjongseok/mtprotod start \ 143 | --port 11011 \ 144 | --addr \ 145 | --apiid \ 146 | --apihash \ 147 | --phone \ 148 | --secrets /opt/ 149 | 150 | # At mtproto/proxy, let's get dialogs through over the proxy 151 | dep ensure 152 | go test proxy/proxy_test.go --run TestDialogs 153 | ``` 154 | 155 | #### Build & Run 156 | ```bash 157 | # In mtproto directory 158 | dep ensure 159 | go run mtprotod/main.go start \ 160 | --addr \ 161 | --apiid \ 162 | --apihash \ 163 | --phone \ 164 | --port \ 165 | --secrets /opt/ 166 | ``` 167 | 168 | ### As a part of Go application 169 | Use mtproto/proxy package. 170 | ```go 171 | // New proxy server 172 | config, _ := mtproto.NewConfiguration(apiId, apiHash, appVersion, deviceModel, systemVersion, language, 0, 0, key) 173 | server = proxy.NewServer(port) 174 | 175 | // Start the server 176 | server.Start(config, phone) 177 | ``` 178 | ## Client in Go 179 | ```go 180 | // New proxy client 181 | client, _ := proxy.NewClient(proxyAddr) 182 | 183 | // Telegram RPC over proxy. It is same with the previous 'Get dialogs' but the RPC caller 184 | emptyPeer := &mtproto.TypeInputPeer{&mtproto.TypeInputPeer_InputPeerEmpty{&mtproto.PredInputPeerEmpty{}} 185 | dialogs, err := client.MessagesGetDialogs(context.Background(), &mtproto.ReqMessagesGetDialogs{ 186 | OffsetDate: 0, 187 | OffsetId: 0, 188 | OffsetPeer: emptyPeer, 189 | Limit: 1, 190 | }) 191 | ``` 192 | ## Client in Python 193 | See [py](https://github.com/cjongseok/mtproto/tree/master/py). 194 | ## Client in other languages 195 | By compiling [types.tl.proto](https://github.com/cjongseok/mtproto/tree/master/types.tl.proto) and [proxy/tl_update.proto](https://github.com/cjongseok/mtproto/tree/master/proxy/tl_update.proto), 196 | you can create clients in your preferred language.
197 | For this, you need to install [Protocol Buffer](https://developers.google.com/protocol-buffers/) together with gRPC library of the target language. 198 | Then you can compile Protocol Buffer files with this kind of command lines: 199 | * [Go](https://github.com/cjongseok/mtproto/blob/master/compiler/build.sh) 200 | * [Python](https://github.com/cjongseok/mtproto/blob/master/compiler/build_py.sh) 201 | 202 | You can find these command lines for other languages in [gRPC tutorial](https://grpc.io/docs/). 203 | 204 | 214 | 215 | 216 | ## Acknowledgement 217 | * https://github.com/sdidyk/mtproto: It is the backend of most MTProto Go implementations. 218 | I referred its MTProto schema compiler, (de)serializer, handshaking, and encryption. 219 | * https://github.com/shelomentsevd/mtproto: I referred its layer 65 implementation and API wrappers. 220 | * https://github.com/ronaksoft/mtproto: I referred its backend changes for layer 71. 221 | 222 | 223 | ## License 224 | Apache 2.0 225 | -------------------------------------------------------------------------------- /access.go: -------------------------------------------------------------------------------- 1 | package mtproto 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "fmt" 7 | "github.com/golang/protobuf/proto" 8 | ) 9 | 10 | // AccessManager keeps channel and user accesses 11 | // Fetching the accesses from the Telegram on every app launching can cause over rate limits, 12 | // if the account has lots of channels and users. AccessManager helps to manage theses accesses 13 | // as json files. Use tools/access to export them. 14 | type AccessManager struct { 15 | //accesses map[int32]Access 16 | channels map[int32]Access 17 | users map[int32]Access 18 | } 19 | 20 | type Access struct { 21 | ID int32 22 | //Required bool 23 | Hash int64 24 | } 25 | 26 | // NewAccessManager instantiate a AccessManager 27 | // If chats or contacts are given, it returns the instance fulfilled 28 | // with the given id and hashes. Empty, otherwise. 29 | func NewAccessManager(chats *TypeMessagesChats, contacts *TypeContactsContacts) (hm *AccessManager) { 30 | hm = &AccessManager{make(map[int32]Access), make(map[int32]Access)} 31 | if chats != nil { 32 | hm.importTypeMessagesChats(chats) 33 | } 34 | if contacts != nil { 35 | hm.importTypeContactsContacts(contacts) 36 | } 37 | return 38 | } 39 | 40 | func (am *AccessManager) importTypeContactsContacts(t *TypeContactsContacts) { 41 | p := t.GetContactsContacts() 42 | if p != nil { 43 | for _, u := range p.Users { 44 | predUser := u.GetUser() 45 | if predUser != nil { 46 | am.users[predUser.Id] = Access{ 47 | predUser.Id, 48 | predUser.AccessHash, 49 | } 50 | } 51 | } 52 | } 53 | } 54 | func typeChatToAccess(t *TypeChat) *Access { 55 | if t == nil { 56 | return nil 57 | } 58 | if channel := t.GetChannel(); channel != nil { 59 | return &Access{channel.Id, channel.AccessHash} 60 | } else if chanForbidden := t.GetChannelForbidden(); chanForbidden != nil { 61 | return &Access{chanForbidden.Id, chanForbidden.AccessHash} 62 | } 63 | 64 | return nil 65 | } 66 | func (am *AccessManager) importTypeMessagesChats(c *TypeMessagesChats) { 67 | if c == nil { 68 | return 69 | } 70 | //fmt.Println(slog.StringifyIndent(c, " ")) 71 | if chats := c.GetMessagesChats(); chats != nil { 72 | for _, chat := range chats.Chats { 73 | access := typeChatToAccess(chat) 74 | if access != nil { 75 | am.channels[access.ID] = *access 76 | } 77 | } 78 | } else if chatsSlice := c.GetMessagesChatsSlice(); chatsSlice != nil { 79 | for _, slice := range chatsSlice.Chats { 80 | access := typeChatToAccess(slice) 81 | if access != nil { 82 | am.channels[access.ID] = *access 83 | } 84 | } 85 | } 86 | } 87 | 88 | func (am *AccessManager) ImportUserAccessesFromFile(filepath string) error { 89 | f, err := os.OpenFile(filepath, os.O_RDONLY, 644) 90 | if err != nil { 91 | return err 92 | } 93 | b, err := ioutil.ReadAll(f) 94 | if err != nil { 95 | return err 96 | } 97 | unmarshaled := &TypeContactsContacts{} 98 | err = proto.Unmarshal(b, unmarshaled) 99 | if err != nil { 100 | return err 101 | } 102 | am.importTypeContactsContacts(unmarshaled) 103 | return nil 104 | } 105 | func (am *AccessManager) ImportChanAccessesFromFile(filepath string) error { 106 | f, err := os.OpenFile(filepath, os.O_RDONLY, 644) 107 | if err != nil { 108 | return fmt.Errorf("open file failure; %v", err) 109 | } 110 | b, err := ioutil.ReadAll(f) 111 | if err != nil { 112 | return fmt.Errorf("read file failure; %v", err) 113 | } 114 | unmarshaled := &TypeMessagesChats{} 115 | err = proto.Unmarshal(b, unmarshaled) 116 | if err != nil { 117 | return fmt.Errorf("unmarshal failure; %v", err) 118 | } 119 | am.importTypeMessagesChats(unmarshaled) 120 | return nil 121 | } 122 | 123 | func (am *AccessManager) ChannelAccess(id int32) Access { 124 | return am.channels[id] 125 | } 126 | func (am *AccessManager) UserAccess(id int32) Access { 127 | return am.users[id] 128 | } 129 | func (am *AccessManager) Users() []int32 { 130 | var ids []int32 131 | for id, _ := range am.users { 132 | ids = append(ids, id) 133 | } 134 | return ids 135 | } 136 | func (am *AccessManager) Channels() []int32 { 137 | var ids []int32 138 | for id, _ := range am.channels { 139 | ids = append(ids, id) 140 | } 141 | return ids 142 | } 143 | -------------------------------------------------------------------------------- /compiler/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm ../types.tl.proto ../convs.tl.go ../procs.tl.go 4 | go run tl2go.go < tl-schema-71.json 5 | mv types.tl.proto convs.tl.go procs.tl.go ../ 6 | protoc -I .. -I ~/Programs/protoc-3.5.1/include types.tl.proto --go_out=plugins=grpc:../ 7 | protoc -I $GOPATH/src -I ../proxy tl_update.proto --go_out=plugins=grpc:../proxy 8 | go fmt ../ 9 | go fmt ../proxy 10 | -------------------------------------------------------------------------------- /compiler/build_py.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python3 -m pip install grpcio-tools 3 | cp ../types.tl.proto ../tl.proto 4 | cp ../proxy/tl_update.proto ../proxy/update.proto 5 | python3 -m grpc_tools.protoc -I .. -I ~/Programs/protoc-3.5.1/include --python_out=../py --grpc_python_out=../py ../tl.proto 6 | python3 -m grpc_tools.protoc -I $GOPATH/src -I ../proxy --python_out=../py --grpc_python_out=../py update.proto 7 | rm ../tl.proto ../proxy/update.proto 8 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package mtproto 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | const ( 10 | appConfigError = "App configuration error: %s" 11 | defaultPingInterval = 1 * time.Minute 12 | defaultSendInterval = 500 * time.Millisecond 13 | ) 14 | 15 | type Configuration struct { 16 | //Id int32 17 | //Hash string 18 | Version string 19 | DeviceModel string 20 | SystemVersion string 21 | Language string 22 | //SessionHome string 23 | PingInterval time.Duration 24 | SendInterval time.Duration 25 | KeyPath string 26 | } 27 | 28 | func NewConfiguration(version, deviceModel, systemVersion, language string, 29 | pingInterval time.Duration, sendInterval time.Duration, keyPath string) (Configuration, error) { 30 | //appConfig := new(Configuration) 31 | appConfig := Configuration{} 32 | 33 | if version == "" { 34 | return Configuration{}, fmt.Errorf(appConfigError, "version is empty") 35 | } 36 | appConfig.Version = version 37 | 38 | appConfig.DeviceModel = deviceModel 39 | if deviceModel == "" { 40 | appConfig.DeviceModel = "Unknown" 41 | } 42 | 43 | appConfig.SystemVersion = systemVersion 44 | if systemVersion == "" { 45 | appConfig.SystemVersion = runtime.GOOS + "/" + runtime.GOARCH 46 | } 47 | 48 | appConfig.Language = language 49 | if language == "" { 50 | appConfig.Language = "en" 51 | } 52 | 53 | //appConfig.SessionHome = sessionFileHome 54 | //if sessionFileHome == "" { 55 | // usr, err := user.Current() 56 | // if err != nil { 57 | // appConfig.SessionHome = os.Getenv("HOME") 58 | // } else { 59 | // appConfig.SessionHome = usr.HomeDir 60 | // } 61 | //} 62 | appConfig.KeyPath = keyPath 63 | 64 | appConfig.PingInterval = pingInterval 65 | if pingInterval == 0 { 66 | appConfig.PingInterval = defaultPingInterval 67 | } 68 | 69 | appConfig.SendInterval = sendInterval 70 | if sendInterval == 0 { 71 | appConfig.SendInterval = defaultSendInterval 72 | } 73 | 74 | return appConfig, nil 75 | } 76 | 77 | func (appConfig Configuration) Check() error { 78 | if appConfig.Version == "" { 79 | return fmt.Errorf(appConfigError, "Configuration.Version is empty") 80 | } 81 | 82 | if appConfig.DeviceModel == "" { 83 | return fmt.Errorf(appConfigError, "Configuration.DeviceModel is empty") 84 | } 85 | 86 | if appConfig.SystemVersion == "" { 87 | return fmt.Errorf(appConfigError, "Configuration.SystemVersion is empty") 88 | } 89 | 90 | if appConfig.Language == "" { 91 | return fmt.Errorf(appConfigError, "Configuration.Language is empty") 92 | } 93 | 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package mtproto 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cjongseok/slog" 6 | "math/rand" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | const ( 12 | //TODO: elastic timeout 13 | TIMEOUT_RPC = 5 * time.Second 14 | TIMEOUT_INVOKE_WITH_LAYER = 10 * time.Second 15 | TIMEOUT_UPDATES_GETSTATE = 7 * time.Second 16 | TIMEOUT_SESSION_BINDING = TIMEOUT_INVOKE_WITH_LAYER + TIMEOUT_UPDATES_GETSTATE 17 | //DELAY_RETRY_OPEN_SESSION = 1 * time.Second 18 | ) 19 | 20 | // Conn is mtproto connection. Conn guarantees it is always wired-up with Telegram server, although 21 | // Session can expire anytime without notice. 22 | type Conn struct { 23 | connID int32 24 | session Session 25 | smonitor chan Event 26 | interrupter chan struct{} 27 | bindSession chan Session 28 | sessionReqs []chan Session 29 | sessionReqsMux *sync.Mutex 30 | listeners []chan Event 31 | updateCallbacks []UpdateCallback 32 | callbackMux *sync.Mutex 33 | discardedUpdatesState *PredUpdatesState 34 | } 35 | 36 | // newConnection creates a Session instance. Other session actions such as 'open', 'close', 37 | // 'bind (to a conn)', 'unbind (from a conn)', 'register (to the manager)', and 'de-register (from 38 | // the manager)' are controlled by Manager. 39 | func newConnection(connListener chan Event) *Conn { 40 | mconn := new(Conn) 41 | rand.Seed(time.Now().UnixNano()) 42 | mconn.connID = rand.Int31() 43 | mconn.smonitor = make(chan Event) 44 | mconn.interrupter = make(chan struct{}) 45 | mconn.AddConnListener(connListener) 46 | mconn.AddConnListener(mconn.smonitor) 47 | mconn.bindSession = make(chan Session) 48 | mconn.sessionReqsMux = &sync.Mutex{} 49 | mconn.callbackMux = &sync.Mutex{} 50 | 51 | go mconn.monitorSession() 52 | 53 | mconn.notify(ConnectionOpened{mconn, 0}) 54 | //return mconn, nil 55 | return mconn 56 | } 57 | 58 | // bind attaches a Session to the Conn. If the Conn already has a Session, it alternates the old 59 | // one. 60 | func (mconn *Conn) bind(session *Session) error { 61 | slog.Logln(mconn, "bind session", session.sessionID) 62 | if session == nil { 63 | return fmt.Errorf("nil ssession") 64 | } 65 | session.AddSessionListener(mconn.smonitor) 66 | session.connID = mconn.connID 67 | slog.Logln(mconn, "pass the session to bindSession ch") 68 | mconn.bindSession <- *session 69 | slog.Logln(mconn, "sesssin passed") 70 | mconn.notify(sessionBound{mconn, session.sessionID}) 71 | 72 | //TODO: get updates difference on opening session rather than its binding 73 | // req updates, if exists 74 | if mconn.discardedUpdatesState != nil { 75 | ptsDiff := session.updatesState.Pts - mconn.discardedUpdatesState.Pts 76 | qtsDiff := session.updatesState.Qts - mconn.discardedUpdatesState.Qts 77 | seqDiff := session.updatesState.Seq - mconn.discardedUpdatesState.Seq 78 | if ptsDiff > 0 || qtsDiff > 0 || seqDiff > 0 { 79 | // missed updates exist. Propagate updates to callbacks 80 | updatesDiff, err := mconn.InvokeBlocked(&ReqUpdatesGetDifference{ 81 | Pts: mconn.discardedUpdatesState.Pts, 82 | PtsTotalLimit: 0, 83 | Date: mconn.discardedUpdatesState.Date, 84 | Qts: mconn.discardedUpdatesState.Qts}) 85 | if err != nil { 86 | return fmt.Errorf("failed to get update difference") 87 | } 88 | 89 | switch udiff := updatesDiff.(type) { 90 | case *PredUpdatesDifferenceEmpty: 91 | slog.Logln(mconn, "bind: diff: empty") 92 | case *PredUpdatesDifference: 93 | slog.Logln(mconn, "bind: diff:", udiff) 94 | mconn.propagate(udiff) 95 | case *PredUpdatesDifferenceSlice: 96 | slog.Logln(mconn, "bind: diff: slice:", udiff) 97 | mconn.propagate(udiff) 98 | case *PredUpdatesDifferenceTooLong: 99 | slog.Logln(mconn, "bind: diff: too long") 100 | default: 101 | slog.Logln(mconn, "bind: no diff") 102 | } 103 | } 104 | mconn.discardedUpdatesState = nil 105 | } else { 106 | slog.Logln(mconn, "bind: mconn.discardedUpdatesState is nil") 107 | } 108 | return nil 109 | } 110 | 111 | func (mconn *Conn) InvokeBlocked(msg TL) (interface{}, error) { 112 | // TODO: timeout the call 113 | select { 114 | case x := <-mconn.InvokeNonBlocked(msg): 115 | if x.err == nil { 116 | return x.data, nil 117 | } 118 | return nil, x.err 119 | 120 | case <-time.After(TIMEOUT_RPC): 121 | return nil, fmt.Errorf("RPC Timeout(%f s)", TIMEOUT_RPC.Seconds()) 122 | } 123 | } 124 | 125 | func (mconn *Conn) InvokeNonBlocked(msg TL) chan response { 126 | resp := make(chan response, 1) 127 | 128 | // get session 129 | var session Session 130 | res := <-mconn.Session() 131 | switch res.(type) { 132 | case Session: 133 | session = res.(Session) 134 | case error: 135 | resp <- response{nil, res.(error)} 136 | return resp 137 | } 138 | 139 | session.queueSend <- packetToSend{ 140 | msg: msg, 141 | resp: resp, 142 | } 143 | return resp 144 | } 145 | 146 | // Session returns the bound bound of the conn. The direct access to the session (using '.') does 147 | // not guarantee both not-nil and data racing. The returned session can expire any time, so that it 148 | // cannot match with the latest bound session of the conn. 149 | func (mconn *Conn) Session() <-chan interface{} { 150 | sessionCh := make(chan Session, 1) 151 | slog.Logln(mconn, "session request", sessionCh) 152 | if mconn.session.sessionID != 0 { 153 | go func(send chan<- Session, session Session) { 154 | send <- session 155 | }(sessionCh, mconn.session) 156 | } else { 157 | mconn.sessionReqsMux.Lock() 158 | mconn.sessionReqs = append(mconn.sessionReqs, sessionCh) 159 | mconn.sessionReqsMux.Unlock() 160 | } 161 | 162 | promise := make(chan interface{}) 163 | go func(raceResult chan interface{}, receiveSession chan Session) { 164 | select { 165 | case newSession := <-receiveSession: 166 | raceResult <- newSession 167 | case <-time.After(TIMEOUT_SESSION_BINDING): 168 | raceResult <- fmt.Errorf("session binding timeout (%v)", TIMEOUT_SESSION_BINDING) 169 | } 170 | }(promise, sessionCh) 171 | return promise 172 | } 173 | 174 | func (mconn *Conn) MigrateSessionTo(newdc int32) error { 175 | // get session 176 | var session Session 177 | res := <-mconn.Session() 178 | switch res.(type) { 179 | case Session: 180 | session = res.(Session) 181 | case error: 182 | return res.(error) 183 | } 184 | 185 | // reconnect to the new datacenter 186 | respch := make(chan sessionResponse, 1) 187 | ipVersion := ipv4 188 | if isIPv6(session.c.IP) { 189 | ipVersion = ipv6 190 | } 191 | dcOption, err := session.apiDcOption(ipVersion, newdc) 192 | if err != nil { 193 | return err 194 | } 195 | slog.Logln(mconn, "migrate session to", dcOption) 196 | 197 | //TODO: Check if renewSession event works with mconn.notify() 198 | mconn.notify(renewSession{ 199 | session.sessionID, 200 | session.c.Phone, 201 | session.c.ApiID, 202 | session.c.ApiHash, 203 | dcOption.IpAddress, 204 | int(dcOption.Port), 205 | respch, 206 | }) 207 | 208 | // Wait for binding the new session 209 | resp := <-respch 210 | return resp.err 211 | } 212 | 213 | // finish connection's internal resource but bound session. 214 | // closing/deregistering session occurs through closeConnection event on Manager 215 | // which is the only caller of this method. 216 | func (mconn *Conn) close() { 217 | close(mconn.interrupter) 218 | close(mconn.smonitor) 219 | 220 | // notify the connection is closed 221 | mconn.notify(connectionClosed{mconn.connID}) 222 | } 223 | 224 | func (mconn *Conn) AddConnListener(listener chan Event) { 225 | mconn.listeners = append(mconn.listeners, listener) 226 | } 227 | 228 | func (mconn *Conn) AddUpdateCallback(callback UpdateCallback) { 229 | mconn.callbackMux.Lock() 230 | defer mconn.callbackMux.Unlock() 231 | mconn.updateCallbacks = append(mconn.updateCallbacks, callback) 232 | 233 | } 234 | 235 | func (mconn *Conn) RemoveConnListener(toremove chan Event) error { 236 | for index, registered := range mconn.listeners { 237 | if registered == toremove { 238 | copy(mconn.listeners[index:], mconn.listeners[index+1:]) 239 | mconn.listeners[len(mconn.listeners)-1] = nil 240 | mconn.listeners = mconn.listeners[:len(mconn.listeners)-1] 241 | return nil 242 | } 243 | } 244 | return fmt.Errorf("listener (%v) doesn't exist", toremove) 245 | } 246 | 247 | func (mconn *Conn) RemoveUpdateListener(toremove UpdateCallback) error { 248 | for index, registered := range mconn.updateCallbacks { 249 | if registered == toremove { 250 | copy(mconn.updateCallbacks[index:], mconn.updateCallbacks[index+1:]) 251 | mconn.updateCallbacks[len(mconn.updateCallbacks)-1] = nil 252 | mconn.updateCallbacks = mconn.updateCallbacks[:len(mconn.updateCallbacks)-1] 253 | return nil 254 | } 255 | } 256 | return fmt.Errorf("UpdateCallback (%x) doesn't exist", toremove) 257 | } 258 | 259 | func (mconn *Conn) notify(event Event) { 260 | mconn.callbackMux.Lock() 261 | defer mconn.callbackMux.Unlock() 262 | for _, listener := range mconn.listeners { 263 | go func(l chan Event, e Event) { 264 | l <- e 265 | }(listener, event) 266 | } 267 | } 268 | 269 | func (mconn *Conn) propagate(u Update) { 270 | for _, callback := range mconn.updateCallbacks { 271 | go func(cb UpdateCallback) { 272 | cb.OnUpdate(u) 273 | }(callback) 274 | } 275 | } 276 | 277 | func (mconn *Conn) monitorSession() { 278 | slog.Logln(mconn, "start") 279 | for { 280 | select { 281 | case <-mconn.interrupter: 282 | slog.Logln(mconn, "stop") 283 | return 284 | case newSession := <-mconn.bindSession: 285 | mconn.session = newSession 286 | go func(session Session) { 287 | mconn.sessionReqsMux.Lock() 288 | defer mconn.sessionReqsMux.Unlock() 289 | for _, req := range mconn.sessionReqs { 290 | go func(c chan Session, s Session){ 291 | c <- s 292 | }(req, session) 293 | } 294 | mconn.sessionReqs = nil 295 | }(mconn.session) 296 | case e := <-mconn.smonitor: 297 | slog.Logf(mconn, "session event %T(%v)\n", e, e) 298 | switch e.(type) { 299 | // Session Events 300 | case newsession: // never triggered on mconn 301 | case loadsession: // never triggered on mconn 302 | case SessionEstablished: // never triggered on mconn 303 | case discardSession: // triggered only on reconnect (either renewSession or refreshSession) 304 | // Unbind the session until the connection has new session 305 | mconn.session = Session{} 306 | slog.Logf(mconn, "session(%d) will be discarded\n", e.(discardSession).sessionId) 307 | go func(sid int64) { 308 | unbound := sessionUnbound{mconn, sid} 309 | // notify that inside selection needs non-blocking handlers 310 | mconn.notify(unbound) 311 | }(e.(discardSession).sessionId) 312 | case SessionDiscarded: // triggered only on reconnect (either renewSession or refreshSession) 313 | case renewSession: 314 | case refreshSession: 315 | 316 | // Connection Events 317 | case ConnectionOpened: 318 | slog.Logln(mconn, "opened") 319 | if e.(ConnectionOpened).sessionID != 0 { 320 | slog.Logln(mconn, "wait for a session binding ...") 321 | } else { 322 | slog.Logln(mconn, "with session,", e.(ConnectionOpened).sessionID) 323 | } 324 | case sessionBound: 325 | slog.Logln(mconn, "bound to session", e.(sessionBound).sessionID) 326 | case sessionUnbound: 327 | e := e.(sessionUnbound) 328 | slog.Logln(mconn, "unbound to session", e.unboundSessionID) 329 | case closeConnection: 330 | slog.Logln(mconn, "this connection will close") 331 | case connectionClosed: 332 | slog.Logln(mconn, "closed") 333 | 334 | // Update Event 335 | case updateReceived: 336 | go func() { 337 | slog.Logln(mconn, "received an update, ", e.(updateReceived).update) 338 | mconn.propagate(e.(updateReceived).update) 339 | }() 340 | default: 341 | } 342 | } 343 | } 344 | } 345 | 346 | func (mconn *Conn) SignIn(phoneNumber, phoneCode, phoneCodeHash string) (*TypeAuthAuthorization, error) { 347 | if phoneNumber == "" || phoneCode == "" || phoneCodeHash == "" { 348 | return nil, fmt.Errorf("empty sign-in argument") 349 | } 350 | 351 | x := <-mconn.InvokeNonBlocked(&ReqAuthSignIn{ 352 | PhoneNumber: phoneNumber, 353 | PhoneCodeHash: phoneCodeHash, 354 | PhoneCode: phoneCode, 355 | }) 356 | if x.err != nil { 357 | return nil, x.err 358 | } 359 | 360 | auth, ok := x.data.(*PredAuthAuthorization) 361 | if !ok { 362 | return nil, fmt.Errorf("RPC: %v", x) 363 | } 364 | 365 | // get session 366 | var session Session 367 | res := <-mconn.Session() 368 | switch res.(type) { 369 | case Session: 370 | session = res.(Session) 371 | case error: 372 | return &TypeAuthAuthorization{Value: auth}, res.(error) 373 | } 374 | 375 | if auth.GetUser().GetUser() != nil { 376 | session.user = auth.GetUser().GetUser() 377 | slog.Logln(mconn, "Signed in as ", session.user) 378 | } else if auth.GetUser().GetUserEmpty() != nil { 379 | session.user = &PredUser{} 380 | slog.Logln(mconn, "Signed in with empty user") 381 | } else { 382 | session.user = &PredUser{} 383 | slog.Logln(mconn, "Signed in without user response: neither user nor user empty") 384 | } 385 | return &TypeAuthAuthorization{Value: auth}, nil 386 | } 387 | 388 | func (mconn *Conn) SignOut() (bool, error) { 389 | var result bool 390 | x := <-mconn.InvokeNonBlocked(&ReqAuthLogOut{}) 391 | if x.err != nil { 392 | return result, x.err 393 | } 394 | 395 | if tl, ok := x.data.(TL); ok { 396 | return toBool(tl), nil 397 | } 398 | return false, fmt.Errorf("invalid rpc return: %T: %v", x.data, x.data) 399 | } 400 | 401 | func (x *Conn) LogPrefix() string { 402 | return fmt.Sprintf("[mconn %d]", x.connID) 403 | } 404 | -------------------------------------------------------------------------------- /credentials.go: -------------------------------------------------------------------------------- 1 | package mtproto 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "errors" 7 | "os" 8 | ) 9 | 10 | type Credentials struct { 11 | Phone string 12 | ApiID int32 13 | ApiHash string 14 | IP string 15 | Port int 16 | Salt []byte 17 | AuthKey []byte 18 | AuthKeyHash []byte 19 | } 20 | 21 | type credentialsJSON struct { 22 | Phone string `json:"phone"` 23 | ApiID int32 `json:"api_id"` 24 | ApiHash string `json:"api_hash"` 25 | IP string `json:"ip"` 26 | Port int `json:"port"` 27 | Salt string `json:"salt"` 28 | AuthKey string `json:"auth_key"` 29 | } 30 | 31 | // SaveToFile stores the Credentials as a JSON file in the format of credentialsJson. 32 | // It creates the file, if not exists, and overwrite the file, if exists. 33 | 34 | // JSON turns the given credentials into JSON binary in the format of credentialsJSON 35 | func (c *Credentials) JSON() ([]byte, error) { 36 | return json.Marshal(credentialsJSON{ 37 | c.Phone, 38 | c.ApiID, 39 | c.ApiHash, 40 | c.IP, 41 | c.Port, 42 | //string(c.Salt), 43 | base64.StdEncoding.EncodeToString(c.Salt), 44 | base64.StdEncoding.EncodeToString(c.AuthKey), 45 | }) 46 | } 47 | func NewCredentials(jsonInBytes []byte) (c *Credentials, err error) { 48 | unmarshaled := &credentialsJSON{} 49 | err = json.Unmarshal(jsonInBytes, unmarshaled) 50 | if err != nil { 51 | return 52 | } 53 | var salt, authKey, authKeyHash []byte 54 | salt, err = base64.StdEncoding.DecodeString(unmarshaled.Salt) 55 | if err != nil { 56 | return 57 | } 58 | authKey, err = base64.StdEncoding.DecodeString(unmarshaled.AuthKey) 59 | if err != nil { 60 | return 61 | } 62 | authKeyHash = sha1(authKey)[12:20] 63 | c = &Credentials{ 64 | unmarshaled.Phone, 65 | unmarshaled.ApiID, 66 | unmarshaled.ApiHash, 67 | unmarshaled.IP, 68 | unmarshaled.Port, 69 | //[]byte(unmarshaled.Salt), 70 | salt, 71 | authKey, 72 | authKeyHash, 73 | } 74 | return 75 | } 76 | 77 | // Save session 78 | //TODO: save channel and datacenter information 79 | func (c *Credentials) Save(f *os.File) (err error) { 80 | /*session.encrypted = true 81 | b := NewEncodeBuf(1024) 82 | b.StringBytes(session.authKey) 83 | b.StringBytes(session.authKeyHash) 84 | b.StringBytes(session.serverSalt) 85 | b.String(session.addr) 86 | var useIPv6UInt uint32 87 | if session.useIPv6 { 88 | useIPv6UInt = 1 89 | } 90 | b.UInt(useIPv6UInt) 91 | */ 92 | 93 | var jsonBytes []byte 94 | jsonBytes, err = c.JSON() 95 | if err != nil { 96 | return err 97 | } 98 | 99 | err = f.Truncate(0) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | _, err = f.WriteAt(jsonBytes, 0) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | return nil 110 | } 111 | 112 | func NewCredentialsFromFile(f *os.File) (*Credentials, error) { 113 | // Decode session file 114 | b := make([]byte, 1024*4) 115 | n, err := f.ReadAt(b, 0) 116 | if n <= 0 || (err != nil && err.Error() != "EOF") { 117 | return nil, errors.New("nothing in the file") 118 | } 119 | return NewCredentials(b[:n]) 120 | 121 | //d := NewDecodeBuf(b) 122 | //session.authKey = d.StringBytes() 123 | //session.authKeyHash = d.StringBytes() 124 | //session.serverSalt = d.StringBytes() 125 | //session.addr = d.String() 126 | //session.useIPv6 = false 127 | //if d.UInt() == 1 { 128 | // session.useIPv6 = true 129 | //} 130 | // 131 | //if d.err != nil { 132 | // // Failed to load session 133 | // return d.err 134 | //} 135 | // 136 | //session.encrypted = true 137 | //return nil 138 | } 139 | -------------------------------------------------------------------------------- /crypto.go: -------------------------------------------------------------------------------- 1 | package mtproto 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/rsa" 7 | sha1lib "crypto/sha1" 8 | "encoding/binary" 9 | "errors" 10 | "fmt" 11 | "math/big" 12 | "math/rand" 13 | "time" 14 | ) 15 | 16 | // TODO: Think about configurable public key 17 | const ( 18 | telegramPublicKey_N = "24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407" 19 | telegramPublicKey_E = 65537 20 | telegramPublicKey_FP = 14101943622620965665 21 | ) 22 | 23 | var telegramPublicKey rsa.PublicKey 24 | 25 | func init() { 26 | telegramPublicKey.N, _ = new(big.Int).SetString(telegramPublicKey_N, 10) 27 | telegramPublicKey.E = telegramPublicKey_E 28 | } 29 | 30 | func sha1(data []byte) []byte { 31 | r := sha1lib.Sum(data) 32 | return r[:] 33 | } 34 | 35 | func doRSAencrypt(em []byte) []byte { 36 | z := make([]byte, 255) 37 | copy(z, em) 38 | 39 | c := new(big.Int) 40 | c.Exp(new(big.Int).SetBytes(z), big.NewInt(int64(telegramPublicKey.E)), telegramPublicKey.N) 41 | 42 | res := make([]byte, 256) 43 | copy(res, c.Bytes()) 44 | 45 | return res 46 | } 47 | 48 | func splitPQ(pq *big.Int) (p1, p2 *big.Int) { 49 | value_0 := big.NewInt(0) 50 | value_1 := big.NewInt(1) 51 | value_15 := big.NewInt(15) 52 | value_17 := big.NewInt(17) 53 | rndmax := big.NewInt(0).SetBit(big.NewInt(0), 64, 1) 54 | 55 | what := big.NewInt(0).Set(pq) 56 | rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 57 | g := big.NewInt(0) 58 | i := 0 59 | for !(g.Cmp(value_1) == 1 && g.Cmp(what) == -1) { 60 | q := big.NewInt(0).Rand(rnd, rndmax) 61 | q = q.And(q, value_15) 62 | q = q.Add(q, value_17) 63 | q = q.Mod(q, what) 64 | 65 | x := big.NewInt(0).Rand(rnd, rndmax) 66 | whatnext := big.NewInt(0).Sub(what, value_1) 67 | x = x.Mod(x, whatnext) 68 | x = x.Add(x, value_1) 69 | 70 | y := big.NewInt(0).Set(x) 71 | lim := 1 << (uint(i) + 18) 72 | j := 1 73 | flag := true 74 | 75 | for j < lim && flag { 76 | a := big.NewInt(0).Set(x) 77 | b := big.NewInt(0).Set(x) 78 | c := big.NewInt(0).Set(q) 79 | 80 | for b.Cmp(value_0) == 1 { 81 | b2 := big.NewInt(0) 82 | if b2.And(b, value_1).Cmp(value_0) == 1 { 83 | c.Add(c, a) 84 | if c.Cmp(what) >= 0 { 85 | c.Sub(c, what) 86 | } 87 | } 88 | a.Add(a, a) 89 | if a.Cmp(what) >= 0 { 90 | a.Sub(a, what) 91 | } 92 | b.Rsh(b, 1) 93 | } 94 | x.Set(c) 95 | 96 | z := big.NewInt(0) 97 | if x.Cmp(y) == -1 { 98 | z.Add(what, x) 99 | z.Sub(z, y) 100 | } else { 101 | z.Sub(x, y) 102 | } 103 | g.GCD(nil, nil, z, what) 104 | 105 | if (j & (j - 1)) == 0 { 106 | y.Set(x) 107 | } 108 | j = j + 1 109 | 110 | if g.Cmp(value_1) != 0 { 111 | flag = false 112 | } 113 | } 114 | i = i + 1 115 | } 116 | 117 | p1 = big.NewInt(0).Set(g) 118 | p2 = big.NewInt(0).Div(what, g) 119 | 120 | if p1.Cmp(p2) == 1 { 121 | p1, p2 = p2, p1 122 | } 123 | 124 | return 125 | } 126 | 127 | func makeGAB(g int32, g_a, dh_prime *big.Int) (b, g_b, g_ab *big.Int) { 128 | rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 129 | rndmax := big.NewInt(0).SetBit(big.NewInt(0), 2048, 1) 130 | b = big.NewInt(0).Rand(rnd, rndmax) 131 | g_b = big.NewInt(0).Exp(big.NewInt(int64(g)), b, dh_prime) 132 | g_ab = big.NewInt(0).Exp(g_a, b, dh_prime) 133 | 134 | return 135 | } 136 | 137 | func generateAES(msg_key, auth_key []byte, decode bool) ([]byte, []byte) { 138 | var x int 139 | if decode { 140 | x = 8 141 | } else { 142 | x = 0 143 | } 144 | aes_key := make([]byte, 0, 32) 145 | aes_iv := make([]byte, 0, 32) 146 | t_a := make([]byte, 0, 48) 147 | t_b := make([]byte, 0, 48) 148 | t_c := make([]byte, 0, 48) 149 | t_d := make([]byte, 0, 48) 150 | 151 | t_a = append(t_a, msg_key...) 152 | t_a = append(t_a, auth_key[x:x+32]...) 153 | 154 | t_b = append(t_b, auth_key[32+x:32+x+16]...) 155 | t_b = append(t_b, msg_key...) 156 | t_b = append(t_b, auth_key[48+x:48+x+16]...) 157 | 158 | t_c = append(t_c, auth_key[64+x:64+x+32]...) 159 | t_c = append(t_c, msg_key...) 160 | 161 | t_d = append(t_d, msg_key...) 162 | t_d = append(t_d, auth_key[96+x:96+x+32]...) 163 | 164 | sha1_a := sha1(t_a) 165 | sha1_b := sha1(t_b) 166 | sha1_c := sha1(t_c) 167 | sha1_d := sha1(t_d) 168 | 169 | aes_key = append(aes_key, sha1_a[0:8]...) 170 | aes_key = append(aes_key, sha1_b[8:8+12]...) 171 | aes_key = append(aes_key, sha1_c[4:4+12]...) 172 | 173 | aes_iv = append(aes_iv, sha1_a[8:8+12]...) 174 | aes_iv = append(aes_iv, sha1_b[0:8]...) 175 | aes_iv = append(aes_iv, sha1_c[16:16+4]...) 176 | aes_iv = append(aes_iv, sha1_d[0:8]...) 177 | 178 | return aes_key, aes_iv 179 | } 180 | 181 | func doAES256IGEencrypt(data, key, iv []byte) ([]byte, error) { 182 | block, err := aes.NewCipher(key) 183 | if err != nil { 184 | return nil, err 185 | } 186 | if len(data) < aes.BlockSize { 187 | return nil, errors.New("AES256IGE: Data too small to encrypt") 188 | } 189 | if len(data)%aes.BlockSize != 0 { 190 | return nil, errors.New("AES256IGE: Data not divisible by block Size") 191 | } 192 | 193 | t := make([]byte, aes.BlockSize) 194 | x := make([]byte, aes.BlockSize) 195 | y := make([]byte, aes.BlockSize) 196 | copy(x, iv[:aes.BlockSize]) 197 | copy(y, iv[aes.BlockSize:]) 198 | encrypted := make([]byte, len(data)) 199 | 200 | i := 0 201 | for i < len(data) { 202 | xor(x, data[i:i+aes.BlockSize]) 203 | block.Encrypt(t, x) 204 | xor(t, y) 205 | x, y = t, data[i:i+aes.BlockSize] 206 | copy(encrypted[i:], t) 207 | i += aes.BlockSize 208 | } 209 | 210 | return encrypted, nil 211 | } 212 | 213 | func doAES256IGEdecrypt(data, key, iv []byte) ([]byte, error) { 214 | block, err := aes.NewCipher(key) 215 | if err != nil { 216 | return nil, err 217 | } 218 | if len(data) < aes.BlockSize { 219 | return nil, errors.New("AES256IGE: Data too small to decrypt") 220 | } 221 | if len(data)%aes.BlockSize != 0 { 222 | return nil, errors.New("AES256IGE: Data not divisible by block Size") 223 | } 224 | 225 | t := make([]byte, aes.BlockSize) 226 | x := make([]byte, aes.BlockSize) 227 | y := make([]byte, aes.BlockSize) 228 | copy(x, iv[:aes.BlockSize]) 229 | copy(y, iv[aes.BlockSize:]) 230 | decrypted := make([]byte, len(data)) 231 | 232 | i := 0 233 | for i < len(data) { 234 | xor(y, data[i:i+aes.BlockSize]) 235 | block.Decrypt(t, y) 236 | xor(t, x) 237 | y, x = t, data[i:i+aes.BlockSize] 238 | copy(decrypted[i:], t) 239 | i += aes.BlockSize 240 | } 241 | 242 | return decrypted, nil 243 | 244 | } 245 | 246 | func xor(dst, src []byte) { 247 | for i := range dst { 248 | dst[i] = dst[i] ^ src[i] 249 | } 250 | } 251 | 252 | func decryptMtproto(buf []byte, authKey []byte) (data interface{}, msgId int64, seqNo int32, err error) { 253 | dbuf := NewDecodeBuf(buf) 254 | 255 | authKeyHash := dbuf.Bytes(8) 256 | if binary.LittleEndian.Uint64(authKeyHash) == 0 { 257 | msgId = dbuf.Long() 258 | messageLen := dbuf.Int() 259 | if int(messageLen) != dbuf.size-20 { 260 | //TODO: check if 0 works for seqNo 261 | return nil, msgId, 0, fmt.Errorf("Message len: %d (need %d)", messageLen, dbuf.size-20) 262 | } 263 | //m.seqNo = 0 264 | seqNo = 0 265 | 266 | data = dbuf.Object() 267 | if dbuf.err != nil { 268 | return nil, msgId, seqNo, dbuf.err 269 | } 270 | 271 | } else { 272 | msgKey := dbuf.Bytes(16) 273 | encryptedData := dbuf.Bytes(dbuf.size - 24) 274 | //aesKey, aesIV := generateAES(msgKey, m.authKey, true) 275 | aesKey, aesIV := generateAES(msgKey, authKey, true) 276 | x, err := doAES256IGEdecrypt(encryptedData, aesKey, aesIV) 277 | if err != nil { 278 | //TODO: check if 0 works for msgId and seqNo 279 | return nil, 0, 0, err 280 | } 281 | dbuf = NewDecodeBuf(x) 282 | _ = dbuf.Long() // salt 283 | _ = dbuf.Long() // session_id 284 | //m.msgId = dbuf.Long() 285 | //m.seqNo = dbuf.Int() 286 | msgId = dbuf.Long() 287 | seqNo = dbuf.Int() 288 | messageLen := dbuf.Int() 289 | if int(messageLen) > dbuf.size-32 { 290 | return nil, msgId, seqNo, fmt.Errorf("Message len: %d (need less than %d)", messageLen, dbuf.size-32) 291 | } 292 | if !bytes.Equal(sha1(dbuf.buf[0 : 32+messageLen])[4:20], msgKey) { 293 | return nil, msgId, seqNo, errors.New("Wrong msg_key") 294 | } 295 | 296 | data = dbuf.Object() 297 | if dbuf.err != nil { 298 | return nil, msgId, seqNo, dbuf.err 299 | } 300 | 301 | } 302 | mod := msgId & 3 303 | if mod != 1 && mod != 3 { 304 | return nil, msgId, seqNo, fmt.Errorf("Wrong bits of message_id: %d", mod) 305 | } 306 | 307 | return data, msgId, seqNo, nil 308 | } 309 | -------------------------------------------------------------------------------- /dump.go: -------------------------------------------------------------------------------- 1 | package mtproto 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "github.com/cjongseok/slog" 7 | "io" 8 | "os" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | type dumpCallback struct { 14 | out chan interface{} 15 | } 16 | 17 | func (cb dumpCallback) OnUpdate(u Update) { 18 | cb.out <- u 19 | } 20 | 21 | type Dump struct { 22 | updatesState *PredUpdatesState 23 | updateCallback dumpCallback 24 | readWaitGroup sync.WaitGroup 25 | readInterrupter chan interface{} 26 | c *Credentials 27 | //authKey []byte 28 | //authKeyHash []byte 29 | //serverSalt []byte 30 | reader io.Reader 31 | } 32 | 33 | func NewDump(authFileName, dumpFilename string, out chan interface{}) (*Dump, error) { 34 | mdump := new(Dump) 35 | authf, err := os.OpenFile(authFileName, os.O_RDONLY, 0666) 36 | if err != nil { 37 | return nil, err 38 | } 39 | dumpf, err := os.OpenFile(dumpFilename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 40 | if err != nil { 41 | return nil, err 42 | } 43 | mdump.updatesState = &PredUpdatesState{} 44 | mdump.updateCallback = dumpCallback{out} 45 | mdump.readWaitGroup = sync.WaitGroup{} 46 | mdump.readInterrupter = make(chan interface{}) 47 | 48 | //mdump.readSessionFile(authf) 49 | mdump.c, err = NewCredentialsFromFile(authf) 50 | if err != nil { 51 | return nil, err 52 | } 53 | reader, err := slog.DumpReader(dumpf) 54 | if err != nil { 55 | return nil, err 56 | } 57 | mdump.reader = reader 58 | return mdump, nil 59 | } 60 | 61 | func (md *Dump) Play() { 62 | md.readWaitGroup.Add(1) 63 | go md.readRoutine() 64 | } 65 | 66 | func (d *Dump) Wait() { 67 | d.readWaitGroup.Wait() 68 | } 69 | 70 | func (md *Dump) read() (interface{}, error) { 71 | var err error 72 | var n int 73 | var size int 74 | var data interface{} 75 | 76 | // Read packet size 77 | b := make([]byte, 1) 78 | n, err = md.reader.Read(b) // Wait for an incoming byte 79 | if err != nil { 80 | return nil, err 81 | } 82 | slog.Record(b) 83 | 84 | if b[0] < 127 { 85 | size = int(b[0]) << 2 86 | } else { 87 | b := make([]byte, 3) 88 | n, err = md.reader.Read(b) 89 | slog.Record(b) 90 | if err != nil { 91 | return nil, err 92 | } 93 | size = (int(b[0]) | int(b[1])<<8 | int(b[2])<<16) << 2 94 | } 95 | 96 | // Read packet 97 | left := size 98 | buf := make([]byte, size) 99 | for left > 0 { 100 | n, err = md.reader.Read(buf[size-left:]) 101 | if err != nil { 102 | return nil, err 103 | } 104 | left -= n 105 | } 106 | slog.Record(buf) 107 | 108 | if size == 4 { 109 | return nil, fmt.Errorf("Server response error: %d", int32(binary.LittleEndian.Uint32(buf))) 110 | } 111 | 112 | // decrypt incoming packet 113 | data, _, _, err = decryptMtproto(buf, md.c.AuthKey) 114 | if err != nil { 115 | return nil, err 116 | } 117 | return data, nil 118 | 119 | } 120 | 121 | func (md *Dump) process( /*msgId int64, seqNo int32, */ data interface{}) interface{} { 122 | switch data := data.(type) { 123 | case TL_msg_container: 124 | //data := data.(TL_msg_container).Items 125 | for _, v := range data.Items { 126 | md.process(v.Data) 127 | } 128 | 129 | case TL_bad_server_salt: 130 | case TL_new_session_created: 131 | case TL_ping: 132 | case TL_pong: 133 | case TL_msgs_ack: 134 | case TL_rpc_result: 135 | md.process(data.Obj) 136 | case *PredUpdatesState: 137 | md.updatesState.Pts = data.Pts 138 | md.updatesState.Qts = data.Qts 139 | md.updatesState.Date = data.Date 140 | md.updatesState.Seq = data.Seq 141 | return data 142 | 143 | // Date updates 144 | case *PredUpdates: 145 | md.updatesState.Date = data.Date 146 | md.updatesState.Seq = data.Seq 147 | md.updateCallback.OnUpdate(data) 148 | return data 149 | case *PredUpdateShort: 150 | md.updatesState.Date = data.Date 151 | md.updateCallback.OnUpdate(data) 152 | return data 153 | 154 | // Pts updates 155 | case *PredUpdateNewMessage: 156 | md.updatesState.Pts = data.Pts 157 | md.updateCallback.OnUpdate(data) 158 | return data 159 | case *PredUpdateReadMessagesContents: 160 | md.updatesState.Pts = data.Pts 161 | md.updateCallback.OnUpdate(data) 162 | return data 163 | case *PredUpdateDeleteMessages: 164 | //data := data.(TL_updateDeleteMessages) 165 | md.updatesState.Pts = data.Pts 166 | md.updateCallback.OnUpdate(data) 167 | return data 168 | 169 | // Pts and Date updates 170 | case *PredUpdateShortMessage: 171 | md.updatesState.Pts = data.Pts 172 | md.updatesState.Date = data.Date 173 | md.updateCallback.OnUpdate(data) 174 | return data 175 | case *PredUpdateShortChatMessage: 176 | md.updatesState.Pts = data.Pts 177 | md.updatesState.Date = data.Date 178 | md.updateCallback.OnUpdate(data) 179 | return data 180 | case *PredUpdateShortSentMessage: 181 | md.updatesState.Pts = data.Pts 182 | md.updatesState.Date = data.Date 183 | md.updateCallback.OnUpdate(data) 184 | return data 185 | 186 | // Qts updates 187 | case *PredUpdateNewEncryptedMessage: 188 | md.updatesState.Qts = data.Qts 189 | md.updateCallback.OnUpdate(data) 190 | return data 191 | 192 | // Channel updates 193 | case *PredUpdateChannel: 194 | md.updateCallback.OnUpdate(data) 195 | return data 196 | case *PredUpdateChannelMessageViews: 197 | md.updateCallback.OnUpdate(data) 198 | return data 199 | case *PredUpdateChannelTooLong: 200 | md.updatesState.Pts = data.Pts 201 | md.updateCallback.OnUpdate(data) 202 | return data 203 | case *PredUpdateReadChannelInbox: 204 | md.updateCallback.OnUpdate(data) 205 | return data 206 | case *PredUpdateReadChannelOutbox: 207 | md.updateCallback.OnUpdate(data) 208 | return data 209 | case *PredUpdateNewChannelMessage: 210 | md.updatesState.Pts = data.Pts 211 | md.updateCallback.OnUpdate(data) 212 | return data 213 | 214 | default: 215 | return data 216 | } 217 | 218 | return nil 219 | } 220 | 221 | func (md *Dump) readRoutine() { 222 | //slog.Logln(md.readRoutine, "read: start") 223 | defer md.readWaitGroup.Done() 224 | 225 | innerRoutineWG := sync.WaitGroup{} 226 | 227 | ch := make(chan interface{}, 1) 228 | innerRoutineWG.Add(1) 229 | go func(ch chan<- interface{}) { 230 | defer innerRoutineWG.Done() 231 | 232 | for { 233 | data, err := md.read() 234 | //slog.Logf(md.readRoutine, "read: type: %v, data: %v, err: %v\n", reflect.TypeOf(data), data, err) 235 | if err == io.EOF { 236 | // Connection closed by server, trying to reconnect 237 | //slog.Logln(md.readRoutine, "read: lost connection (captured EOF). reconnect") 238 | //TODO: figure out the end of dump file, and stop the loop 239 | close(ch) 240 | return 241 | } else if err != nil { 242 | if strings.Contains(err.Error(), "use of closed network connection") { 243 | //slog.Logf(md.readRoutine, "read: TCP connection closed (%s)\n", err) 244 | } else if strings.Contains(err.Error(), "connection reset by peer") { 245 | //slog.Logf(md.readRoutine, "read: lost connection (%s). reconnect\n", err) 246 | } else { 247 | //slog.Logf(md.readRoutine, "read: unknown error, %s. reconnect\n", err) 248 | } 249 | } else { 250 | ch <- data 251 | } 252 | } 253 | }(ch) 254 | 255 | for { 256 | // Run async wait for data from server 257 | 258 | select { 259 | case <-md.readInterrupter: 260 | //slog.Logln(md.readRoutine, "read: wait for inner routine ...") 261 | innerRoutineWG.Wait() 262 | close(ch) 263 | //slog.Logln(md.readRoutine, "read: stop") 264 | return 265 | case data := <-ch: 266 | if data == nil { 267 | //slog.Logln(md.readRoutine, "data is nil") 268 | md.updateCallback.OnUpdate(nil) 269 | return 270 | } 271 | md.process(data) 272 | } 273 | } 274 | } 275 | 276 | // Implements interface error 277 | //func (md *Dump) readSessionFile(f *os.File) error { 278 | // // Decode session file 279 | // b := make([]byte, 1024*4) 280 | // n, err := f.ReadAt(b, 0) 281 | // if n <= 0 || (err != nil && err.Error() != "EOF") { 282 | // return errors.New("New session") 283 | // } 284 | // 285 | // d := NewDecodeBuf(b) 286 | // md.authKey = d.StringBytes() 287 | // md.authKeyHash = d.StringBytes() 288 | // md.serverSalt = d.StringBytes() 289 | // return nil 290 | //} 291 | -------------------------------------------------------------------------------- /examples/simpleshell/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "regexp" 7 | "bufio" 8 | "github.com/cjongseok/mtproto" 9 | "github.com/cjongseok/slog" 10 | "strconv" 11 | "strings" 12 | "time" 13 | "math/rand" 14 | "golang.org/x/net/context" 15 | "net" 16 | ) 17 | 18 | const ( 19 | defaultNewKeyFile = "credentials.json" 20 | 21 | appVersion = "0.0.1" 22 | deviceModel = "" 23 | systemVersion = "" 24 | language = "" 25 | //telegramAddress = "149.154.167.50:443" 26 | ) 27 | 28 | func usage() { 29 | fmt.Println(`Usage: simpleshell 30 | simpleshell 31 | 32 | Params: 33 | APIID means Telegram API id. If you do not have it yet, go https://my.telegram.org/apps 34 | APIHASH means hashcode of . It is published together with API id. 35 | PHONE means phone number in international format w/o hyphen. e.g., +15417543010 36 | IP means Telegram server IP address in IPv4 or IPv6. You can find a vaild address in 37 | your https://my.telegram.org/apps page. 38 | PORT means Telegram server port number. You can find a vaild address in your 39 | https://my.telegram.org/apps page. 40 | JSON_FILE means MTProto credentials file whose format is JSON. Once you sign-in with full 41 | params such as API ID, API hash, phone number, IP, and port, SimpleShell generates 42 | 'credentials.json' file. With this file, you can simply sign-in to Telegram without 43 | 'Enter Code' process. 44 | `) 45 | } 46 | 47 | type subscriber struct { 48 | mconn *mtproto.Conn 49 | } 50 | 51 | func newSubscriber(mconn *mtproto.Conn) *subscriber { 52 | s := new(subscriber) 53 | s.mconn = mconn 54 | return s 55 | } 56 | 57 | func isServerEndpoint(addr string) (err error) { 58 | var tcpAddr *net.TCPAddr 59 | tcpAddr, err = net.ResolveTCPAddr("tcp", addr) 60 | if err == nil && tcpAddr.IP.To4() == nil && tcpAddr.IP.To16() == nil { 61 | err = fmt.Errorf("invalid ip address") 62 | } 63 | return 64 | } 65 | 66 | func parseArgs() (apiId int32, apiHash, phoneNumber, ip string, port int, credentials string, err error){ 67 | switch len(os.Args) { 68 | case 2: 69 | if _, err = os.Stat(os.Args[1]); err == nil { 70 | credentials = os.Args[1] 71 | } 72 | return 73 | case 6: 74 | default: 75 | err = fmt.Errorf("invalid number of arguments") 76 | return 77 | } 78 | 79 | var apiId64 int64 80 | apiId64, err = strconv.ParseInt(os.Args[1], 10, 32) 81 | if err != nil { 82 | return 83 | } 84 | apiId = int32(apiId64) 85 | apiHash = os.Args[2] 86 | phoneNumber = os.Args[3] 87 | 88 | var matched bool 89 | //matched, err = regexp.MatchString("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5}$", os.Args[4]) 90 | matched, err = regexp.MatchString("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$", os.Args[4]) 91 | if err != nil { 92 | err = fmt.Errorf("invalid ip address; %v", err) 93 | return 94 | } 95 | if !matched { 96 | err = fmt.Errorf("invalid ip address") 97 | return 98 | } 99 | ip = os.Args[4] 100 | 101 | matched, err = regexp.MatchString("^\\d{1,5}$", os.Args[5]) 102 | if err != nil { 103 | err = fmt.Errorf("invalid port; %v", err) 104 | return 105 | } 106 | if !matched { 107 | err = fmt.Errorf("invalid port") 108 | return 109 | } 110 | port, err = strconv.Atoi(os.Args[5]) 111 | if err != nil { 112 | err = fmt.Errorf("invalid port; %v", err) 113 | } 114 | return 115 | } 116 | 117 | func (s *subscriber) OnUpdate(u mtproto.Update) { 118 | fmt.Printf("update(%s):\n%s\n", time.Now(), slog.StringifyIndent(u, " ")) 119 | } 120 | 121 | func handleError(err error) { 122 | if err != nil { 123 | fmt.Println(err) 124 | os.Exit(1) 125 | } 126 | } 127 | 128 | func help() { 129 | fmt.Println("Commands:") 130 | fmt.Printf("\tdialogs\n\tsend2c \n\thelp\n\texit\n") 131 | } 132 | 133 | func main() { 134 | // set up logging 135 | logf, err := os.OpenFile("ss.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 136 | if err != nil { 137 | fmt.Printf("error opening file: %v", err) 138 | } 139 | defer logf.Close() 140 | slog.SetLogOutput(logf) 141 | 142 | // parse args 143 | apiID, apiHash, phone, ip, port, credentials, err := parseArgs() 144 | if err != nil { 145 | fmt.Fprintln(os.Stderr, err) 146 | usage() 147 | handleError(err) 148 | } 149 | 150 | config, err := mtproto.NewConfiguration(appVersion, deviceModel, systemVersion, language, 0, 0, credentials) 151 | handleError(err) 152 | 153 | // Connect by phone number 154 | var manager *mtproto.Manager 155 | var mconn *mtproto.Conn 156 | if config.KeyPath == "" { 157 | config.KeyPath = defaultNewKeyFile 158 | 159 | // request to send authentication code to the phone 160 | var sentCode *mtproto.TypeAuthSentCode 161 | manager, err = mtproto.NewManager(config) 162 | handleError(err) 163 | mconn, sentCode, err = manager.NewAuthentication(phone, apiID, apiHash, ip, port) 164 | handleError(err) 165 | 166 | // sign-in with the code from the user input 167 | var code string 168 | fmt.Printf("Enter Code: ") 169 | fmt.Scanf("%s", &code) 170 | _, err = mconn.SignIn(phone, code, sentCode.GetValue().PhoneCodeHash) 171 | handleError(err) 172 | } else { 173 | manager, err = mtproto.NewManager(config) 174 | handleError(err) 175 | mconn, err = manager.LoadAuthentication() 176 | handleError(err) 177 | } 178 | 179 | mconn.AddUpdateCallback(newSubscriber(mconn)) 180 | 181 | caller := mtproto.RPCaller{mconn} 182 | for { 183 | // ready for the user command 184 | var cmd string 185 | fmt.Printf("$ ") 186 | reader := bufio.NewReader(os.Stdin) 187 | cmd, err := reader.ReadString('\n') 188 | if err != nil { 189 | help() 190 | continue 191 | } 192 | cmd = strings.Trim(cmd, "\n") 193 | args := strings.Split(cmd, " ") 194 | 195 | // parse & execute the command 196 | switch args[0] { 197 | case "dialogs": // $ dialogs 198 | if len(args) != 1 { 199 | help() 200 | break 201 | } 202 | emptyPeer := &mtproto.TypeInputPeer{Value: &mtproto.TypeInputPeer_InputPeerEmpty{&mtproto.PredInputPeerEmpty{}}} 203 | resp, err := caller.MessagesGetDialogs(context.Background(), &mtproto.ReqMessagesGetDialogs{ 204 | OffsetDate: 0, OffsetId: 0, OffsetPeer: emptyPeer, Limit: 1, 205 | }) 206 | handleError(err) 207 | switch dialogs := resp.GetValue().(type) { 208 | case *mtproto.TypeMessagesDialogs_MessagesDialogs: 209 | fmt.Println(slog.StringifyIndent(dialogs.MessagesDialogs, " ")) 210 | case *mtproto.TypeMessagesDialogs_MessagesDialogsSlice: 211 | fmt.Println(slog.StringifyIndent(dialogs.MessagesDialogsSlice, " ")) 212 | } 213 | case "chans": 214 | if len(args) != 1 { 215 | help() 216 | break 217 | } 218 | resp, err := caller.MessagesGetAllChats(context.Background(), &mtproto.ReqMessagesGetAllChats{}) 219 | handleError(err) 220 | switch chats := resp.Value.(type) { 221 | case *mtproto.TypeMessagesChats_MessagesChats : 222 | fmt.Println(slog.StringifyIndent(chats.MessagesChats, " ")) 223 | case *mtproto.TypeMessagesChats_MessagesChatsSlice: 224 | fmt.Println(slog.StringifyIndent(chats.MessagesChatsSlice, " ")) 225 | } 226 | 227 | case "send2c": // send2c 228 | if len(args) != 4 { 229 | help() 230 | return 231 | } 232 | chanId, err := strconv.ParseInt(args[1], 0, 32) 233 | handleError(err) 234 | chanHash, err := strconv.ParseInt(args[2], 0, 64) 235 | handleError(err) 236 | peer := &mtproto.TypeInputPeer{Value: &mtproto.TypeInputPeer_InputPeerChannel{ 237 | &mtproto.PredInputPeerChannel{ 238 | ChannelId: int32(chanId), AccessHash: int64(chanHash), 239 | }}} 240 | resp, err := caller.MessagesSendMessage(context.Background(), &mtproto.ReqMessagesSendMessage{ 241 | Peer: peer, 242 | Message: args[3], 243 | RandomId: rand.Int63(), 244 | }) 245 | handleError(err) 246 | fmt.Println("send response:", slog.StringifyIndent(resp, " ")) 247 | case "contacts": // $ contacts 248 | if len(args) != 1 { 249 | help() 250 | return 251 | } 252 | resp, err := caller.ContactsGetContacts(context.Background(), &mtproto.ReqContactsGetContacts{}) 253 | handleError(err) 254 | fmt.Println("contacts response:", slog.StringifyIndent(resp, " " )) 255 | case "help": 256 | help() 257 | case "exit": 258 | if len(args) != 1 { 259 | help() 260 | break 261 | } 262 | return 263 | case "": // new line 264 | default: 265 | fmt.Println("Wrong command") 266 | help() 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /manager.go: -------------------------------------------------------------------------------- 1 | package mtproto 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/cjongseok/slog" 7 | "math/rand" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | const ( 13 | DEBUG_LEVEL_NETWORK = 0x01 14 | DEBUG_LEVEL_NETWORK_DETAILS = 0x02 15 | DEBUG_LEVEL_DECODE = 0x04 16 | DEBUG_LEVEL_DECODE_DETAILS = 0x08 17 | DEBUG_LEVEL_ENCODE_DETAILS = 0x10 18 | ) 19 | 20 | var ( 21 | __debug = 0 22 | ) 23 | 24 | type Manager struct { 25 | managerId int32 26 | appConfig Configuration 27 | conns map[int32]*Conn 28 | sessions map[int64]*Session 29 | stuckSessions map[int64]int32 30 | eventq chan Event 31 | 32 | manageInterrupter chan struct{} 33 | manageWaitGroup sync.WaitGroup 34 | } 35 | 36 | func NewManager(appConfig Configuration) (*Manager, error) { 37 | var err error 38 | 39 | err = appConfig.Check() 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | mm := new(Manager) 45 | rand.Seed(time.Now().UnixNano()) 46 | mm.managerId = rand.Int31() 47 | mm.appConfig = appConfig 48 | //TODO: set proper buf size to channels 49 | mm.conns = make(map[int32]*Conn) 50 | mm.sessions = make(map[int64]*Session) 51 | mm.stuckSessions = make(map[int64]int32) 52 | mm.eventq = make(chan Event) 53 | mm.manageInterrupter = make(chan struct{}) 54 | mm.manageWaitGroup = sync.WaitGroup{} 55 | 56 | go mm.manageRoutine() 57 | 58 | return mm, nil 59 | } 60 | 61 | func (mm *Manager) Finish() { 62 | // close all connections 63 | for id, _ := range mm.conns { 64 | mm.eventq <- closeConnection{id, nil} 65 | } 66 | 67 | // Send stop signal to manage routine 68 | close(mm.manageInterrupter) 69 | 70 | // Wait for event routines + manage routine 71 | mm.manageWaitGroup.Wait() 72 | } 73 | 74 | func (mm *Manager) LoadAuthentication() (*Conn, error) { 75 | // req connect 76 | respCh := make(chan sessionResponse, 1) 77 | mm.eventq <- loadsession{0, noRetry, respCh} 78 | 79 | // Wait for connection built 80 | resp := <-respCh 81 | if resp.err != nil { 82 | return nil, resp.err 83 | } 84 | 85 | // Check user authentication by user info 86 | mconn := mm.conns[resp.connId] 87 | 88 | // Request full user 89 | inputUser := &TypeInputUser{Value: &TypeInputUser_InputUserSelf{&PredInputUserSelf{}}} 90 | var userFull *TypeUserFull 91 | x := <-mconn.InvokeNonBlocked(&ReqUsersGetFullUser{Id: inputUser}) 92 | if x.err != nil { 93 | return nil, x.err 94 | } 95 | 96 | switch casted := x.data.(type) { 97 | case *PredUserFull: 98 | userFull = &TypeUserFull{Value: casted} 99 | default: 100 | return nil, fmt.Errorf("no full user: %T: %v", x, x) 101 | } 102 | 103 | // get session 104 | var session Session 105 | res := <-mconn.Session() 106 | switch res.(type) { 107 | case Session: 108 | session = res.(Session) 109 | case error: 110 | return mconn, res.(error) 111 | } 112 | 113 | // Already authenticated 114 | typeUser := userFull.GetValue().GetUser() 115 | if typeUser.GetUser() != nil { 116 | user := typeUser.GetUser() 117 | session.user = user 118 | slog.Logln(mm, "Auth as ", user) 119 | } else if typeUser.GetUserEmpty() != nil { 120 | session.user = &PredUser{} 121 | slog.Logln(mm, "Authenticated, but failed to get user") 122 | } 123 | return mm.conns[resp.connId], nil 124 | } 125 | 126 | func (mm *Manager) NewAuthentication(phone string, apiID int32, apiHash, ip string, port int) (*Conn, *TypeAuthSentCode, error) { 127 | // req connect 128 | respCh := make(chan sessionResponse, 1) 129 | mm.eventq <- newsession{0, phone, apiID, apiHash, ip, port, respCh} 130 | 131 | // Wait for connection 132 | resp := <-respCh 133 | if resp.err != nil { 134 | return nil, nil, resp.err 135 | } 136 | 137 | // sendAuthCode 138 | mconn := mm.conns[resp.connId] 139 | for { 140 | // get session 141 | var session Session 142 | res := <-mconn.Session() 143 | switch res.(type) { 144 | case Session: 145 | session = res.(Session) 146 | case error: 147 | return nil, nil, res.(error) 148 | } 149 | 150 | // request to send code 151 | data, err := mconn.InvokeBlocked(&ReqAuthSendCode{ 152 | Flags: 0x00000001, 153 | PhoneNumber: phone, 154 | CurrentNumber: &TypeBool{Value: &TypeBool_BoolTrue{&PredBoolTrue{}}}, 155 | ApiId: session.c.ApiID, 156 | ApiHash: session.c.ApiHash, 157 | }) 158 | switch x := data.(type) { 159 | case *PredAuthSentCode: 160 | return mconn, &TypeAuthSentCode{Value: x}, nil 161 | } 162 | 163 | // retry the send code request to another server 164 | if err != nil { 165 | rpcError, ok := err.(TL_rpc_error) 166 | if !ok { 167 | return nil, nil, err 168 | } 169 | if rpcError.error_code != errorSeeOther { 170 | return nil, nil, err 171 | } 172 | 173 | var newdc int32 174 | n, _ := fmt.Sscanf(rpcError.error_message, "PHONE_MIGRATE_%d", &newdc) 175 | if n != 1 { 176 | n, _ = fmt.Sscanf(rpcError.error_message, "NETWORK_MIGRATE_%d", &newdc) 177 | } 178 | if n != 1 { 179 | return nil, nil, err 180 | } else { 181 | // get session 182 | //var session Session 183 | res := <-mconn.Session() 184 | switch res.(type) { 185 | case Session: 186 | session = res.(Session) 187 | case error: 188 | return nil, nil, res.(error) 189 | } 190 | 191 | // reconnect to the new datacenter 192 | respch := make(chan sessionResponse, 1) 193 | ipVersion := ipv4 194 | if isIPv6(session.c.IP) { 195 | ipVersion = ipv6 196 | } 197 | dcOption, err := session.apiDcOption(ipVersion, newdc) 198 | if err != nil { 199 | return nil, nil, err 200 | } 201 | slog.Logln(mm, "migrate session to", dcOption) 202 | 203 | //TODO: Check if renewSession event works with mconn.notify() 204 | mconn.notify(renewSession{ 205 | session.sessionID, 206 | session.c.Phone, 207 | session.c.ApiID, 208 | session.c.ApiHash, 209 | dcOption.IpAddress, 210 | int(dcOption.Port), 211 | respch, 212 | }) 213 | 214 | // Wait for binding with new session 215 | resp := <-respch 216 | if resp.err != nil { 217 | return nil, nil, resp.err 218 | } 219 | } 220 | } 221 | } 222 | } 223 | 224 | func (mm *Manager) manageRoutine() { 225 | slog.Logln(mm, "start") 226 | mm.manageWaitGroup.Add(1) 227 | defer mm.manageWaitGroup.Done() 228 | 229 | for { 230 | select { 231 | case <-mm.manageInterrupter: 232 | // Default interrupt is STOP 233 | slog.Logln(mm, "stop") 234 | return 235 | 236 | case e := <-mm.eventq: 237 | // Delegate event handlings to go routines 238 | switch e.(type) { 239 | // Session Event Handlers 240 | // In normal case, three resp events, 241 | // SessionEstablished, ConnectionOpened, sessionBound, 242 | // are generated and propagated. 243 | case newsession: 244 | go func() { 245 | mm.manageWaitGroup.Add(1) 246 | defer mm.manageWaitGroup.Done() 247 | e := e.(newsession) 248 | slog.Logln(mm, "newsession to ", fmt.Sprintf("%s:%d", e.ip, e.port)) 249 | session, err := newSession(e.phone, e.apiid, e.apihash, e.ip, e.port, 250 | mm.appConfig /*mm.queueSend,*/, mm.eventq) 251 | var resp sessionResponse 252 | if err != nil { 253 | slog.Logln(mm, "connect failure:", err) 254 | //TODO: need to handle nil resp channel? 255 | //e.resp <- sessionResponse{0, nil, err} 256 | resp = sessionResponse{0, nil, err} 257 | } else { 258 | // Bind the session with mconn and mmanager 259 | mm.sessions[session.sessionID] = session // Immediate registration 260 | var mconn *Conn 261 | if e.connId != 0 { 262 | mconn = mm.conns[e.connId] 263 | } else { 264 | // Create new connection, if not exist 265 | mconn = newConnection(mm.eventq) 266 | if err != nil { 267 | if e.resp != nil { 268 | e.resp <- sessionResponse{0, nil, err} 269 | } 270 | return 271 | } 272 | mm.conns[mconn.connID] = mconn // Immediate registration 273 | } 274 | mconn.bind(session) 275 | //TODO: need to handle nil resp channel? 276 | resp = sessionResponse{mconn.connID, session, nil} 277 | } 278 | if e.resp != nil { 279 | e.resp <- resp 280 | } 281 | }() 282 | 283 | // In normal case, three resp events, 284 | // SessionEstablished, ConnectionOpened, sessionBound, 285 | // are generated and propagated. 286 | case loadsession: 287 | go func() { 288 | mm.manageWaitGroup.Add(1) 289 | defer mm.manageWaitGroup.Done() 290 | e := e.(loadsession) 291 | slog.Logln(mm, "loadsession of conn ", e.connId) 292 | session, err := loadSession(mm.appConfig /*mm.queueSend,*/, mm.eventq) 293 | var resp sessionResponse 294 | if err != nil { 295 | slog.Logln(mm, "connect failure:", err) 296 | if session != nil { 297 | switch err.(type) { 298 | case handshakingFailure: 299 | mm.stuckSessions[session.sessionID] = e.connId // register the stuck session 300 | // usually TCP resets causes stuck sessions, and the sessions are refreshed in the cases. 301 | // Sometimes TCP t/o makes stuck sessions, and the sessions are refreshed as well, 302 | // however it takes too long to be identified. 303 | // So trigger the refresh session by closing the TCP connection 304 | //mm.eventq <- refreshSession{session.sessionID, session.phonenumber, nil} 305 | session.close() 306 | } 307 | } 308 | //TODO: separate the handshaking error into two cases and trigger refreshSession on tcp dialing 309 | // failure 310 | resp = sessionResponse{0, session, err} 311 | if e.policy == untilSuccess { 312 | mm.eventq <- e 313 | } 314 | } else { 315 | // Bind the session with mconn and mmanager 316 | mm.sessions[session.sessionID] = session // Immediate registration 317 | var mconn *Conn 318 | if e.connId != 0 { 319 | mconn = mm.conns[e.connId] 320 | } else { 321 | mconn = newConnection(mm.eventq) 322 | mm.conns[mconn.connID] = mconn // Immediate registration 323 | } 324 | mconn.bind(session) 325 | //TODO: need to handle nil resp channel? 326 | resp = sessionResponse{mconn.connID, session, nil} 327 | } 328 | if e.resp != nil { 329 | e.resp <- resp 330 | } 331 | }() 332 | 333 | case SessionEstablished: 334 | go func() { 335 | mm.manageWaitGroup.Add(1) 336 | defer mm.manageWaitGroup.Done() 337 | e := e.(SessionEstablished) 338 | slog.Logf(mm, "session established %d\n", e.session.sessionID) 339 | }() 340 | 341 | // In normal case, an event, 342 | // SessionDiscarded, 343 | // is generated and propagated. 344 | case discardSession: 345 | go func() { 346 | mm.manageWaitGroup.Add(1) 347 | defer mm.manageWaitGroup.Done() 348 | e := e.(discardSession) 349 | slog.Logln(mm, "discard session ", e.sessionId) 350 | session := mm.sessions[e.sessionId] 351 | session.close() 352 | 353 | // Immediate assignment of discarded session's updates state 354 | // The assignment on handling SessionDiscarded event is sometimes slower than new sessionBound 355 | // event, so that it results in either nil discardedUpdateState or a lot of duplicated updates. 356 | marshaled, err := json.Marshal(session.updatesState) 357 | if err == nil { 358 | slog.Logf(mm, "session is discarded. keep its updates state, (json): %s\n", marshaled) 359 | } else { 360 | slog.Logf(mm, "session is discarded. keep its updates state, %v\n", session.updatesState) 361 | } 362 | if e.connId != 0 { 363 | mconn := mm.conns[e.connId] 364 | mconn.discardedUpdatesState = &PredUpdatesState{} 365 | *mconn.discardedUpdatesState = *session.updatesState 366 | } 367 | if e.resp != nil { 368 | e.resp <- sessionResponse{e.connId, session, nil} 369 | } 370 | }() 371 | 372 | case SessionDiscarded: 373 | go func() { 374 | mm.manageWaitGroup.Add(1) 375 | defer mm.manageWaitGroup.Done() 376 | e := e.(SessionDiscarded) 377 | slog.Logln(mm, "session discarded ", e.discardedSessionId) 378 | delete(mm.sessions, e.discardedSessionId) // Late deregistration 379 | }() 380 | 381 | // In normal case, five events, 382 | // discardSesseion, (SessionDiscarded), newsession, (SessionEstablished, ConnectionOpened, sessionBound), 383 | // are generated and propagated. 384 | case renewSession: 385 | go func() { 386 | mm.manageWaitGroup.Add(1) 387 | defer mm.manageWaitGroup.Done() 388 | e := e.(renewSession) 389 | slog.Logln(mm, "renewSession to ", fmt.Sprintf("%s:%d", e.ip, e.port)) 390 | connId := mm.sessions[e.sessionId].connID 391 | 392 | // Req discardSession 393 | disconnectRespCh := make(chan sessionResponse, 1) 394 | mm.sessions[e.sessionId].notify(discardSession{connId, e.sessionId, disconnectRespCh}) 395 | 396 | // Wait for disconnection 397 | disconnectResp := <-disconnectRespCh 398 | if disconnectResp.err != nil { 399 | slog.Logf(mm, "renewSession failure: cannot discardSession %d. %v\n", e.sessionId, disconnectResp.err) 400 | if e.resp != nil { 401 | e.resp <- sessionResponse{0, nil, fmt.Errorf("cannot discardSession %d. %v", e.sessionId, disconnectResp.err)} 402 | } 403 | return 404 | } 405 | 406 | // Req newsession 407 | slog.Logln(mm, "renewRoutine: req newsession") 408 | connectRespCh := make(chan sessionResponse, 1) 409 | mm.eventq <- newsession{connId, e.phone, e.apiID, e.apiHash, e.ip, e.port, connectRespCh} 410 | connectResp := <-connectRespCh 411 | if connectResp.err != nil { 412 | slog.Logf(mm, "renewSession failure: cannot connect to %s:%d. %v\n", e.ip, e.port, connectResp.err) 413 | if e.resp != nil { 414 | e.resp <- sessionResponse{0, nil, fmt.Errorf("cannot connect to %s:%d. %v", e.ip, e.port, connectResp.err)} 415 | } 416 | return 417 | } 418 | slog.Logln(mm, "renewSession done") 419 | //TODO: need to handle nil resp channel? 420 | if e.resp != nil { 421 | e.resp <- sessionResponse{connectResp.connId, connectResp.session, nil} 422 | } 423 | //TODO: figure out missed updates 424 | }() 425 | 426 | // In normal case, five events, 427 | // discardSesseion, (SessionDiscarded), newsession, (SessionEstablished, ConnectionOpened, sessionBound), 428 | // are generated and propagated. 429 | case refreshSession: 430 | go func() { 431 | mm.manageWaitGroup.Add(1) 432 | defer mm.manageWaitGroup.Done() 433 | e := e.(refreshSession) 434 | slog.Logln(mm, "refreshSession ", e.sessionId) 435 | //TODO: alternate the spin lock 436 | // Wait for session registration and binding for graceful refreshing 437 | var connId int32 438 | spinLock := true 439 | skipDiscardSession := false 440 | if mm.sessions[e.sessionId] != nil { 441 | connId = mm.sessions[e.sessionId].connID 442 | spinLock = false 443 | } 444 | for spinLock { 445 | select { 446 | // sleep timer 447 | case <-time.After(1 * time.Second): 448 | if mm.sessions[e.sessionId] != nil { 449 | // session is registered 450 | if mm.sessions[e.sessionId].connID != 0 { 451 | // session is bound to a connection 452 | spinLock = false 453 | connId = mm.sessions[e.sessionId].connID 454 | slog.Logln(mm, "spinlocked. session(%d) is bound. Release the lock now.", e.sessionId) 455 | } else { 456 | // session is not bound to a connection yet 457 | slog.Logf(mm, "spinlocked. wait for the session(%d) binding.\n", e.sessionId) 458 | } 459 | } else if stuckSessionConnId, ok := mm.stuckSessions[e.sessionId]; ok { 460 | // session is not registered yet, 461 | // even the session would not be registered forever, 462 | // because either invokeWithLayer or updatesGetState does not respond. 463 | spinLock = false 464 | skipDiscardSession = true 465 | connId = stuckSessionConnId 466 | delete(mm.stuckSessions, e.sessionId) 467 | slog.Logf(mm, "spinlocked. Session(%d) is stuck on either invokeWithLayer or "+ 468 | "updatesGetState. Release the lock now and skip discardSession.\n", e.sessionId) 469 | } else { 470 | // session is not registered yet. wait for the registration. 471 | slog.Logf(mm, "spinlocked. Session(%d) is waiting for a response from either "+ 472 | "invokeWithLayer or updatesGetState.\n", e.sessionId) 473 | } 474 | } 475 | } 476 | 477 | if !skipDiscardSession { 478 | // req discardSession 479 | disconnectRespCh := make(chan sessionResponse, 1) 480 | mm.sessions[e.sessionId].notify(discardSession{connId, e.sessionId, disconnectRespCh}) 481 | disconnectResp := <-disconnectRespCh 482 | 483 | // handle disconnect error 484 | if disconnectResp.err != nil { 485 | slog.Logf(mm, "refreshSession failure; discardSession(%d) failure; %v\n", e.sessionId, disconnectResp.err) 486 | refreshResp := sessionResponse{0, nil, disconnectResp.err} 487 | if e.policy == untilSuccess { 488 | slog.Logln(mm, "retry refreshSession") 489 | mm.eventq <- refreshSession{ 490 | e.sessionId, 491 | e.phone, 492 | e.policy, 493 | e.resp, 494 | } 495 | } 496 | if e.resp != nil { 497 | e.resp <- refreshResp 498 | } 499 | return 500 | } 501 | } 502 | 503 | // req loadsession 504 | var refreshResp sessionResponse 505 | slog.Logln(mm, "req loadsession") 506 | connectRespCh := make(chan sessionResponse, 1) 507 | mm.eventq <- loadsession{connId, noRetry, connectRespCh} 508 | connectResp := <-connectRespCh 509 | 510 | // handle load error 511 | if connectResp.err != nil { 512 | slog.Logf(mm, "refreshSession failure; loadSession failure; %v; connID: %d, session: %v\n", 513 | connectResp.err, connectResp.connId, connectResp.session) 514 | refreshResp = sessionResponse{0, nil, connectResp.err} 515 | if e.policy == untilSuccess { 516 | if connectResp.session == nil || connectResp.session.sessionID == 0 { 517 | slog.Logln(mm, "retry loadSession") 518 | mm.eventq <- loadsession{connId, e.policy, e.resp} 519 | } else { 520 | slog.Logln(mm, "retry refreshSession") 521 | mm.eventq <- refreshSession{ 522 | connectResp.session.sessionID, 523 | e.phone, 524 | e.policy, 525 | e.resp, 526 | } 527 | } 528 | } 529 | } else { 530 | refreshResp = connectResp 531 | } 532 | 533 | slog.Logln(mm, "refreshSession is done.") 534 | if e.resp != nil { 535 | e.resp <- refreshResp 536 | } 537 | }() 538 | 539 | // Connection Event Handlers 540 | case ConnectionOpened: 541 | go func() { 542 | mm.manageWaitGroup.Add(1) 543 | defer mm.manageWaitGroup.Done() 544 | e := e.(ConnectionOpened) 545 | slog.Logln(mm, "connectionOpened ", e.mconn.connID) 546 | }() 547 | 548 | case sessionBound: 549 | go func() { 550 | mm.manageWaitGroup.Add(1) 551 | defer mm.manageWaitGroup.Done() 552 | e := e.(sessionBound) 553 | slog.Logf(mm, "sessionBound: session %d is bound to mconn %d\n", e.sessionID, e.mconn.connID) 554 | }() 555 | case sessionUnbound: 556 | go func() { 557 | mm.manageWaitGroup.Add(1) 558 | defer mm.manageWaitGroup.Done() 559 | e := e.(sessionUnbound) 560 | slog.Logf(mm, "sessionUnbound: session %d is unbound from mconn %d\n", e.unboundSessionID, e.mconn.connID) 561 | }() 562 | case closeConnection: 563 | go func() { 564 | mm.manageWaitGroup.Add(1) 565 | defer mm.manageWaitGroup.Done() 566 | e := e.(closeConnection) 567 | slog.Logln(mm, "closeConnection ", e.connId) 568 | 569 | // close, unbound, and de-register session 570 | mconn := mm.conns[e.connId] 571 | 572 | // get session 573 | var session Session 574 | res := <-mconn.Session() 575 | switch res.(type) { 576 | case Session: 577 | session = res.(Session) 578 | case error: 579 | err := res.(error) 580 | if e.resp != nil { 581 | e.resp <- err 582 | } 583 | return 584 | } 585 | 586 | discardSessionRespCh := make(chan sessionResponse, 1) 587 | //mm.eventq <- discardSession{closeE.connID, session.sessionID, discardSessionRespCh} 588 | mconn.notify(discardSession{e.connId, session.sessionID, discardSessionRespCh}) 589 | 590 | // close and deregister connection 591 | discardSessionResp := <-discardSessionRespCh 592 | if discardSessionResp.err == nil { 593 | mconn.close() 594 | if e.resp != nil { 595 | e.resp <- nil 596 | } 597 | return 598 | } 599 | slog.Logln(mm, "closeConnection failure: cannot discard its session ", session.sessionID) 600 | e.resp <- fmt.Errorf("Failed to discard its session %d", session.sessionID) 601 | }() 602 | case connectionClosed: 603 | go func() { 604 | mm.manageWaitGroup.Add(1) 605 | defer mm.manageWaitGroup.Done() 606 | e := e.(connectionClosed) 607 | slog.Logln(mm, "connectionClosed ", e.closedConnId) 608 | delete(mm.conns, e.closedConnId) // Late deregistration 609 | }() 610 | case updateReceived: 611 | default: 612 | } 613 | } 614 | } 615 | } 616 | 617 | func (x *Manager) LogPrefix() string { 618 | return fmt.Sprintf("[MM %d]", x.managerId) 619 | } 620 | -------------------------------------------------------------------------------- /mtprotod/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | ADD main / 3 | ADD cacert.pem /etc/ssl/certs/ 4 | ENTRYPOINT ["/main"] 5 | CMD ["start"] 6 | -------------------------------------------------------------------------------- /mtprotod/README.md: -------------------------------------------------------------------------------- 1 | mtprotod 2 | === 3 | MTProto proxy daemon. 4 | It contains Telegram MTProto implementation in Go. 5 | Its clients can share one Telegram MTProto session, or you can make your own session scheduler. 6 | 7 | Quick Start 8 | --- 9 | You can test if the proxy running using [proxy_test.go](https://github.com/cjongseok/mtproto/blob/master/proxy/proxy_test.go). 10 | ```bash 11 | # start mtprotod at port 11011 12 | docker run \ 13 | -p 11011: 11011 \ 14 | -v /your/mtproto/secrets/directory:/opt \ 15 | cjongseok/mtprotod start \ 16 | --port 11011 \ 17 | --addr \ 18 | --apiid \ 19 | --apihash \ 20 | --phone \ 21 | --secrets /opt/ 22 | 23 | # At mtproto/proxy, let's get dialogs through over the proxy 24 | dep ensure 25 | go test --run TestDialogs 26 | ``` 27 | 28 | Usage 29 | --- 30 | ### Run as a container 31 | mtprotod Docker image is at Docker hub, [cjongseok/mtprotod](https://hub.docker.com/r/cjongseok/mtprotod/). 32 | ```bash 33 | docker run \ 34 | -p : \ 35 | -v /your/mtproto/secrets/directory:/opt \ 36 | cjongseok/mtprotod start \ 37 | --addr \ 38 | --apiid \ 39 | --apihash \ 40 | --phone \ 41 | --port \ 42 | --secrets /opt/ 43 | ``` 44 | ### Build & Run 45 | mtprotod is vendored in [***dep***](https://github.com/golang/dep). 46 | ```bash 47 | # In mtprotod directory 48 | dep ensure 49 | go run main.go start \ 50 | --addr \ 51 | --apiid \ 52 | --apihash \ 53 | --phone \ 54 | --port \ 55 | --secrets /opt/ 56 | ``` 57 | 58 | Clients 59 | --- 60 | * [Go client](https://github.com/cjongseok/mtproto#client-in-go) 61 | * [Python client](https://github.com/cjongseok/mtproto/tree/master/py) 62 | -------------------------------------------------------------------------------- /mtprotod/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # build a linux executable 3 | dep ensure 4 | CGO_ENABLED=0 GOOS=linux go build -ldflags '-s' -a -installsuffix cgo -o main 5 | 6 | # build & push a Docker image 7 | proxyVersion=$(go run main.go --version | awk '{print $3}') 8 | dockerImage=cjongseok/mtprotod:${proxyVersion} 9 | latestImage=cjongseok/mtprotod:latest 10 | docker build -t ${dockerImage} . 11 | docker tag ${dockerImage} ${latestImage} 12 | docker login 13 | docker push ${dockerImage} 14 | docker push ${latestImage} 15 | -------------------------------------------------------------------------------- /mtprotod/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "fmt" 6 | "os" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | // Daemon exit codes 11 | const ( 12 | normal = 0 13 | failure = 1 14 | invalidArgs = 128 15 | ) 16 | 17 | // Cobra command root 18 | var rootCmd = &cobra.Command{ 19 | Use: "mtprotod", 20 | Short: "Telegram MTProto proxy", 21 | Long: `Telegram Proxy. It has its own Telegram 22 | MTProto implementation, proxy clients can calls Telegram 23 | RPC procedures through it over gRPC.`, 24 | Version: "71.0.0 (MTProto Layer 71)", 25 | } 26 | 27 | // Execute runs the root Cobra command 28 | func Execute() { 29 | if err := rootCmd.Execute(); err != nil { 30 | fmt.Println(err) 31 | os.Exit(1) 32 | } 33 | } 34 | 35 | func init() { 36 | cobra.OnInitialize(initConfig) 37 | 38 | rootCmd.AddCommand(startCmd, testCmd) 39 | } 40 | 41 | func initConfig() { 42 | viper.AutomaticEnv() 43 | } 44 | -------------------------------------------------------------------------------- /mtprotod/cmd/start.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "github.com/spf13/viper" 6 | "github.com/cjongseok/mtproto" 7 | "github.com/cjongseok/mtproto/proxy" 8 | "os" 9 | "fmt" 10 | "strings" 11 | ) 12 | 13 | // MTProto config 14 | const ( 15 | appVersion = "0.0.1" 16 | deviceModel = "" 17 | systemVersion = "" 18 | language = "" 19 | ) 20 | 21 | // Proxy parameters 22 | var ( 23 | port int 24 | secrets string 25 | ) 26 | 27 | // Cobra command 28 | var startCmd = &cobra.Command{ 29 | Use: "start", 30 | Short: "Start proxy", 31 | Long: `Start a proxy at the given port.`, 32 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 33 | 34 | // check required flags 35 | required := []string{"port", "secrets"} 36 | var notset string 37 | for _, flagStr := range required { 38 | f := viper.Get(flagStr) 39 | switch casted := f.(type) { 40 | case string: 41 | if casted == "" { 42 | notset = fmt.Sprintf("%s, --%s", notset, flagStr) 43 | } 44 | case int: 45 | if casted == 0 { 46 | notset = fmt.Sprintf("%s, --%s", notset, flagStr) 47 | } 48 | } 49 | } 50 | if notset != "" { 51 | notset = strings.Replace(notset, ", ", "", 1) 52 | return fmt.Errorf("%s are required", notset) 53 | 54 | } 55 | 56 | // get flags 57 | port = viper.GetInt("port") 58 | secrets = viper.GetString("secrets") 59 | return nil 60 | }, 61 | Run: func(cmd *cobra.Command, args []string) { 62 | os.Exit(startProxy(port, secrets)) 63 | }, 64 | } 65 | 66 | func init() { 67 | viper.BindEnv("port") 68 | flags := startCmd.PersistentFlags() 69 | //flags.StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") 70 | 71 | flags.Int("port", 0, "port number the proxy runs at") 72 | flags.String("secrets", "", "MTProto secrets") 73 | 74 | viper.BindPFlag("port", flags.Lookup("port")) 75 | viper.BindPFlag("secrets", flags.Lookup("secrets")) 76 | } 77 | 78 | func startProxy(port int, secrets string) int { 79 | //slog.DisableLogging() 80 | config, err := mtproto.NewConfiguration(appVersion, deviceModel, systemVersion, language, 0, 0, secrets) 81 | if err != nil { 82 | return invalidArgs 83 | } 84 | server := proxy.NewServer(port) 85 | err = server.Start(config) 86 | if err != nil { 87 | return failure 88 | } 89 | server.Wait() 90 | return normal 91 | } 92 | -------------------------------------------------------------------------------- /mtprotod/cmd/test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "github.com/spf13/viper" 6 | "github.com/cjongseok/mtproto" 7 | "github.com/cjongseok/mtproto/proxy" 8 | "os" 9 | "fmt" 10 | "strings" 11 | "github.com/cjongseok/slog" 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | // Proxy client parameters 16 | var ( 17 | proxyAddr string 18 | //clientCfgFile string 19 | ) 20 | 21 | // Cobra command 22 | var testCmd = &cobra.Command{ 23 | Use: "test", 24 | Short: "Test proxy", 25 | Long: `Test the proxy by getting recent Telegram dialogs. 26 | If it is normal, you can see the dialogs in JSON.`, 27 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 28 | 29 | // check required flags 30 | required := []string{"proxy"} 31 | var notset string 32 | for _, flagStr := range required { 33 | f := viper.Get(flagStr) 34 | switch casted := f.(type) { 35 | case string: 36 | if casted == "" { 37 | notset = fmt.Sprintf("%s, --%s", notset, flagStr) 38 | } 39 | case int: 40 | if casted == 0 { 41 | notset = fmt.Sprintf("%s, --%s", notset, flagStr) 42 | } 43 | } 44 | } 45 | if notset != "" { 46 | notset = strings.Replace(notset, ", ", "", 1) 47 | return fmt.Errorf("%s are required", notset) 48 | 49 | } 50 | 51 | // get flags 52 | proxyAddr = viper.GetString("proxy") 53 | return nil 54 | }, 55 | Run: func(cmd *cobra.Command, args []string) { 56 | os.Exit(dialogs(proxyAddr)) 57 | }, 58 | } 59 | 60 | func init() { 61 | viper.BindEnv("proxy") 62 | flags := testCmd.PersistentFlags() 63 | //flags.StringVar(&clientCfgFile, "client_config", "", "config file (default is $HOME/.cobra.yaml)") 64 | 65 | flags.String("proxy", "", "Proxy address") 66 | viper.BindPFlag("proxy", flags.Lookup("proxy")) 67 | } 68 | 69 | func dialogs(addr string) int { 70 | client, err := proxy.NewClient(fmt.Sprintf("localhost:%d", port)) 71 | emptyPeer := &mtproto.TypeInputPeer{Value: &mtproto.TypeInputPeer_InputPeerEmpty{&mtproto.PredInputPeerEmpty{}}} 72 | dialogs, err := client.MessagesGetDialogs(context.Background(), &mtproto.ReqMessagesGetDialogs{ 73 | OffsetDate: 0, 74 | OffsetId: 0, 75 | OffsetPeer: emptyPeer, 76 | Limit: 1, 77 | }) 78 | if err != nil { 79 | fmt.Println("test failure:", err) 80 | return failure 81 | } 82 | switch casted := dialogs.Value.(type) { 83 | case *mtproto.TypeMessagesDialogs_MessagesDialogs: 84 | fmt.Println(slog.StringifyIndent(casted.MessagesDialogs, " ")) 85 | case *mtproto.TypeMessagesDialogs_MessagesDialogsSlice: 86 | fmt.Println(slog.StringifyIndent(casted.MessagesDialogsSlice, " ")) 87 | default: 88 | fmt.Println("got unknown types of dialogs:") 89 | fmt.Println(slog.StringifyIndent(dialogs.Value, " ")) 90 | } 91 | return normal 92 | } 93 | -------------------------------------------------------------------------------- /mtprotod/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/cjongseok/mtproto/mtprotod/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cjongseok/mtproto" 6 | "github.com/cjongseok/slog" 7 | "google.golang.org/grpc" 8 | "net" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type Server struct { 14 | grpcServer *grpc.Server 15 | mmanager *mtproto.Manager 16 | mconn *mtproto.Conn 17 | port int 18 | streams []chan *Update 19 | wg *sync.WaitGroup 20 | } 21 | 22 | func NewServer(port int) *Server { 23 | p := &Server{} 24 | grpcServer := grpc.NewServer([]grpc.ServerOption{}...) 25 | mtproto.RegisterMtprotoServer(grpcServer, &mtproto.RPCaller{p}) 26 | RegisterUpdateStreamerServer(grpcServer, p) 27 | p.grpcServer = grpcServer 28 | p.port = port 29 | p.wg = &sync.WaitGroup{} 30 | return p 31 | } 32 | 33 | func (p *Server) AddUpdateCallback(callback mtproto.UpdateCallback) { 34 | p.mconn.AddUpdateCallback(callback) 35 | } 36 | 37 | func (p *Server) InvokeBlocked(msg mtproto.TL) (interface{}, error) { 38 | return p.mconn.InvokeBlocked(msg) 39 | } 40 | 41 | func (p *Server) Start(config mtproto.Configuration) error { 42 | err := p.connect(config) 43 | if err != nil { 44 | return err 45 | } 46 | err = p.serve() 47 | if err != nil { 48 | return err 49 | } 50 | return nil 51 | } 52 | 53 | func (p *Server) connect(config mtproto.Configuration) error { // open mrptoro 54 | var err error 55 | p.mmanager, err = mtproto.NewManager(config) 56 | if err != nil { 57 | return fmt.Errorf("invalid configuration: %s", err) 58 | } 59 | p.mconn, err = p.mmanager.LoadAuthentication() 60 | if err != nil { 61 | return fmt.Errorf("load auth failure: %v", err) 62 | } 63 | p.mconn.AddUpdateCallback(p) 64 | return nil 65 | } 66 | 67 | func (p *Server) serve() error { 68 | lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", p.port)) 69 | if err != nil { 70 | return fmt.Errorf("socket open failure: %v", err) 71 | } 72 | p.wg.Add(1) 73 | go func() { 74 | defer p.wg.Done() 75 | slog.Logln(p, "start serving gRPC") 76 | err := p.grpcServer.Serve(lis) 77 | switch err { 78 | case grpc.ErrServerStopped: 79 | slog.Logln(p, "gRPC stopped:", err) 80 | default: 81 | slog.Logln(p, "unknown error on serving gRPC:", err) 82 | slog.Logln(p, "cool down the proxy for a second...") 83 | <-time.After(time.Second) 84 | slog.Logln(p, "shut down the socket and restart the proxy...") 85 | lis.Close() 86 | p.wg.Add(1) 87 | go func() { 88 | p.wg.Done() 89 | startErr := p.serve() 90 | if startErr != nil { 91 | slog.Logln(p, "restart failure:", startErr) 92 | } 93 | }() 94 | } 95 | }() 96 | return nil 97 | } 98 | 99 | func (p *Server) Wait() { 100 | p.wg.Wait() 101 | } 102 | 103 | func (p *Server) Stop() { 104 | p.grpcServer.GracefulStop() 105 | } 106 | 107 | func (p *Server) LogPrefix() string { 108 | return "[proxy]" 109 | } 110 | 111 | func (p *Server) OnUpdate(mu mtproto.Update) { 112 | pu := toProxyUpdate(mu) 113 | if pu != nil { 114 | for _, s := range p.streams { 115 | go func(stream chan *Update, update *Update) { 116 | stream <- update 117 | }(s, pu) 118 | } 119 | } 120 | } 121 | 122 | func (p *Server) ListenOnUpdates(req *ListenRequest, stream UpdateStreamer_ListenOnUpdatesServer) error { 123 | ch := make(chan *Update) 124 | //TODO: thread safe streams 125 | p.streams = append(p.streams, ch) 126 | for { 127 | select { 128 | case u := <-ch: 129 | if u == nil { 130 | slog.Logln(p, "close stream; channel closed") 131 | return nil 132 | } 133 | if err := stream.Send(u); err != nil { 134 | // TODO: monitor the channels condition and reset abnormal ones 135 | slog.Logln(p, "stream an update failure:", err) 136 | } 137 | } 138 | } 139 | } 140 | 141 | type Client struct { 142 | mtproto.MtprotoClient 143 | UpdateStreamerClient 144 | } 145 | 146 | func NewClient(addr string) (*Client, error) { 147 | conn, err := grpc.Dial(addr, []grpc.DialOption{grpc.WithInsecure()}...) 148 | if err != nil { 149 | return nil, err 150 | } 151 | mtprotorotoClient := mtproto.NewMtprotoClient(conn) 152 | updateClient := NewUpdateStreamerClient(conn) 153 | return &Client{mtprotorotoClient, updateClient}, nil 154 | } 155 | 156 | func toProxyUpdate(u mtproto.Update) *Update { 157 | switch pu := u.(type) { 158 | case *mtproto.PredUpdatesState: 159 | return &Update{Value: &Update_UpdatesState{pu}} 160 | case *mtproto.PredUpdateShortMessage: 161 | return &Update{Value: &Update_UpdateShortMessage{pu}} 162 | case *mtproto.PredUpdateShortChatMessage: 163 | return &Update{Value: &Update_UpdateShortChatMessage{pu}} 164 | case *mtproto.PredUpdateShort: 165 | return &Update{Value: &Update_UpdateShort{pu}} 166 | case *mtproto.PredUpdates: 167 | return &Update{Value: &Update_Updates{pu}} 168 | case *mtproto.PredUpdateShortSentMessage: 169 | return &Update{Value: &Update_UpdateShortSentMessage{pu}} 170 | case *mtproto.PredUpdatesDifference: 171 | return &Update{Value: &Update_UpdatesDifference{pu}} 172 | case *mtproto.PredUpdatesDifferenceSlice: 173 | return &Update{Value: &Update_UpdatesDifferenceSlice{pu}} 174 | case *mtproto.PredUpdateNewMessage: 175 | return &Update{Value: &Update_UpdateNewMessage{pu}} 176 | case *mtproto.PredUpdateReadMessagesContents: 177 | return &Update{Value: &Update_UpdateReadMessagesContents{pu}} 178 | case *mtproto.PredUpdateDeleteMessages: 179 | return &Update{Value: &Update_UpdateDeleteMessages{pu}} 180 | case *mtproto.PredUpdateNewEncryptedMessage: 181 | return &Update{Value: &Update_UpdateNewEncryptedMessage{pu}} 182 | case *mtproto.PredUpdateChannel: 183 | return &Update{Value: &Update_UpdateChannel{pu}} 184 | case *mtproto.PredUpdateChannelMessageViews: 185 | return &Update{Value: &Update_UpdateChannelMessageViews{pu}} 186 | case *mtproto.PredUpdateChannelTooLong: 187 | return &Update{Value: &Update_UpdateChannelTooLong{pu}} 188 | case *mtproto.PredUpdateReadChannelInbox: 189 | return &Update{Value: &Update_UpdateReadChannelInbox{pu}} 190 | case *mtproto.PredUpdateReadChannelOutbox: 191 | return &Update{Value: &Update_UpdateReadChannelOutbox{pu}} 192 | case *mtproto.PredUpdateNewChannelMessage: 193 | return &Update{Value: &Update_UpdateNewChannelMessage{pu}} 194 | } 195 | return nil 196 | } 197 | -------------------------------------------------------------------------------- /proxy/proxy_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/cjongseok/mtproto" 7 | "github.com/cjongseok/slog" 8 | "golang.org/x/net/context" 9 | "testing" 10 | ) 11 | 12 | const ( 13 | port = 11011 14 | appVersion = "0.0.1" 15 | deviceModel = "" 16 | systemVersion = "" 17 | language = "" 18 | sessionFileHome = "" 19 | telegramAddress = "149.154.167.50:443" 20 | ) 21 | 22 | var ( 23 | //apiId = flag.Int("apiid", 0, "Telegram API id") 24 | //apiHash = flag.String("apihash", "", "Telegram API hash") 25 | //phone = flag.String("phone", "", "Phone number including nation code") 26 | //addr = flag.String("addr", "", "Preferred Telegram server address") 27 | secrets = flag.String("secrets", "", "MTProto secrets file") 28 | 29 | proxy *Server 30 | client *Client 31 | ) 32 | 33 | func beforeTest(t *testing.T) { 34 | flag.Parse() 35 | if *secrets == "" { 36 | flag.Usage() 37 | t.FailNow() 38 | } 39 | 40 | if proxy == nil { 41 | configuration, err := mtproto.NewConfiguration(appVersion, deviceModel, systemVersion, language, 0, 0, *secrets) 42 | handleError(t, err) 43 | proxy = NewServer(port) 44 | err = proxy.Start(configuration) 45 | handleError(t, err) 46 | } 47 | 48 | var err error 49 | client, err = NewClient(fmt.Sprintf("localhost:%d", port)) 50 | handleError(t, err) 51 | } 52 | 53 | func afterTest(t *testing.T) { 54 | //proxy.Stop() 55 | //proxy = nil 56 | client = nil 57 | } 58 | func TestDialogs(t *testing.T) { 59 | beforeTest(t) 60 | defer afterTest(t) 61 | emptyPeer := &mtproto.TypeInputPeer{Value: &mtproto.TypeInputPeer_InputPeerEmpty{&mtproto.PredInputPeerEmpty{}}} 62 | dialogs, err := client.MessagesGetDialogs(context.Background(), &mtproto.ReqMessagesGetDialogs{ 63 | OffsetDate: 0, 64 | OffsetId: 0, 65 | OffsetPeer: emptyPeer, 66 | Limit: 1, 67 | }) 68 | handleError(t, err) 69 | switch casted := dialogs.Value.(type) { 70 | case *mtproto.TypeMessagesDialogs_MessagesDialogs: 71 | fmt.Println(slog.StringifyIndent(casted.MessagesDialogs, " ")) 72 | case *mtproto.TypeMessagesDialogs_MessagesDialogsSlice: 73 | fmt.Println(slog.StringifyIndent(casted.MessagesDialogsSlice, " ")) 74 | default: 75 | handleError(t, fmt.Errorf("unknown return: %T: %v", dialogs.Value, dialogs.Value)) 76 | } 77 | } 78 | 79 | func TestUpdateMessage(t *testing.T) { 80 | // send a message 81 | 82 | // update the message 83 | } 84 | 85 | func TestListenOnUpdates(t *testing.T) { 86 | beforeTest(t) 87 | defer afterTest(t) 88 | stream, err := client.ListenOnUpdates(context.Background(), &ListenRequest{}) 89 | handleError(t, err) 90 | limit := 5 91 | for updateCounter := 0; updateCounter < limit; { 92 | u, err := stream.Recv() 93 | handleError(t, err) 94 | fmt.Println("[client]: got an update:", u) 95 | updateCounter++ 96 | } 97 | } 98 | 99 | func handleError(t *testing.T, err error) { 100 | if err != nil { 101 | fmt.Println("error:", err) 102 | t.FailNow() 103 | t.SkipNow() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /proxy/tl_update.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: tl_update.proto 3 | 4 | package proxy 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | import mtproto "github.com/cjongseok/mtproto" 10 | 11 | import ( 12 | context "golang.org/x/net/context" 13 | grpc "google.golang.org/grpc" 14 | ) 15 | 16 | // Reference imports to suppress errors if they are not otherwise used. 17 | var _ = proto.Marshal 18 | var _ = fmt.Errorf 19 | var _ = math.Inf 20 | 21 | // This is a compile-time assertion to ensure that this generated file 22 | // is compatible with the proto package it is being compiled against. 23 | // A compilation error at this line likely means your copy of the 24 | // proto package needs to be updated. 25 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 26 | 27 | type Update struct { 28 | // Types that are valid to be assigned to Value: 29 | // *Update_UpdatesState 30 | // *Update_UpdateShortMessage 31 | // *Update_UpdateShortChatMessage 32 | // *Update_UpdateShort 33 | // *Update_Updates 34 | // *Update_UpdateShortSentMessage 35 | // *Update_UpdatesDifference 36 | // *Update_UpdatesDifferenceSlice 37 | // *Update_UpdateNewMessage 38 | // *Update_UpdateReadMessagesContents 39 | // *Update_UpdateDeleteMessages 40 | // *Update_UpdateNewEncryptedMessage 41 | // *Update_UpdateChannel 42 | // *Update_UpdateChannelMessageViews 43 | // *Update_UpdateChannelTooLong 44 | // *Update_UpdateReadChannelInbox 45 | // *Update_UpdateReadChannelOutbox 46 | // *Update_UpdateNewChannelMessage 47 | Value isUpdate_Value `protobuf_oneof:"Value"` 48 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 49 | XXX_unrecognized []byte `json:"-"` 50 | XXX_sizecache int32 `json:"-"` 51 | } 52 | 53 | func (m *Update) Reset() { *m = Update{} } 54 | func (m *Update) String() string { return proto.CompactTextString(m) } 55 | func (*Update) ProtoMessage() {} 56 | func (*Update) Descriptor() ([]byte, []int) { 57 | return fileDescriptor_tl_update_1c97a93e1b72e269, []int{0} 58 | } 59 | func (m *Update) XXX_Unmarshal(b []byte) error { 60 | return xxx_messageInfo_Update.Unmarshal(m, b) 61 | } 62 | func (m *Update) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 63 | return xxx_messageInfo_Update.Marshal(b, m, deterministic) 64 | } 65 | func (dst *Update) XXX_Merge(src proto.Message) { 66 | xxx_messageInfo_Update.Merge(dst, src) 67 | } 68 | func (m *Update) XXX_Size() int { 69 | return xxx_messageInfo_Update.Size(m) 70 | } 71 | func (m *Update) XXX_DiscardUnknown() { 72 | xxx_messageInfo_Update.DiscardUnknown(m) 73 | } 74 | 75 | var xxx_messageInfo_Update proto.InternalMessageInfo 76 | 77 | type isUpdate_Value interface { 78 | isUpdate_Value() 79 | } 80 | 81 | type Update_UpdatesState struct { 82 | UpdatesState *mtproto.PredUpdatesState `protobuf:"bytes,1,opt,name=UpdatesState,oneof"` 83 | } 84 | type Update_UpdateShortMessage struct { 85 | UpdateShortMessage *mtproto.PredUpdateShortMessage `protobuf:"bytes,2,opt,name=UpdateShortMessage,oneof"` 86 | } 87 | type Update_UpdateShortChatMessage struct { 88 | UpdateShortChatMessage *mtproto.PredUpdateShortChatMessage `protobuf:"bytes,3,opt,name=UpdateShortChatMessage,oneof"` 89 | } 90 | type Update_UpdateShort struct { 91 | UpdateShort *mtproto.PredUpdateShort `protobuf:"bytes,4,opt,name=UpdateShort,oneof"` 92 | } 93 | type Update_Updates struct { 94 | Updates *mtproto.PredUpdates `protobuf:"bytes,5,opt,name=Updates,oneof"` 95 | } 96 | type Update_UpdateShortSentMessage struct { 97 | UpdateShortSentMessage *mtproto.PredUpdateShortSentMessage `protobuf:"bytes,6,opt,name=UpdateShortSentMessage,oneof"` 98 | } 99 | type Update_UpdatesDifference struct { 100 | UpdatesDifference *mtproto.PredUpdatesDifference `protobuf:"bytes,7,opt,name=UpdatesDifference,oneof"` 101 | } 102 | type Update_UpdatesDifferenceSlice struct { 103 | UpdatesDifferenceSlice *mtproto.PredUpdatesDifferenceSlice `protobuf:"bytes,8,opt,name=UpdatesDifferenceSlice,oneof"` 104 | } 105 | type Update_UpdateNewMessage struct { 106 | UpdateNewMessage *mtproto.PredUpdateNewMessage `protobuf:"bytes,9,opt,name=UpdateNewMessage,oneof"` 107 | } 108 | type Update_UpdateReadMessagesContents struct { 109 | UpdateReadMessagesContents *mtproto.PredUpdateReadMessagesContents `protobuf:"bytes,10,opt,name=UpdateReadMessagesContents,oneof"` 110 | } 111 | type Update_UpdateDeleteMessages struct { 112 | UpdateDeleteMessages *mtproto.PredUpdateDeleteMessages `protobuf:"bytes,11,opt,name=UpdateDeleteMessages,oneof"` 113 | } 114 | type Update_UpdateNewEncryptedMessage struct { 115 | UpdateNewEncryptedMessage *mtproto.PredUpdateNewEncryptedMessage `protobuf:"bytes,12,opt,name=UpdateNewEncryptedMessage,oneof"` 116 | } 117 | type Update_UpdateChannel struct { 118 | UpdateChannel *mtproto.PredUpdateChannel `protobuf:"bytes,13,opt,name=UpdateChannel,oneof"` 119 | } 120 | type Update_UpdateChannelMessageViews struct { 121 | UpdateChannelMessageViews *mtproto.PredUpdateChannelMessageViews `protobuf:"bytes,14,opt,name=UpdateChannelMessageViews,oneof"` 122 | } 123 | type Update_UpdateChannelTooLong struct { 124 | UpdateChannelTooLong *mtproto.PredUpdateChannelTooLong `protobuf:"bytes,15,opt,name=UpdateChannelTooLong,oneof"` 125 | } 126 | type Update_UpdateReadChannelInbox struct { 127 | UpdateReadChannelInbox *mtproto.PredUpdateReadChannelInbox `protobuf:"bytes,16,opt,name=UpdateReadChannelInbox,oneof"` 128 | } 129 | type Update_UpdateReadChannelOutbox struct { 130 | UpdateReadChannelOutbox *mtproto.PredUpdateReadChannelOutbox `protobuf:"bytes,17,opt,name=UpdateReadChannelOutbox,oneof"` 131 | } 132 | type Update_UpdateNewChannelMessage struct { 133 | UpdateNewChannelMessage *mtproto.PredUpdateNewChannelMessage `protobuf:"bytes,18,opt,name=UpdateNewChannelMessage,oneof"` 134 | } 135 | 136 | func (*Update_UpdatesState) isUpdate_Value() {} 137 | func (*Update_UpdateShortMessage) isUpdate_Value() {} 138 | func (*Update_UpdateShortChatMessage) isUpdate_Value() {} 139 | func (*Update_UpdateShort) isUpdate_Value() {} 140 | func (*Update_Updates) isUpdate_Value() {} 141 | func (*Update_UpdateShortSentMessage) isUpdate_Value() {} 142 | func (*Update_UpdatesDifference) isUpdate_Value() {} 143 | func (*Update_UpdatesDifferenceSlice) isUpdate_Value() {} 144 | func (*Update_UpdateNewMessage) isUpdate_Value() {} 145 | func (*Update_UpdateReadMessagesContents) isUpdate_Value() {} 146 | func (*Update_UpdateDeleteMessages) isUpdate_Value() {} 147 | func (*Update_UpdateNewEncryptedMessage) isUpdate_Value() {} 148 | func (*Update_UpdateChannel) isUpdate_Value() {} 149 | func (*Update_UpdateChannelMessageViews) isUpdate_Value() {} 150 | func (*Update_UpdateChannelTooLong) isUpdate_Value() {} 151 | func (*Update_UpdateReadChannelInbox) isUpdate_Value() {} 152 | func (*Update_UpdateReadChannelOutbox) isUpdate_Value() {} 153 | func (*Update_UpdateNewChannelMessage) isUpdate_Value() {} 154 | 155 | func (m *Update) GetValue() isUpdate_Value { 156 | if m != nil { 157 | return m.Value 158 | } 159 | return nil 160 | } 161 | 162 | func (m *Update) GetUpdatesState() *mtproto.PredUpdatesState { 163 | if x, ok := m.GetValue().(*Update_UpdatesState); ok { 164 | return x.UpdatesState 165 | } 166 | return nil 167 | } 168 | 169 | func (m *Update) GetUpdateShortMessage() *mtproto.PredUpdateShortMessage { 170 | if x, ok := m.GetValue().(*Update_UpdateShortMessage); ok { 171 | return x.UpdateShortMessage 172 | } 173 | return nil 174 | } 175 | 176 | func (m *Update) GetUpdateShortChatMessage() *mtproto.PredUpdateShortChatMessage { 177 | if x, ok := m.GetValue().(*Update_UpdateShortChatMessage); ok { 178 | return x.UpdateShortChatMessage 179 | } 180 | return nil 181 | } 182 | 183 | func (m *Update) GetUpdateShort() *mtproto.PredUpdateShort { 184 | if x, ok := m.GetValue().(*Update_UpdateShort); ok { 185 | return x.UpdateShort 186 | } 187 | return nil 188 | } 189 | 190 | func (m *Update) GetUpdates() *mtproto.PredUpdates { 191 | if x, ok := m.GetValue().(*Update_Updates); ok { 192 | return x.Updates 193 | } 194 | return nil 195 | } 196 | 197 | func (m *Update) GetUpdateShortSentMessage() *mtproto.PredUpdateShortSentMessage { 198 | if x, ok := m.GetValue().(*Update_UpdateShortSentMessage); ok { 199 | return x.UpdateShortSentMessage 200 | } 201 | return nil 202 | } 203 | 204 | func (m *Update) GetUpdatesDifference() *mtproto.PredUpdatesDifference { 205 | if x, ok := m.GetValue().(*Update_UpdatesDifference); ok { 206 | return x.UpdatesDifference 207 | } 208 | return nil 209 | } 210 | 211 | func (m *Update) GetUpdatesDifferenceSlice() *mtproto.PredUpdatesDifferenceSlice { 212 | if x, ok := m.GetValue().(*Update_UpdatesDifferenceSlice); ok { 213 | return x.UpdatesDifferenceSlice 214 | } 215 | return nil 216 | } 217 | 218 | func (m *Update) GetUpdateNewMessage() *mtproto.PredUpdateNewMessage { 219 | if x, ok := m.GetValue().(*Update_UpdateNewMessage); ok { 220 | return x.UpdateNewMessage 221 | } 222 | return nil 223 | } 224 | 225 | func (m *Update) GetUpdateReadMessagesContents() *mtproto.PredUpdateReadMessagesContents { 226 | if x, ok := m.GetValue().(*Update_UpdateReadMessagesContents); ok { 227 | return x.UpdateReadMessagesContents 228 | } 229 | return nil 230 | } 231 | 232 | func (m *Update) GetUpdateDeleteMessages() *mtproto.PredUpdateDeleteMessages { 233 | if x, ok := m.GetValue().(*Update_UpdateDeleteMessages); ok { 234 | return x.UpdateDeleteMessages 235 | } 236 | return nil 237 | } 238 | 239 | func (m *Update) GetUpdateNewEncryptedMessage() *mtproto.PredUpdateNewEncryptedMessage { 240 | if x, ok := m.GetValue().(*Update_UpdateNewEncryptedMessage); ok { 241 | return x.UpdateNewEncryptedMessage 242 | } 243 | return nil 244 | } 245 | 246 | func (m *Update) GetUpdateChannel() *mtproto.PredUpdateChannel { 247 | if x, ok := m.GetValue().(*Update_UpdateChannel); ok { 248 | return x.UpdateChannel 249 | } 250 | return nil 251 | } 252 | 253 | func (m *Update) GetUpdateChannelMessageViews() *mtproto.PredUpdateChannelMessageViews { 254 | if x, ok := m.GetValue().(*Update_UpdateChannelMessageViews); ok { 255 | return x.UpdateChannelMessageViews 256 | } 257 | return nil 258 | } 259 | 260 | func (m *Update) GetUpdateChannelTooLong() *mtproto.PredUpdateChannelTooLong { 261 | if x, ok := m.GetValue().(*Update_UpdateChannelTooLong); ok { 262 | return x.UpdateChannelTooLong 263 | } 264 | return nil 265 | } 266 | 267 | func (m *Update) GetUpdateReadChannelInbox() *mtproto.PredUpdateReadChannelInbox { 268 | if x, ok := m.GetValue().(*Update_UpdateReadChannelInbox); ok { 269 | return x.UpdateReadChannelInbox 270 | } 271 | return nil 272 | } 273 | 274 | func (m *Update) GetUpdateReadChannelOutbox() *mtproto.PredUpdateReadChannelOutbox { 275 | if x, ok := m.GetValue().(*Update_UpdateReadChannelOutbox); ok { 276 | return x.UpdateReadChannelOutbox 277 | } 278 | return nil 279 | } 280 | 281 | func (m *Update) GetUpdateNewChannelMessage() *mtproto.PredUpdateNewChannelMessage { 282 | if x, ok := m.GetValue().(*Update_UpdateNewChannelMessage); ok { 283 | return x.UpdateNewChannelMessage 284 | } 285 | return nil 286 | } 287 | 288 | // XXX_OneofFuncs is for the internal use of the proto package. 289 | func (*Update) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { 290 | return _Update_OneofMarshaler, _Update_OneofUnmarshaler, _Update_OneofSizer, []interface{}{ 291 | (*Update_UpdatesState)(nil), 292 | (*Update_UpdateShortMessage)(nil), 293 | (*Update_UpdateShortChatMessage)(nil), 294 | (*Update_UpdateShort)(nil), 295 | (*Update_Updates)(nil), 296 | (*Update_UpdateShortSentMessage)(nil), 297 | (*Update_UpdatesDifference)(nil), 298 | (*Update_UpdatesDifferenceSlice)(nil), 299 | (*Update_UpdateNewMessage)(nil), 300 | (*Update_UpdateReadMessagesContents)(nil), 301 | (*Update_UpdateDeleteMessages)(nil), 302 | (*Update_UpdateNewEncryptedMessage)(nil), 303 | (*Update_UpdateChannel)(nil), 304 | (*Update_UpdateChannelMessageViews)(nil), 305 | (*Update_UpdateChannelTooLong)(nil), 306 | (*Update_UpdateReadChannelInbox)(nil), 307 | (*Update_UpdateReadChannelOutbox)(nil), 308 | (*Update_UpdateNewChannelMessage)(nil), 309 | } 310 | } 311 | 312 | func _Update_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { 313 | m := msg.(*Update) 314 | // Value 315 | switch x := m.Value.(type) { 316 | case *Update_UpdatesState: 317 | b.EncodeVarint(1<<3 | proto.WireBytes) 318 | if err := b.EncodeMessage(x.UpdatesState); err != nil { 319 | return err 320 | } 321 | case *Update_UpdateShortMessage: 322 | b.EncodeVarint(2<<3 | proto.WireBytes) 323 | if err := b.EncodeMessage(x.UpdateShortMessage); err != nil { 324 | return err 325 | } 326 | case *Update_UpdateShortChatMessage: 327 | b.EncodeVarint(3<<3 | proto.WireBytes) 328 | if err := b.EncodeMessage(x.UpdateShortChatMessage); err != nil { 329 | return err 330 | } 331 | case *Update_UpdateShort: 332 | b.EncodeVarint(4<<3 | proto.WireBytes) 333 | if err := b.EncodeMessage(x.UpdateShort); err != nil { 334 | return err 335 | } 336 | case *Update_Updates: 337 | b.EncodeVarint(5<<3 | proto.WireBytes) 338 | if err := b.EncodeMessage(x.Updates); err != nil { 339 | return err 340 | } 341 | case *Update_UpdateShortSentMessage: 342 | b.EncodeVarint(6<<3 | proto.WireBytes) 343 | if err := b.EncodeMessage(x.UpdateShortSentMessage); err != nil { 344 | return err 345 | } 346 | case *Update_UpdatesDifference: 347 | b.EncodeVarint(7<<3 | proto.WireBytes) 348 | if err := b.EncodeMessage(x.UpdatesDifference); err != nil { 349 | return err 350 | } 351 | case *Update_UpdatesDifferenceSlice: 352 | b.EncodeVarint(8<<3 | proto.WireBytes) 353 | if err := b.EncodeMessage(x.UpdatesDifferenceSlice); err != nil { 354 | return err 355 | } 356 | case *Update_UpdateNewMessage: 357 | b.EncodeVarint(9<<3 | proto.WireBytes) 358 | if err := b.EncodeMessage(x.UpdateNewMessage); err != nil { 359 | return err 360 | } 361 | case *Update_UpdateReadMessagesContents: 362 | b.EncodeVarint(10<<3 | proto.WireBytes) 363 | if err := b.EncodeMessage(x.UpdateReadMessagesContents); err != nil { 364 | return err 365 | } 366 | case *Update_UpdateDeleteMessages: 367 | b.EncodeVarint(11<<3 | proto.WireBytes) 368 | if err := b.EncodeMessage(x.UpdateDeleteMessages); err != nil { 369 | return err 370 | } 371 | case *Update_UpdateNewEncryptedMessage: 372 | b.EncodeVarint(12<<3 | proto.WireBytes) 373 | if err := b.EncodeMessage(x.UpdateNewEncryptedMessage); err != nil { 374 | return err 375 | } 376 | case *Update_UpdateChannel: 377 | b.EncodeVarint(13<<3 | proto.WireBytes) 378 | if err := b.EncodeMessage(x.UpdateChannel); err != nil { 379 | return err 380 | } 381 | case *Update_UpdateChannelMessageViews: 382 | b.EncodeVarint(14<<3 | proto.WireBytes) 383 | if err := b.EncodeMessage(x.UpdateChannelMessageViews); err != nil { 384 | return err 385 | } 386 | case *Update_UpdateChannelTooLong: 387 | b.EncodeVarint(15<<3 | proto.WireBytes) 388 | if err := b.EncodeMessage(x.UpdateChannelTooLong); err != nil { 389 | return err 390 | } 391 | case *Update_UpdateReadChannelInbox: 392 | b.EncodeVarint(16<<3 | proto.WireBytes) 393 | if err := b.EncodeMessage(x.UpdateReadChannelInbox); err != nil { 394 | return err 395 | } 396 | case *Update_UpdateReadChannelOutbox: 397 | b.EncodeVarint(17<<3 | proto.WireBytes) 398 | if err := b.EncodeMessage(x.UpdateReadChannelOutbox); err != nil { 399 | return err 400 | } 401 | case *Update_UpdateNewChannelMessage: 402 | b.EncodeVarint(18<<3 | proto.WireBytes) 403 | if err := b.EncodeMessage(x.UpdateNewChannelMessage); err != nil { 404 | return err 405 | } 406 | case nil: 407 | default: 408 | return fmt.Errorf("Update.Value has unexpected type %T", x) 409 | } 410 | return nil 411 | } 412 | 413 | func _Update_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { 414 | m := msg.(*Update) 415 | switch tag { 416 | case 1: // Value.UpdatesState 417 | if wire != proto.WireBytes { 418 | return true, proto.ErrInternalBadWireType 419 | } 420 | msg := new(mtproto.PredUpdatesState) 421 | err := b.DecodeMessage(msg) 422 | m.Value = &Update_UpdatesState{msg} 423 | return true, err 424 | case 2: // Value.UpdateShortMessage 425 | if wire != proto.WireBytes { 426 | return true, proto.ErrInternalBadWireType 427 | } 428 | msg := new(mtproto.PredUpdateShortMessage) 429 | err := b.DecodeMessage(msg) 430 | m.Value = &Update_UpdateShortMessage{msg} 431 | return true, err 432 | case 3: // Value.UpdateShortChatMessage 433 | if wire != proto.WireBytes { 434 | return true, proto.ErrInternalBadWireType 435 | } 436 | msg := new(mtproto.PredUpdateShortChatMessage) 437 | err := b.DecodeMessage(msg) 438 | m.Value = &Update_UpdateShortChatMessage{msg} 439 | return true, err 440 | case 4: // Value.UpdateShort 441 | if wire != proto.WireBytes { 442 | return true, proto.ErrInternalBadWireType 443 | } 444 | msg := new(mtproto.PredUpdateShort) 445 | err := b.DecodeMessage(msg) 446 | m.Value = &Update_UpdateShort{msg} 447 | return true, err 448 | case 5: // Value.Updates 449 | if wire != proto.WireBytes { 450 | return true, proto.ErrInternalBadWireType 451 | } 452 | msg := new(mtproto.PredUpdates) 453 | err := b.DecodeMessage(msg) 454 | m.Value = &Update_Updates{msg} 455 | return true, err 456 | case 6: // Value.UpdateShortSentMessage 457 | if wire != proto.WireBytes { 458 | return true, proto.ErrInternalBadWireType 459 | } 460 | msg := new(mtproto.PredUpdateShortSentMessage) 461 | err := b.DecodeMessage(msg) 462 | m.Value = &Update_UpdateShortSentMessage{msg} 463 | return true, err 464 | case 7: // Value.UpdatesDifference 465 | if wire != proto.WireBytes { 466 | return true, proto.ErrInternalBadWireType 467 | } 468 | msg := new(mtproto.PredUpdatesDifference) 469 | err := b.DecodeMessage(msg) 470 | m.Value = &Update_UpdatesDifference{msg} 471 | return true, err 472 | case 8: // Value.UpdatesDifferenceSlice 473 | if wire != proto.WireBytes { 474 | return true, proto.ErrInternalBadWireType 475 | } 476 | msg := new(mtproto.PredUpdatesDifferenceSlice) 477 | err := b.DecodeMessage(msg) 478 | m.Value = &Update_UpdatesDifferenceSlice{msg} 479 | return true, err 480 | case 9: // Value.UpdateNewMessage 481 | if wire != proto.WireBytes { 482 | return true, proto.ErrInternalBadWireType 483 | } 484 | msg := new(mtproto.PredUpdateNewMessage) 485 | err := b.DecodeMessage(msg) 486 | m.Value = &Update_UpdateNewMessage{msg} 487 | return true, err 488 | case 10: // Value.UpdateReadMessagesContents 489 | if wire != proto.WireBytes { 490 | return true, proto.ErrInternalBadWireType 491 | } 492 | msg := new(mtproto.PredUpdateReadMessagesContents) 493 | err := b.DecodeMessage(msg) 494 | m.Value = &Update_UpdateReadMessagesContents{msg} 495 | return true, err 496 | case 11: // Value.UpdateDeleteMessages 497 | if wire != proto.WireBytes { 498 | return true, proto.ErrInternalBadWireType 499 | } 500 | msg := new(mtproto.PredUpdateDeleteMessages) 501 | err := b.DecodeMessage(msg) 502 | m.Value = &Update_UpdateDeleteMessages{msg} 503 | return true, err 504 | case 12: // Value.UpdateNewEncryptedMessage 505 | if wire != proto.WireBytes { 506 | return true, proto.ErrInternalBadWireType 507 | } 508 | msg := new(mtproto.PredUpdateNewEncryptedMessage) 509 | err := b.DecodeMessage(msg) 510 | m.Value = &Update_UpdateNewEncryptedMessage{msg} 511 | return true, err 512 | case 13: // Value.UpdateChannel 513 | if wire != proto.WireBytes { 514 | return true, proto.ErrInternalBadWireType 515 | } 516 | msg := new(mtproto.PredUpdateChannel) 517 | err := b.DecodeMessage(msg) 518 | m.Value = &Update_UpdateChannel{msg} 519 | return true, err 520 | case 14: // Value.UpdateChannelMessageViews 521 | if wire != proto.WireBytes { 522 | return true, proto.ErrInternalBadWireType 523 | } 524 | msg := new(mtproto.PredUpdateChannelMessageViews) 525 | err := b.DecodeMessage(msg) 526 | m.Value = &Update_UpdateChannelMessageViews{msg} 527 | return true, err 528 | case 15: // Value.UpdateChannelTooLong 529 | if wire != proto.WireBytes { 530 | return true, proto.ErrInternalBadWireType 531 | } 532 | msg := new(mtproto.PredUpdateChannelTooLong) 533 | err := b.DecodeMessage(msg) 534 | m.Value = &Update_UpdateChannelTooLong{msg} 535 | return true, err 536 | case 16: // Value.UpdateReadChannelInbox 537 | if wire != proto.WireBytes { 538 | return true, proto.ErrInternalBadWireType 539 | } 540 | msg := new(mtproto.PredUpdateReadChannelInbox) 541 | err := b.DecodeMessage(msg) 542 | m.Value = &Update_UpdateReadChannelInbox{msg} 543 | return true, err 544 | case 17: // Value.UpdateReadChannelOutbox 545 | if wire != proto.WireBytes { 546 | return true, proto.ErrInternalBadWireType 547 | } 548 | msg := new(mtproto.PredUpdateReadChannelOutbox) 549 | err := b.DecodeMessage(msg) 550 | m.Value = &Update_UpdateReadChannelOutbox{msg} 551 | return true, err 552 | case 18: // Value.UpdateNewChannelMessage 553 | if wire != proto.WireBytes { 554 | return true, proto.ErrInternalBadWireType 555 | } 556 | msg := new(mtproto.PredUpdateNewChannelMessage) 557 | err := b.DecodeMessage(msg) 558 | m.Value = &Update_UpdateNewChannelMessage{msg} 559 | return true, err 560 | default: 561 | return false, nil 562 | } 563 | } 564 | 565 | func _Update_OneofSizer(msg proto.Message) (n int) { 566 | m := msg.(*Update) 567 | // Value 568 | switch x := m.Value.(type) { 569 | case *Update_UpdatesState: 570 | s := proto.Size(x.UpdatesState) 571 | n += 1 // tag and wire 572 | n += proto.SizeVarint(uint64(s)) 573 | n += s 574 | case *Update_UpdateShortMessage: 575 | s := proto.Size(x.UpdateShortMessage) 576 | n += 1 // tag and wire 577 | n += proto.SizeVarint(uint64(s)) 578 | n += s 579 | case *Update_UpdateShortChatMessage: 580 | s := proto.Size(x.UpdateShortChatMessage) 581 | n += 1 // tag and wire 582 | n += proto.SizeVarint(uint64(s)) 583 | n += s 584 | case *Update_UpdateShort: 585 | s := proto.Size(x.UpdateShort) 586 | n += 1 // tag and wire 587 | n += proto.SizeVarint(uint64(s)) 588 | n += s 589 | case *Update_Updates: 590 | s := proto.Size(x.Updates) 591 | n += 1 // tag and wire 592 | n += proto.SizeVarint(uint64(s)) 593 | n += s 594 | case *Update_UpdateShortSentMessage: 595 | s := proto.Size(x.UpdateShortSentMessage) 596 | n += 1 // tag and wire 597 | n += proto.SizeVarint(uint64(s)) 598 | n += s 599 | case *Update_UpdatesDifference: 600 | s := proto.Size(x.UpdatesDifference) 601 | n += 1 // tag and wire 602 | n += proto.SizeVarint(uint64(s)) 603 | n += s 604 | case *Update_UpdatesDifferenceSlice: 605 | s := proto.Size(x.UpdatesDifferenceSlice) 606 | n += 1 // tag and wire 607 | n += proto.SizeVarint(uint64(s)) 608 | n += s 609 | case *Update_UpdateNewMessage: 610 | s := proto.Size(x.UpdateNewMessage) 611 | n += 1 // tag and wire 612 | n += proto.SizeVarint(uint64(s)) 613 | n += s 614 | case *Update_UpdateReadMessagesContents: 615 | s := proto.Size(x.UpdateReadMessagesContents) 616 | n += 1 // tag and wire 617 | n += proto.SizeVarint(uint64(s)) 618 | n += s 619 | case *Update_UpdateDeleteMessages: 620 | s := proto.Size(x.UpdateDeleteMessages) 621 | n += 1 // tag and wire 622 | n += proto.SizeVarint(uint64(s)) 623 | n += s 624 | case *Update_UpdateNewEncryptedMessage: 625 | s := proto.Size(x.UpdateNewEncryptedMessage) 626 | n += 1 // tag and wire 627 | n += proto.SizeVarint(uint64(s)) 628 | n += s 629 | case *Update_UpdateChannel: 630 | s := proto.Size(x.UpdateChannel) 631 | n += 1 // tag and wire 632 | n += proto.SizeVarint(uint64(s)) 633 | n += s 634 | case *Update_UpdateChannelMessageViews: 635 | s := proto.Size(x.UpdateChannelMessageViews) 636 | n += 1 // tag and wire 637 | n += proto.SizeVarint(uint64(s)) 638 | n += s 639 | case *Update_UpdateChannelTooLong: 640 | s := proto.Size(x.UpdateChannelTooLong) 641 | n += 1 // tag and wire 642 | n += proto.SizeVarint(uint64(s)) 643 | n += s 644 | case *Update_UpdateReadChannelInbox: 645 | s := proto.Size(x.UpdateReadChannelInbox) 646 | n += 2 // tag and wire 647 | n += proto.SizeVarint(uint64(s)) 648 | n += s 649 | case *Update_UpdateReadChannelOutbox: 650 | s := proto.Size(x.UpdateReadChannelOutbox) 651 | n += 2 // tag and wire 652 | n += proto.SizeVarint(uint64(s)) 653 | n += s 654 | case *Update_UpdateNewChannelMessage: 655 | s := proto.Size(x.UpdateNewChannelMessage) 656 | n += 2 // tag and wire 657 | n += proto.SizeVarint(uint64(s)) 658 | n += s 659 | case nil: 660 | default: 661 | panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) 662 | } 663 | return n 664 | } 665 | 666 | type ListenRequest struct { 667 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 668 | XXX_unrecognized []byte `json:"-"` 669 | XXX_sizecache int32 `json:"-"` 670 | } 671 | 672 | func (m *ListenRequest) Reset() { *m = ListenRequest{} } 673 | func (m *ListenRequest) String() string { return proto.CompactTextString(m) } 674 | func (*ListenRequest) ProtoMessage() {} 675 | func (*ListenRequest) Descriptor() ([]byte, []int) { 676 | return fileDescriptor_tl_update_1c97a93e1b72e269, []int{1} 677 | } 678 | func (m *ListenRequest) XXX_Unmarshal(b []byte) error { 679 | return xxx_messageInfo_ListenRequest.Unmarshal(m, b) 680 | } 681 | func (m *ListenRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 682 | return xxx_messageInfo_ListenRequest.Marshal(b, m, deterministic) 683 | } 684 | func (dst *ListenRequest) XXX_Merge(src proto.Message) { 685 | xxx_messageInfo_ListenRequest.Merge(dst, src) 686 | } 687 | func (m *ListenRequest) XXX_Size() int { 688 | return xxx_messageInfo_ListenRequest.Size(m) 689 | } 690 | func (m *ListenRequest) XXX_DiscardUnknown() { 691 | xxx_messageInfo_ListenRequest.DiscardUnknown(m) 692 | } 693 | 694 | var xxx_messageInfo_ListenRequest proto.InternalMessageInfo 695 | 696 | func init() { 697 | proto.RegisterType((*Update)(nil), "proxy.Update") 698 | proto.RegisterType((*ListenRequest)(nil), "proxy.ListenRequest") 699 | } 700 | 701 | // Reference imports to suppress errors if they are not otherwise used. 702 | var _ context.Context 703 | var _ grpc.ClientConn 704 | 705 | // This is a compile-time assertion to ensure that this generated file 706 | // is compatible with the grpc package it is being compiled against. 707 | const _ = grpc.SupportPackageIsVersion4 708 | 709 | // Client API for UpdateStreamer service 710 | 711 | type UpdateStreamerClient interface { 712 | ListenOnUpdates(ctx context.Context, in *ListenRequest, opts ...grpc.CallOption) (UpdateStreamer_ListenOnUpdatesClient, error) 713 | } 714 | 715 | type updateStreamerClient struct { 716 | cc *grpc.ClientConn 717 | } 718 | 719 | func NewUpdateStreamerClient(cc *grpc.ClientConn) UpdateStreamerClient { 720 | return &updateStreamerClient{cc} 721 | } 722 | 723 | func (c *updateStreamerClient) ListenOnUpdates(ctx context.Context, in *ListenRequest, opts ...grpc.CallOption) (UpdateStreamer_ListenOnUpdatesClient, error) { 724 | stream, err := grpc.NewClientStream(ctx, &_UpdateStreamer_serviceDesc.Streams[0], c.cc, "/proxy.UpdateStreamer/ListenOnUpdates", opts...) 725 | if err != nil { 726 | return nil, err 727 | } 728 | x := &updateStreamerListenOnUpdatesClient{stream} 729 | if err := x.ClientStream.SendMsg(in); err != nil { 730 | return nil, err 731 | } 732 | if err := x.ClientStream.CloseSend(); err != nil { 733 | return nil, err 734 | } 735 | return x, nil 736 | } 737 | 738 | type UpdateStreamer_ListenOnUpdatesClient interface { 739 | Recv() (*Update, error) 740 | grpc.ClientStream 741 | } 742 | 743 | type updateStreamerListenOnUpdatesClient struct { 744 | grpc.ClientStream 745 | } 746 | 747 | func (x *updateStreamerListenOnUpdatesClient) Recv() (*Update, error) { 748 | m := new(Update) 749 | if err := x.ClientStream.RecvMsg(m); err != nil { 750 | return nil, err 751 | } 752 | return m, nil 753 | } 754 | 755 | // Server API for UpdateStreamer service 756 | 757 | type UpdateStreamerServer interface { 758 | ListenOnUpdates(*ListenRequest, UpdateStreamer_ListenOnUpdatesServer) error 759 | } 760 | 761 | func RegisterUpdateStreamerServer(s *grpc.Server, srv UpdateStreamerServer) { 762 | s.RegisterService(&_UpdateStreamer_serviceDesc, srv) 763 | } 764 | 765 | func _UpdateStreamer_ListenOnUpdates_Handler(srv interface{}, stream grpc.ServerStream) error { 766 | m := new(ListenRequest) 767 | if err := stream.RecvMsg(m); err != nil { 768 | return err 769 | } 770 | return srv.(UpdateStreamerServer).ListenOnUpdates(m, &updateStreamerListenOnUpdatesServer{stream}) 771 | } 772 | 773 | type UpdateStreamer_ListenOnUpdatesServer interface { 774 | Send(*Update) error 775 | grpc.ServerStream 776 | } 777 | 778 | type updateStreamerListenOnUpdatesServer struct { 779 | grpc.ServerStream 780 | } 781 | 782 | func (x *updateStreamerListenOnUpdatesServer) Send(m *Update) error { 783 | return x.ServerStream.SendMsg(m) 784 | } 785 | 786 | var _UpdateStreamer_serviceDesc = grpc.ServiceDesc{ 787 | ServiceName: "proxy.UpdateStreamer", 788 | HandlerType: (*UpdateStreamerServer)(nil), 789 | Methods: []grpc.MethodDesc{}, 790 | Streams: []grpc.StreamDesc{ 791 | { 792 | StreamName: "ListenOnUpdates", 793 | Handler: _UpdateStreamer_ListenOnUpdates_Handler, 794 | ServerStreams: true, 795 | }, 796 | }, 797 | Metadata: "tl_update.proto", 798 | } 799 | 800 | func init() { proto.RegisterFile("tl_update.proto", fileDescriptor_tl_update_1c97a93e1b72e269) } 801 | 802 | var fileDescriptor_tl_update_1c97a93e1b72e269 = []byte{ 803 | // 570 bytes of a gzipped FileDescriptorProto 804 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x95, 0x5d, 0x6f, 0x12, 0x4f, 805 | 0x14, 0xc6, 0xa7, 0xff, 0xbf, 0x80, 0x0e, 0x50, 0xda, 0x09, 0xd1, 0x29, 0x89, 0x6f, 0x68, 0xd4, 806 | 0xc4, 0x64, 0x69, 0xea, 0x9d, 0x31, 0x31, 0x29, 0x35, 0x19, 0x23, 0x52, 0x1d, 0xb4, 0x5e, 0x19, 807 | 0x5d, 0xe0, 0x00, 0xab, 0xcb, 0x0c, 0xee, 0x0e, 0xa1, 0x7c, 0x1e, 0xbf, 0xa8, 0xe9, 0xec, 0xc0, 808 | 0xec, 0x2e, 0xb3, 0xdc, 0xc1, 0x79, 0x9e, 0xe7, 0x77, 0x5e, 0xd8, 0x2c, 0xb8, 0xa1, 0xc2, 0x1f, 809 | 0xcb, 0xc5, 0xd8, 0x57, 0xe0, 0x2d, 0x22, 0xa9, 0x24, 0x29, 0x2d, 0x22, 0x79, 0xbd, 0x6e, 0xbd, 810 | 0x9c, 0x06, 0x6a, 0xb6, 0x1c, 0x7a, 0x23, 0x39, 0xef, 0x8c, 0x7e, 0x49, 0x31, 0x8d, 0x41, 0xfe, 811 | 0xee, 0xcc, 0x95, 0x36, 0x75, 0xd4, 0x7a, 0x01, 0xb1, 0xa7, 0xc2, 0x24, 0xd3, 0xfe, 0x5b, 0xc3, 812 | 0xe5, 0xaf, 0x1a, 0x42, 0xde, 0xe2, 0x5a, 0xf2, 0x29, 0x1e, 0x28, 0x5f, 0x01, 0x3d, 0x78, 0x74, 813 | 0xf0, 0xa2, 0x7a, 0x76, 0xe2, 0x99, 0xa4, 0xf7, 0x29, 0x82, 0x71, 0xda, 0xc0, 0x10, 0xcf, 0x04, 814 | 0xc8, 0x67, 0x4c, 0x92, 0xef, 0x83, 0x99, 0x8c, 0xd4, 0x47, 0x88, 0x63, 0x7f, 0x0a, 0xf4, 0x3f, 815 | 0x8d, 0x79, 0xe8, 0xc0, 0xa4, 0x6d, 0x0c, 0x71, 0x47, 0x98, 0x7c, 0xc7, 0x77, 0x53, 0xd5, 0xee, 816 | 0xcc, 0xdf, 0x62, 0xff, 0xd7, 0xd8, 0x27, 0x45, 0xd8, 0x94, 0x95, 0x21, 0x5e, 0x00, 0x21, 0x6f, 817 | 0x70, 0x35, 0xa5, 0xd0, 0x5b, 0x9a, 0x49, 0x8b, 0x98, 0x0c, 0xf1, 0xb4, 0x9d, 0x9c, 0xe2, 0x8a, 818 | 0xd9, 0x9f, 0x96, 0x74, 0xb2, 0xe9, 0xba, 0x15, 0x43, 0x7c, 0x63, 0xcb, 0xad, 0x33, 0x00, 0xb1, 819 | 0x5d, 0xa7, 0xbc, 0x7f, 0x9d, 0x94, 0x35, 0xb7, 0x4e, 0x4a, 0x21, 0x7d, 0x7c, 0x6c, 0x3a, 0x5d, 820 | 0x04, 0x93, 0x09, 0x44, 0x20, 0x46, 0x40, 0x2b, 0x9a, 0xfc, 0xc0, 0x35, 0x9a, 0x75, 0x31, 0xc4, 821 | 0x77, 0xa3, 0x76, 0xdc, 0x54, 0x71, 0x10, 0x06, 0x23, 0xa0, 0xb7, 0x0b, 0xc7, 0xcd, 0x5b, 0xed, 822 | 0xb8, 0x79, 0x85, 0x7c, 0xc0, 0x47, 0x89, 0xd2, 0x87, 0xd5, 0xe6, 0x0e, 0x77, 0x34, 0xf8, 0xbe, 823 | 0x03, 0x6c, 0x4d, 0x0c, 0xf1, 0x9d, 0x20, 0x09, 0x70, 0x2b, 0xa9, 0x71, 0xf0, 0xc7, 0xa6, 0x18, 824 | 0x77, 0xa5, 0x50, 0x20, 0x54, 0x4c, 0xb1, 0xc6, 0x3e, 0x77, 0x60, 0x5d, 0x76, 0x86, 0xf8, 0x1e, 825 | 0x18, 0xf9, 0x86, 0x9b, 0x89, 0x7a, 0x01, 0x21, 0x28, 0xd8, 0xe8, 0xb4, 0xaa, 0x9b, 0x3c, 0x76, 826 | 0x34, 0xc9, 0x1a, 0x19, 0xe2, 0x4e, 0x00, 0x99, 0xe0, 0x93, 0xed, 0x5e, 0xef, 0xc4, 0x28, 0x5a, 827 | 0x2f, 0x14, 0x6c, 0xda, 0xd3, 0x9a, 0xa6, 0x3f, 0x73, 0x5f, 0x26, 0xef, 0x66, 0x88, 0x17, 0xa3, 828 | 0xc8, 0x39, 0xae, 0x27, 0x62, 0x77, 0xe6, 0x0b, 0x01, 0x21, 0xad, 0x6b, 0x76, 0xcb, 0xc1, 0x36, 829 | 0x0e, 0x86, 0x78, 0x36, 0x62, 0x67, 0x35, 0x05, 0xc3, 0xbe, 0x0a, 0x60, 0x15, 0xd3, 0xc3, 0xc2, 830 | 0x59, 0x1d, 0x6e, 0x3b, 0xab, 0x43, 0xb4, 0xc7, 0x36, 0xe2, 0x17, 0x29, 0x7b, 0x52, 0x4c, 0x69, 831 | 0xa3, 0xf0, 0xd8, 0x59, 0xa3, 0x3d, 0x76, 0xb6, 0x6e, 0x1f, 0xee, 0x9b, 0xdf, 0xd8, 0x68, 0xef, 832 | 0xc5, 0x50, 0x5e, 0xd3, 0xa3, 0xc2, 0x87, 0x3b, 0x6f, 0xb5, 0x0f, 0x77, 0x5e, 0x21, 0x3f, 0xf1, 833 | 0xbd, 0x1d, 0xe5, 0x72, 0xa9, 0x6e, 0xf8, 0xc7, 0x9a, 0xff, 0x74, 0x3f, 0x3f, 0xf1, 0x32, 0xc4, 834 | 0x8b, 0x30, 0xb6, 0x43, 0x1f, 0x56, 0xd9, 0xcb, 0x51, 0x52, 0xd8, 0x61, 0xc7, 0x6b, 0x3b, 0xec, 835 | 0x48, 0xe7, 0x15, 0x5c, 0xba, 0xf2, 0xc3, 0x25, 0xb4, 0x1b, 0xb8, 0xde, 0x0b, 0x62, 0x05, 0x82, 836 | 0xc3, 0x9f, 0x25, 0xc4, 0xea, 0xac, 0x87, 0x0f, 0xcd, 0x3b, 0x48, 0x45, 0xe0, 0xcf, 0x21, 0x22, 837 | 0xaf, 0x71, 0x23, 0xb1, 0x5c, 0x8a, 0xcd, 0xdb, 0xae, 0xe9, 0xe9, 0x3f, 0x24, 0x2f, 0x13, 0x6d, 838 | 0xd5, 0x4d, 0x35, 0x71, 0xb5, 0xd1, 0xe9, 0xc1, 0xb0, 0xac, 0xa7, 0x7c, 0xf5, 0x2f, 0x00, 0x00, 839 | 0xff, 0xff, 0x7c, 0xb3, 0xe8, 0xda, 0xd2, 0x06, 0x00, 0x00, 840 | } 841 | -------------------------------------------------------------------------------- /proxy/tl_update.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package proxy; 3 | import "github.com/cjongseok/mtproto/types.tl.proto"; 4 | 5 | message Update { 6 | oneof Value { 7 | mtproto.PredUpdatesState UpdatesState = 1; 8 | mtproto.PredUpdateShortMessage UpdateShortMessage = 2; 9 | mtproto.PredUpdateShortChatMessage UpdateShortChatMessage = 3; 10 | mtproto.PredUpdateShort UpdateShort = 4; 11 | mtproto.PredUpdates Updates = 5; 12 | mtproto.PredUpdateShortSentMessage UpdateShortSentMessage = 6; 13 | mtproto.PredUpdatesDifference UpdatesDifference = 7; 14 | mtproto.PredUpdatesDifferenceSlice UpdatesDifferenceSlice = 8; 15 | mtproto.PredUpdateNewMessage UpdateNewMessage = 9; 16 | mtproto.PredUpdateReadMessagesContents UpdateReadMessagesContents = 10; 17 | mtproto.PredUpdateDeleteMessages UpdateDeleteMessages = 11; 18 | mtproto.PredUpdateNewEncryptedMessage UpdateNewEncryptedMessage = 12; 19 | mtproto.PredUpdateChannel UpdateChannel = 13; 20 | mtproto.PredUpdateChannelMessageViews UpdateChannelMessageViews = 14; 21 | mtproto.PredUpdateChannelTooLong UpdateChannelTooLong = 15; 22 | mtproto.PredUpdateReadChannelInbox UpdateReadChannelInbox = 16; 23 | mtproto.PredUpdateReadChannelOutbox UpdateReadChannelOutbox = 17; 24 | mtproto.PredUpdateNewChannelMessage UpdateNewChannelMessage = 18; 25 | } 26 | } 27 | 28 | message ListenRequest {} 29 | 30 | service UpdateStreamer { 31 | rpc ListenOnUpdates (ListenRequest) returns (stream Update) {} 32 | } 33 | -------------------------------------------------------------------------------- /py/README.md: -------------------------------------------------------------------------------- 1 | Python client 2 | === 3 | It is a mtproto proxy Python client. 4 | 5 | Quick Start 6 | --- 7 | ```bash 8 | # If you don't have a mtproto key, generate it first. 9 | # At mtproto home directory, 10 | go run tools/keygen/main.go 11 | 12 | # Move the key inside secrets directory 13 | cd py 14 | mkdir secrets 15 | mv ../key.mtproto ./secrets 16 | 17 | # Install python grpc library 18 | pip install grpcio-tools 19 | 20 | # Spawn a proxy server at port 11011 21 | docker run \ 22 | -p 11011:11011 \ 23 | -v ${PWD}/secrets:/opt \ 24 | cjongseok/mtprotod start \ 25 | --port 11011 \ 26 | --addr \ 27 | --apiid \ 28 | --apihash \ 29 | --phone \ 30 | --port \ 31 | --secrets /opt/key.mtproto 32 | 33 | # GetDialogs() over Python client 34 | python3 main.py 35 | ``` 36 | 37 | Files 38 | --- 39 | * main.py: Python client excample fetching Dialogs 40 | * tl_pb2.py: Generated by [mtproto/compiler/build_py.sh](https://github.com/cjongseok/mtproto/blob/master/compiler/build_py.sh) 41 | * tl_b2_grpc.py: Generated by [mtproto/compiler/build_py.sh](https://github.com/cjongseok/mtproto/blob/master/compiler/build_py.sh) 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /py/main.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import grpc 4 | 5 | import tl_pb2 6 | import tl_pb2_grpc 7 | 8 | 9 | def run(): 10 | channel = grpc.insecure_channel('localhost:11011') 11 | stub = tl_pb2_grpc.MtprotoStub(channel) 12 | peer = tl_pb2.TypeInputPeer(InputPeerEmpty = tl_pb2.PredInputPeerEmpty()) 13 | response = stub.MessagesGetDialogs(tl_pb2.ReqMessagesGetDialogs(OffsetPeer = peer)) 14 | print("Greeter client received: " + str(response)) 15 | 16 | 17 | if __name__ == '__main__': 18 | run() 19 | -------------------------------------------------------------------------------- /tl.go: -------------------------------------------------------------------------------- 1 | package mtproto 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "crypto/rand" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "errors" 10 | "fmt" 11 | "github.com/cjongseok/slog" 12 | "math" 13 | "math/big" 14 | "reflect" 15 | "runtime" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | const ( 21 | layer = 71 22 | 23 | // https://core.telegram.org/schema/mtproto 24 | //crc_vector = 0x1cb5c415 25 | crc_resPQ = 0x05162463 26 | crc_p_q_inner_data = 0x83c95aec 27 | crc_server_DH_params_fail = 0x79cb045d 28 | crc_server_DH_params_ok = 0xd0e8075c 29 | crc_server_DH_inner_data = 0xb5890dba 30 | crc_client_DH_inner_data = 0x6643b654 31 | crc_dh_gen_ok = 0x3bcbf734 32 | crc_dh_gen_retry = 0x46dc1fb9 33 | crc_dh_gen_fail = 0xa69dae02 34 | crc_rpc_result = 0xf35c6d01 35 | crc_rpc_error = 0x2144ca19 36 | crc_rpc_answer_unknown = 0x5e2ad36e 37 | crc_rpc_answer_dropped_running = 0xcd78e586 38 | crc_rpc_answer_dropped = 0xa43ad8b7 39 | crc_future_salt = 0x0949d9dc 40 | crc_future_salts = 0xae500895 41 | crc_pong = 0x347773c5 42 | crc_destroy_session_ok = 0xe22045fc 43 | crc_destroy_session_none = 0x62d350c9 44 | crc_new_session_created = 0x9ec20908 45 | crc_msg_container = 0x73f1f8dc 46 | crc_msg_copy = 0xe06046b2 47 | crc_gzip_packed = 0x3072cfa1 48 | crc_msgs_ack = 0x62d6b459 49 | crc_bad_msg_notification = 0xa7eff811 50 | crc_bad_server_salt = 0xedab447b 51 | crc_msg_resend_req = 0x7d861a08 52 | crc_msgs_state_req = 0xda69fb52 53 | crc_msgs_state_info = 0x04deb57d 54 | crc_msgs_all_info = 0x8cc0d131 55 | crc_msg_detailed_info = 0x276d3ec6 56 | crc_msg_new_detailed_info = 0x809db6df 57 | crc_req_pq = 0x60469778 58 | crc_req_DH_params = 0xd712e4be 59 | crc_set_client_DH_params = 0xf5045f1f 60 | crc_rpc_drop_answer = 0x58e4a740 61 | crc_get_future_salts = 0xb921bd04 62 | crc_ping = 0x7abe77ec 63 | crc_ping_delay_disconnect = 0xf3427b8c 64 | crc_destroy_session = 0xe7512126 65 | crc_http_wait = 0x9299359f 66 | ) 67 | 68 | type TL interface { 69 | encode() []byte 70 | } 71 | 72 | type RemoteProcedureCall interface { 73 | InvokeBlocked(msg TL) (interface{}, error) 74 | } 75 | 76 | type Predicate interface { 77 | ToType() TL 78 | } 79 | 80 | type RPCaller struct { 81 | RPC RemoteProcedureCall 82 | } 83 | 84 | type EncodeBuf struct { 85 | buf []byte 86 | } 87 | 88 | type DecodeBuf struct { 89 | buf []byte 90 | off int 91 | size int 92 | err error 93 | } 94 | 95 | type TL_msg_container struct { 96 | Items []TL_MT_message 97 | } 98 | 99 | type TL_MT_message struct { 100 | Msg_id int64 101 | Seq_no int32 102 | Size int32 103 | Data interface{} 104 | } 105 | 106 | type TL_req_pq struct { 107 | nonce []byte 108 | } 109 | 110 | type TL_p_q_inner_data struct { 111 | pq *big.Int 112 | p *big.Int 113 | q *big.Int 114 | nonce []byte 115 | server_nonce []byte 116 | new_nonce []byte 117 | } 118 | type TL_req_DH_params struct { 119 | nonce []byte 120 | server_nonce []byte 121 | p *big.Int 122 | q *big.Int 123 | fp uint64 124 | encdata []byte 125 | } 126 | type TL_client_DH_inner_data struct { 127 | nonce []byte 128 | server_nonce []byte 129 | retry int64 130 | g_b *big.Int 131 | } 132 | type TL_set_client_DH_params struct { 133 | nonce []byte 134 | server_nonce []byte 135 | encdata []byte 136 | } 137 | type TL_resPQ struct { 138 | nonce []byte 139 | server_nonce []byte 140 | pq *big.Int 141 | fingerprints []int64 142 | } 143 | 144 | type TL_server_DH_params_ok struct { 145 | nonce []byte 146 | server_nonce []byte 147 | encrypted_answer []byte 148 | } 149 | 150 | type TL_server_DH_inner_data struct { 151 | nonce []byte 152 | server_nonce []byte 153 | g int32 154 | dh_prime *big.Int 155 | g_a *big.Int 156 | server_time int32 157 | } 158 | 159 | type TL_new_session_created struct { 160 | first_msg_id int64 161 | unique_id int64 162 | server_salt []byte 163 | } 164 | 165 | type TL_bad_server_salt struct { 166 | bad_msg_id int64 167 | bad_msg_seqno int32 168 | error_code int32 169 | new_server_salt []byte 170 | } 171 | 172 | type TL_crc_bad_msg_notification struct { 173 | bad_msg_id int64 174 | bad_msg_seqno int32 175 | error_code int32 176 | } 177 | 178 | type TL_msgs_ack struct { 179 | msgIds []int64 180 | } 181 | 182 | type TL_rpc_result struct { 183 | req_msg_id int64 184 | Obj interface{} 185 | } 186 | 187 | type TL_rpc_error struct { 188 | error_code int32 189 | error_message string 190 | } 191 | 192 | type TL_dh_gen_ok struct { 193 | nonce []byte 194 | server_nonce []byte 195 | new_nonce_hash1 []byte 196 | } 197 | 198 | type TL_ping struct { 199 | ping_id int64 200 | } 201 | 202 | type TL_pong struct { 203 | msg_id int64 204 | ping_id int64 205 | } 206 | 207 | // Encoders 208 | func GenerateNonce(size int) []byte { 209 | b := make([]byte, size) 210 | _, _ = rand.Read(b) 211 | return b 212 | } 213 | 214 | func GenerateMessageId() int64 { 215 | const nano = 1000 * 1000 * 1000 216 | //FIXME: Windows system clock has time resolution issue. https://github.com/golang/go/issues/17696 217 | //Remove the sleep when the issue is resolved. 218 | if strings.Contains(runtime.GOOS, "windows") { 219 | time.Sleep(2 * time.Millisecond) 220 | } 221 | unixnano := time.Now().UnixNano() 222 | 223 | return ((unixnano / nano) << 32) | ((unixnano % nano) & -4) 224 | } 225 | 226 | func NewEncodeBuf(cap int) *EncodeBuf { 227 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 228 | slog.Logln("Encode::NewBuf::", "cap=", cap) 229 | } 230 | return &EncodeBuf{make([]byte, 0, cap)} 231 | } 232 | 233 | func (e *EncodeBuf) Int(s int32) { 234 | e.buf = append(e.buf, 0, 0, 0, 0) 235 | binary.LittleEndian.PutUint32(e.buf[len(e.buf)-4:], uint32(s)) 236 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 237 | slog.Logln("Encode::Int::", s) 238 | } 239 | } 240 | 241 | func (e *EncodeBuf) UInt(s uint32) { 242 | e.buf = append(e.buf, 0, 0, 0, 0) 243 | binary.LittleEndian.PutUint32(e.buf[len(e.buf)-4:], s) 244 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 245 | slog.Logf("Encode::UInt::", "%d(0x%x)", s, s) 246 | } 247 | } 248 | 249 | func (e *EncodeBuf) Long(s int64) { 250 | e.buf = append(e.buf, 0, 0, 0, 0, 0, 0, 0, 0) 251 | binary.LittleEndian.PutUint64(e.buf[len(e.buf)-8:], uint64(s)) 252 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 253 | slog.Logln("Encode::Long::", s) 254 | } 255 | } 256 | 257 | func (e *EncodeBuf) Double(s float64) { 258 | e.buf = append(e.buf, 0, 0, 0, 0, 0, 0, 0, 0) 259 | binary.LittleEndian.PutUint64(e.buf[len(e.buf)-8:], math.Float64bits(s)) 260 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 261 | slog.Logln("Encode::Double::", s) 262 | } 263 | } 264 | 265 | func (e *EncodeBuf) String(s string) { 266 | e.StringBytes([]byte(s)) 267 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 268 | slog.Logln("Encode::String::", s) 269 | } 270 | } 271 | 272 | func (e *EncodeBuf) BigInt(s *big.Int) { 273 | e.StringBytes(s.Bytes()) 274 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 275 | slog.Logln("Encode::BigInt::", s) 276 | } 277 | } 278 | 279 | func (e *EncodeBuf) StringBytes(s []byte) { 280 | var res []byte 281 | size := len(s) 282 | if size < 254 { 283 | nl := 1 + size + (4-(size+1)%4)&3 284 | res = make([]byte, nl) 285 | res[0] = byte(size) 286 | copy(res[1:], s) 287 | 288 | } else { 289 | nl := 4 + size + (4-size%4)&3 290 | res = make([]byte, nl) 291 | binary.LittleEndian.PutUint32(res, uint32(size<<8|254)) 292 | copy(res[4:], s) 293 | 294 | } 295 | e.buf = append(e.buf, res...) 296 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 297 | slog.Logln("Encode::StringBytes::", s) 298 | } 299 | } 300 | 301 | func (e *EncodeBuf) Bytes(s []byte) { 302 | e.buf = append(e.buf, s...) 303 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 304 | slog.Logln("Encode::Bytes::", s) 305 | } 306 | } 307 | 308 | func (e *EncodeBuf) VectorInt(v []int32) { 309 | x := make([]byte, 4+4+len(v)*4) 310 | binary.LittleEndian.PutUint32(x, crc_vector) 311 | binary.LittleEndian.PutUint32(x[4:], uint32(len(v))) 312 | i := 8 313 | for _, v := range v { 314 | binary.LittleEndian.PutUint32(x[i:], uint32(v)) 315 | i += 4 316 | } 317 | e.buf = append(e.buf, x...) 318 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 319 | slog.Logln("Encode::VectorInt::", v) 320 | } 321 | } 322 | 323 | func (e *EncodeBuf) VectorLong(v []int64) { 324 | x := make([]byte, 4+4+len(v)*8) 325 | binary.LittleEndian.PutUint32(x, crc_vector) 326 | binary.LittleEndian.PutUint32(x[4:], uint32(len(v))) 327 | i := 8 328 | for _, v := range v { 329 | binary.LittleEndian.PutUint64(x[i:], uint64(v)) 330 | i += 8 331 | } 332 | e.buf = append(e.buf, x...) 333 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 334 | slog.Logln("Encode::VectorLong::", v) 335 | } 336 | } 337 | 338 | func (e *EncodeBuf) VectorString(v []string) { 339 | x := make([]byte, 8) 340 | binary.LittleEndian.PutUint32(x, crc_vector) 341 | binary.LittleEndian.PutUint32(x[4:], uint32(len(v))) 342 | e.buf = append(e.buf, x...) 343 | for _, v := range v { 344 | e.String(v) 345 | } 346 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 347 | slog.Logln("Encode::VectorString::", v) 348 | } 349 | } 350 | 351 | func (e *EncodeBuf) Vector(v []TL) { 352 | x := make([]byte, 8) 353 | binary.LittleEndian.PutUint32(x, crc_vector) 354 | binary.LittleEndian.PutUint32(x[4:], uint32(len(v))) 355 | e.buf = append(e.buf, x...) 356 | for _, v := range v { 357 | e.buf = append(e.buf, v.encode()...) 358 | } 359 | if __debug&DEBUG_LEVEL_ENCODE_DETAILS != 0 { 360 | slog.Logln("Encode::Vector::", v) 361 | } 362 | } 363 | 364 | func (e TL_msg_container) encode() []byte { return nil } 365 | func (e TL_resPQ) encode() []byte { return nil } 366 | func (e TL_server_DH_params_ok) encode() []byte { return nil } 367 | func (e TL_server_DH_inner_data) encode() []byte { return nil } 368 | func (e TL_dh_gen_ok) encode() []byte { return nil } 369 | func (e TL_rpc_result) encode() []byte { return nil } 370 | func (e TL_rpc_error) encode() []byte { return nil } 371 | func (e TL_new_session_created) encode() []byte { return nil } 372 | func (e TL_bad_server_salt) encode() []byte { return nil } 373 | func (e TL_crc_bad_msg_notification) encode() []byte { return nil } 374 | 375 | func (e TL_req_pq) encode() []byte { 376 | x := NewEncodeBuf(20) 377 | x.UInt(crc_req_pq) 378 | x.Bytes(e.nonce) 379 | return x.buf 380 | } 381 | 382 | func (e TL_p_q_inner_data) encode() []byte { 383 | x := NewEncodeBuf(256) 384 | x.UInt(crc_p_q_inner_data) 385 | x.BigInt(e.pq) 386 | x.BigInt(e.p) 387 | x.BigInt(e.q) 388 | x.Bytes(e.nonce) 389 | x.Bytes(e.server_nonce) 390 | x.Bytes(e.new_nonce) 391 | return x.buf 392 | } 393 | 394 | func (e TL_req_DH_params) encode() []byte { 395 | x := NewEncodeBuf(512) 396 | x.UInt(crc_req_DH_params) 397 | x.Bytes(e.nonce) 398 | x.Bytes(e.server_nonce) 399 | x.BigInt(e.p) 400 | x.BigInt(e.q) 401 | x.Long(int64(e.fp)) 402 | x.StringBytes(e.encdata) 403 | return x.buf 404 | } 405 | 406 | func (e TL_client_DH_inner_data) encode() []byte { 407 | x := NewEncodeBuf(512) 408 | x.UInt(crc_client_DH_inner_data) 409 | x.Bytes(e.nonce) 410 | x.Bytes(e.server_nonce) 411 | x.Long(e.retry) 412 | x.BigInt(e.g_b) 413 | return x.buf 414 | } 415 | 416 | func (e TL_set_client_DH_params) encode() []byte { 417 | x := NewEncodeBuf(256) 418 | x.UInt(crc_set_client_DH_params) 419 | x.Bytes(e.nonce) 420 | x.Bytes(e.server_nonce) 421 | x.StringBytes(e.encdata) 422 | return x.buf 423 | } 424 | 425 | func (e TL_ping) encode() []byte { 426 | x := NewEncodeBuf(32) 427 | x.UInt(crc_ping) 428 | x.Long(e.ping_id) 429 | return x.buf 430 | } 431 | 432 | func (e TL_pong) encode() []byte { 433 | x := NewEncodeBuf(32) 434 | x.UInt(crc_pong) 435 | x.Long(e.msg_id) 436 | x.Long(e.ping_id) 437 | return x.buf 438 | } 439 | 440 | func (e TL_msgs_ack) encode() []byte { 441 | x := NewEncodeBuf(64) 442 | x.UInt(crc_msgs_ack) 443 | x.VectorLong(e.msgIds) 444 | return x.buf 445 | } 446 | 447 | func (e *EncodeBuf) FlaggedLong(flags, f int32, s int64) { 448 | bit := int32(1 << uint(f)) 449 | if flags&bit == 0 { 450 | return 451 | } 452 | e.Long(s) 453 | } 454 | func (e *EncodeBuf) FlaggedDouble(flags, f int32, s float64) { 455 | bit := int32(1 << uint(f)) 456 | if flags&bit == 0 { 457 | return 458 | } 459 | e.Double(s) 460 | } 461 | func (e *EncodeBuf) FlaggedInt(flags, f int32, s int32) { 462 | bit := int32(1 << uint(f)) 463 | if flags&bit == 0 { 464 | return 465 | } 466 | e.Int(s) 467 | } 468 | func (e *EncodeBuf) FlaggedString(flags, f int32, s string) { 469 | bit := int32(1 << uint(f)) 470 | if flags&bit == 0 { 471 | return 472 | } 473 | e.String(s) 474 | } 475 | func (e *EncodeBuf) FlaggedVector(flags, f int32, v []TL) { 476 | bit := int32(1 << uint(f)) 477 | if flags&bit == 0 { 478 | return 479 | } 480 | e.Vector(v) 481 | } 482 | func (e *EncodeBuf) FlaggedObject(flags, f int32, o TL) { 483 | bit := int32(1 << uint(f)) 484 | if flags&bit == 0 { 485 | return 486 | } 487 | e.Bytes(o.encode()) 488 | } 489 | func (e *EncodeBuf) FlaggedStringBytes(flags, f int32, s []byte) { 490 | bit := int32(1 << uint(f)) 491 | if flags&bit == 0 { 492 | return 493 | } 494 | e.StringBytes(s) 495 | } 496 | func (e *EncodeBuf) FlaggedVectorInt(flags, f int32, v []int32) { 497 | bit := int32(1 << uint(f)) 498 | if flags&bit == 0 { 499 | return 500 | } 501 | e.VectorInt(v) 502 | } 503 | func (e *EncodeBuf) FlaggedVectorLong(flags, f int32, v []int64) { 504 | bit := int32(1 << uint(f)) 505 | if flags&bit == 0 { 506 | return 507 | } 508 | e.VectorLong(v) 509 | } 510 | func (e *EncodeBuf) FlaggedVectorString(flags, f int32, v []string) { 511 | bit := int32(1 << uint(f)) 512 | if flags&bit == 0 { 513 | return 514 | } 515 | e.VectorString(v) 516 | } 517 | 518 | func toTLslice(slice interface{}) []TL { 519 | if reflect.TypeOf(slice).Kind() != reflect.Slice { 520 | return nil 521 | } 522 | s := reflect.ValueOf(slice) 523 | if s.Len() < 1 { 524 | return nil 525 | } 526 | switch s.Index(0).Interface().(type) { 527 | case TL: 528 | tlslice := make([]TL, s.Len()) 529 | for i := 0; i < s.Len(); i++ { 530 | tlslice[i] = s.Index(i).Interface().(TL) 531 | } 532 | return tlslice 533 | default: 534 | return nil 535 | } 536 | } 537 | 538 | // Decoders 539 | func NewDecodeBuf(b []byte) *DecodeBuf { 540 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 541 | slog.Logln("Decode::NewBuf::", "bytes = ", b) 542 | } 543 | return &DecodeBuf{b, 0, len(b), nil} 544 | } 545 | 546 | func (m *DecodeBuf) Long() int64 { 547 | if m.err != nil { 548 | return 0 549 | } 550 | if m.off+8 > m.size { 551 | m.err = errors.New("DecodeLong") 552 | return 0 553 | } 554 | x := int64(binary.LittleEndian.Uint64(m.buf[m.off : m.off+8])) 555 | m.off += 8 556 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 557 | slog.Logln("Decode::Long::", x) 558 | } 559 | return x 560 | } 561 | 562 | func (m *DecodeBuf) Double() float64 { 563 | if m.err != nil { 564 | return 0 565 | } 566 | if m.off+8 > m.size { 567 | m.err = errors.New("DecodeDouble") 568 | return 0 569 | } 570 | x := math.Float64frombits(binary.LittleEndian.Uint64(m.buf[m.off : m.off+8])) 571 | m.off += 8 572 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 573 | slog.Logln("Decode::Double::", x) 574 | } 575 | return x 576 | } 577 | 578 | func (m *DecodeBuf) Int() int32 { 579 | if m.err != nil { 580 | return 0 581 | } 582 | if m.off+4 > m.size { 583 | m.err = errors.New("DecodeInt") 584 | return 0 585 | } 586 | x := binary.LittleEndian.Uint32(m.buf[m.off : m.off+4]) 587 | m.off += 4 588 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 589 | slog.Logln("Decode::Int::", x) 590 | } 591 | return int32(x) 592 | } 593 | 594 | func (m *DecodeBuf) UInt() uint32 { 595 | if m.err != nil { 596 | return 0 597 | } 598 | if m.off+4 > m.size { 599 | m.err = errors.New("DecodeUInt") 600 | return 0 601 | } 602 | x := binary.LittleEndian.Uint32(m.buf[m.off : m.off+4]) 603 | m.off += 4 604 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 605 | slog.Logf("Decode::UInt::", "%d(0x%x)", x, x) 606 | } 607 | return x 608 | } 609 | 610 | func (m *DecodeBuf) Bytes(size int) []byte { 611 | if m.err != nil { 612 | return nil 613 | } 614 | if m.off+size > m.size { 615 | m.err = errors.New("DecodeBytes") 616 | return nil 617 | } 618 | x := make([]byte, size) 619 | copy(x, m.buf[m.off:m.off+size]) 620 | m.off += size 621 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 622 | if len(x) > 10 { 623 | slog.Logln("Decode::Bytes::", len(x), x[:10], " ...") 624 | } else { 625 | slog.Logln("Decode::Bytes::", len(x), x) 626 | } 627 | 628 | } 629 | return x 630 | } 631 | 632 | func (m *DecodeBuf) StringBytes() []byte { 633 | if m.err != nil { 634 | return nil 635 | } 636 | var size, padding int 637 | 638 | if m.off+1 > m.size { 639 | m.err = errors.New("DecodeStringBytes") 640 | return nil 641 | } 642 | size = int(m.buf[m.off]) 643 | m.off++ 644 | padding = (4 - ((size + 1) % 4)) & 3 645 | if size == 254 { 646 | if m.off+3 > m.size { 647 | m.err = errors.New("DecodeStringBytes") 648 | return nil 649 | } 650 | size = int(m.buf[m.off]) | int(m.buf[m.off+1])<<8 | int(m.buf[m.off+2])<<16 651 | m.off += 3 652 | padding = (4 - size%4) & 3 653 | } 654 | 655 | if m.off+size > m.size { 656 | m.err = errors.New("DecodeStringBytes: Wrong size") 657 | return nil 658 | } 659 | x := make([]byte, size) 660 | copy(x, m.buf[m.off:m.off+size]) 661 | m.off += size 662 | 663 | if m.off+padding > m.size { 664 | m.err = errors.New("DecodeStringBytes: Wrong padding") 665 | return nil 666 | } 667 | m.off += padding 668 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 669 | if len(x) > 10 { 670 | slog.Logln("Decode::StringBytes::", len(x), x[:10], " ...") 671 | } else { 672 | slog.Logln("Decode::StringBytes::", len(x), x) 673 | } 674 | 675 | } 676 | return x 677 | } 678 | 679 | func (m *DecodeBuf) String() string { 680 | b := m.StringBytes() 681 | if m.err != nil { 682 | return "" 683 | } 684 | x := string(b) 685 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 686 | slog.Logln("Decode::String::", x) 687 | } 688 | return x 689 | } 690 | 691 | func (m *DecodeBuf) BigInt() *big.Int { 692 | b := m.StringBytes() 693 | if m.err != nil { 694 | return nil 695 | } 696 | y := make([]byte, len(b)+1) 697 | y[0] = 0 698 | copy(y[1:], b) 699 | x := new(big.Int).SetBytes(y) 700 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 701 | slog.Logln("Decode::BigInt::", x) 702 | } 703 | return x 704 | } 705 | 706 | func (m *DecodeBuf) VectorInt() []int32 { 707 | constructor := m.UInt() 708 | if m.err != nil { 709 | return nil 710 | } 711 | if constructor != crc_vector { 712 | m.err = fmt.Errorf("DecodeVectorInt: Wrong constructor (0x%08x)", constructor) 713 | return nil 714 | } 715 | size := m.Int() 716 | if m.err != nil { 717 | return nil 718 | } 719 | if size < 0 { 720 | m.err = errors.New("DecodeVectorInt: Wrong size") 721 | return nil 722 | } 723 | x := make([]int32, size) 724 | i := int32(0) 725 | for i < size { 726 | y := m.Int() 727 | if m.err != nil { 728 | return nil 729 | } 730 | x[i] = y 731 | i++ 732 | } 733 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 734 | slog.Logln("Decode::VectorInt::", x) 735 | } 736 | return x 737 | } 738 | 739 | func (m *DecodeBuf) VectorLong() []int64 { 740 | constructor := m.UInt() 741 | if m.err != nil { 742 | return nil 743 | } 744 | if constructor != crc_vector { 745 | m.err = fmt.Errorf("DecodeVectorLong: Wrong constructor (0x%08x)", constructor) 746 | return nil 747 | } 748 | size := m.Int() 749 | if m.err != nil { 750 | return nil 751 | } 752 | if size < 0 { 753 | m.err = errors.New("DecodeVectorLong: Wrong size") 754 | return nil 755 | } 756 | x := make([]int64, size) 757 | i := int32(0) 758 | for i < size { 759 | y := m.Long() 760 | if m.err != nil { 761 | return nil 762 | } 763 | x[i] = y 764 | i++ 765 | } 766 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 767 | slog.Logln("Decode::VectorLong::", x) 768 | } 769 | return x 770 | } 771 | 772 | func (m *DecodeBuf) VectorString() []string { 773 | constructor := m.UInt() 774 | if m.err != nil { 775 | return nil 776 | } 777 | if constructor != crc_vector { 778 | m.err = fmt.Errorf("DecodeVectorString: Wrong constructor (0x%08x)", constructor) 779 | return nil 780 | } 781 | size := m.Int() 782 | if m.err != nil { 783 | return nil 784 | } 785 | if size < 0 { 786 | m.err = errors.New("DecodeVectorString: Wrong size") 787 | return nil 788 | } 789 | x := make([]string, size) 790 | i := int32(0) 791 | for i < size { 792 | y := m.String() 793 | if m.err != nil { 794 | return nil 795 | } 796 | x[i] = y 797 | i++ 798 | } 799 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 800 | slog.Logln("Decode::VectorString::", x) 801 | } 802 | return x 803 | } 804 | 805 | func (m *DecodeBuf) Bool() bool { 806 | constructor := m.UInt() 807 | if m.err != nil { 808 | return false 809 | } 810 | switch constructor { 811 | case crc_boolFalse: 812 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 813 | slog.Logln("Decode::Bool::", false) 814 | } 815 | return false 816 | case crc_boolTrue: 817 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 818 | slog.Logln("Decode::Bool::", true) 819 | } 820 | return true 821 | } 822 | return false 823 | } 824 | 825 | func (m *DecodeBuf) TL_Vector() []TL { 826 | //constructor := m.UInt() 827 | //if m.err != nil { 828 | // return nil 829 | //} 830 | //if constructor != crc_vector { 831 | // m.err = fmt.Errorf("DecodeTL_Vector: Wrong constructor (0x%08x)", constructor) 832 | // return nil 833 | //} 834 | //slog.Logln("byte len=", len(m.buf)) 835 | //slog.Logln("m.size=", m.size) 836 | //slog.Logln(hex.EncodeToString(m.buf)) 837 | //xx := m.UInt() 838 | //slog.Logln("x=", xx) 839 | //size := m.Int() 840 | //slog.Logln("size=", size) 841 | //if m.err != nil { 842 | // return nil 843 | //} 844 | //if size < 0 { 845 | // m.err = errors.New("DecodeTL_Vector: Wrong size") 846 | // return nil 847 | //} 848 | //x := make([]TL, size) 849 | //i := int32(0) 850 | //for i < size { 851 | // y := m.Object() 852 | // if m.err != nil { 853 | // return nil 854 | // } 855 | // x[i] = y 856 | // i++ 857 | //} 858 | //if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 859 | // slog.Logln("Decode::Vector::", x) 860 | //} 861 | //return x 862 | m.err = fmt.Errorf("DecodeTL_Vector: NOT SUPPORTED YET") 863 | return nil 864 | } 865 | 866 | func (m *DecodeBuf) Vector() []TL { 867 | constructor := m.UInt() 868 | if m.err != nil { 869 | return nil 870 | } 871 | if constructor != crc_vector { 872 | m.err = fmt.Errorf("DecodeVector: Wrong constructor (0x%08x)", constructor) 873 | return nil 874 | } 875 | size := m.Int() 876 | if m.err != nil { 877 | return nil 878 | } 879 | if size < 0 { 880 | m.err = errors.New("DecodeVector: Wrong size") 881 | return nil 882 | } 883 | x := make([]TL, size) 884 | i := int32(0) 885 | for i < size { 886 | y := m.Object() 887 | if m.err != nil { 888 | return nil 889 | } 890 | x[i] = y 891 | i++ 892 | } 893 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 894 | slog.Logln("Decode::Vector::", x) 895 | } 896 | return x 897 | } 898 | 899 | func (m *DecodeBuf) Object() (r TL) { 900 | constructor := m.UInt() 901 | if m.err != nil { 902 | return nil 903 | } 904 | switch constructor { 905 | 906 | case crc_resPQ: 907 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 908 | slog.Logln("reqPQ", constructor) 909 | } 910 | r = TL_resPQ{m.Bytes(16), m.Bytes(16), m.BigInt(), m.VectorLong()} 911 | 912 | case crc_server_DH_params_ok: 913 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 914 | slog.Logln("server_DH_params_ok", constructor) 915 | } 916 | r = TL_server_DH_params_ok{m.Bytes(16), m.Bytes(16), m.StringBytes()} 917 | 918 | case crc_server_DH_inner_data: 919 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 920 | slog.Logln("server_DH_inner_data", constructor) 921 | } 922 | r = TL_server_DH_inner_data{ 923 | m.Bytes(16), m.Bytes(16), m.Int(), 924 | m.BigInt(), m.BigInt(), m.Int(), 925 | } 926 | 927 | case crc_dh_gen_ok: 928 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 929 | slog.Logln("dh_gen_ok", constructor) 930 | } 931 | r = TL_dh_gen_ok{m.Bytes(16), m.Bytes(16), m.Bytes(16)} 932 | 933 | case crc_ping: 934 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 935 | slog.Logln("ping", constructor) 936 | } 937 | r = TL_ping{m.Long()} 938 | 939 | case crc_pong: 940 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 941 | slog.Logln("pong", constructor) 942 | } 943 | r = TL_pong{m.Long(), m.Long()} 944 | 945 | case crc_msg_container: 946 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 947 | slog.Logln("msg_container", constructor) 948 | } 949 | size := m.Int() 950 | arr := make([]TL_MT_message, size) 951 | for i := int32(0); i < size; i++ { 952 | arr[i] = TL_MT_message{m.Long(), m.Int(), m.Int(), m.Object()} 953 | //slog.Logln(constructor, arr[i]) 954 | if m.err != nil { 955 | slog.Logln(m.err.Error()) 956 | return nil 957 | } 958 | } 959 | r = TL_msg_container{arr} 960 | 961 | case crc_rpc_result: 962 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 963 | slog.Logln("rpc_result", constructor) 964 | } 965 | r = TL_rpc_result{m.Long(), m.Object()} 966 | 967 | case crc_rpc_error: 968 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 969 | slog.Logln("rpc_error", constructor) 970 | } 971 | r = TL_rpc_error{m.Int(), m.String()} 972 | 973 | case crc_new_session_created: 974 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 975 | slog.Logln("new_session_created", constructor) 976 | } 977 | r = TL_new_session_created{m.Long(), m.Long(), m.Bytes(8)} 978 | 979 | case crc_bad_server_salt: 980 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 981 | slog.Logln("bad_server_salt", constructor) 982 | } 983 | r = TL_bad_server_salt{m.Long(), m.Int(), m.Int(), m.Bytes(8)} 984 | 985 | case crc_bad_msg_notification: 986 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 987 | slog.Logln("bad_msg_notification", constructor) 988 | } 989 | r = TL_crc_bad_msg_notification{m.Long(), m.Int(), m.Int()} 990 | 991 | case crc_msgs_ack: 992 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 993 | slog.Logln("msgs_ack", constructor) 994 | } 995 | r = TL_msgs_ack{m.VectorLong()} 996 | 997 | case crc_gzip_packed: 998 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 999 | slog.Logln("gzip_packed", constructor) 1000 | } 1001 | obj := make([]byte, 0, 4096) 1002 | 1003 | var buf bytes.Buffer 1004 | _, _ = buf.Write(m.StringBytes()) 1005 | gz, _ := gzip.NewReader(&buf) 1006 | b := make([]byte, 4096) 1007 | for true { 1008 | n, _ := gz.Read(b) 1009 | obj = append(obj, b[0:n]...) 1010 | if n <= 0 { 1011 | break 1012 | } 1013 | } 1014 | d := NewDecodeBuf(obj) 1015 | r = d.Object() 1016 | 1017 | default: 1018 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 1019 | slog.Logln(fmt.Sprintf("default %x", constructor)) 1020 | } 1021 | r = m.ObjectGenerated(constructor) 1022 | 1023 | } 1024 | 1025 | if m.err != nil { 1026 | slog.Logln(m.err.Error()) 1027 | return nil 1028 | } 1029 | return 1030 | } 1031 | 1032 | func (m *DecodeBuf) Flags() int32 { 1033 | if m.err != nil { 1034 | return 0 1035 | } 1036 | if m.off+4 > m.size { 1037 | m.err = errors.New("DecodeInt") 1038 | return 0 1039 | } 1040 | x := binary.LittleEndian.Uint32(m.buf[m.off : m.off+4]) 1041 | m.off += 4 1042 | if __debug&DEBUG_LEVEL_DECODE_DETAILS != 0 { 1043 | slog.Logln("Decode::Flags::", x) 1044 | } 1045 | return int32(x) 1046 | } 1047 | 1048 | func (m *DecodeBuf) FlaggedLong(flags, f int32) int64 { 1049 | bit := int32(1 << uint(f)) 1050 | if flags&bit == 0 { 1051 | return 0 1052 | } 1053 | return m.Long() 1054 | } 1055 | 1056 | func (m *DecodeBuf) FlaggedDouble(flags, f int32) float64 { 1057 | bit := int32(1 << uint(f)) 1058 | if flags&bit == 0 { 1059 | return 0 1060 | } 1061 | return m.Double() 1062 | } 1063 | 1064 | func (m *DecodeBuf) FlaggedInt(flags, f int32) int32 { 1065 | if m.err != nil { 1066 | return 0 1067 | } 1068 | bit := int32(1 << uint(f)) 1069 | if flags&bit == 0 { 1070 | return 0 1071 | } 1072 | return m.Int() 1073 | } 1074 | 1075 | func (m *DecodeBuf) FlaggedString(flags, f int32) string { 1076 | bit := int32(1 << uint(f)) 1077 | if flags&bit == 0 { 1078 | return "" 1079 | } 1080 | return m.String() 1081 | } 1082 | 1083 | func (m *DecodeBuf) FlaggedVector(flags, f int32) []TL { 1084 | bit := int32(1 << uint(f)) 1085 | if flags&bit == 0 { 1086 | return nil 1087 | } 1088 | return m.Vector() 1089 | } 1090 | 1091 | func (m *DecodeBuf) FlaggedObject(flags, f int32) (r TL) { 1092 | bit := int32(1 << uint(f)) 1093 | if flags&bit == 0 { 1094 | return nil 1095 | } 1096 | return m.Object() 1097 | } 1098 | 1099 | func (m *DecodeBuf) FlaggedStringBytes(flags, f int32) []byte { 1100 | bit := int32(1 << uint(f)) 1101 | if flags&bit == 0 { 1102 | return nil 1103 | } 1104 | return m.StringBytes() 1105 | } 1106 | 1107 | func (d *DecodeBuf) dump() { 1108 | slog.Logln(hex.Dump(d.buf[d.off:d.size])) 1109 | } 1110 | 1111 | func toBool(x TL) bool { 1112 | _, ok := x.(*PredBoolTrue) 1113 | return ok 1114 | } 1115 | -------------------------------------------------------------------------------- /tools/access/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/cjongseok/mtproto" 6 | "os" 7 | "fmt" 8 | "context" 9 | "github.com/cjongseok/slog" 10 | "github.com/golang/protobuf/proto" 11 | ) 12 | 13 | const ( 14 | appVersion = "0.0.1" 15 | deviceModel = "" 16 | systemVersion = "" 17 | language = "" 18 | ) 19 | 20 | var ( 21 | credentials = flag.String("credentials", "credentials.json", "Credentials file. Default is credentials.json") 22 | chans = flag.String("chans", "chans.acc", "Output file. Default is chans.json") 23 | users = flag.String("users", "users.acc", "Output file. Default is users.json") 24 | ) 25 | 26 | const logfile = "access.log" 27 | 28 | func main() { 29 | flag.Parse() 30 | if *credentials == "" || *chans== "" || *users == "" { 31 | flag.Usage() 32 | os.Exit(1) 33 | } 34 | 35 | slog.SetLogOutputAsFile(logfile) 36 | 37 | chatsFp, err := os.OpenFile(*chans, os.O_WRONLY | os.O_CREATE, 0644) 38 | handleError(err) 39 | usersFp, err := os.OpenFile(*users, os.O_WRONLY | os.O_CREATE, 0644) 40 | handleError(err) 41 | config, err := mtproto.NewConfiguration(appVersion, deviceModel, systemVersion, language, 0, 0, *credentials) 42 | handleError(err) 43 | manager, err := mtproto.NewManager(config) 44 | handleError(err) 45 | conn, err := manager.LoadAuthentication() 46 | handleError(err) 47 | caller := mtproto.RPCaller{conn} 48 | 49 | // chats 50 | allchats, err := caller.MessagesGetAllChats(context.Background(), &mtproto.ReqMessagesGetAllChats{}) 51 | handleError(err) 52 | marshaledChats, err := proto.Marshal(allchats) 53 | handleError(err) 54 | err = chatsFp.Truncate(0) 55 | handleError(err) 56 | _, err = chatsFp.WriteAt(marshaledChats, 0) 57 | handleError(err) 58 | fmt.Println(*chans, "is generated") 59 | 60 | // users 61 | contacts, err := caller.ContactsGetContacts(context.Background(), &mtproto.ReqContactsGetContacts{}) 62 | handleError(err) 63 | marshaledContacts, err := proto.Marshal(contacts) 64 | handleError(err) 65 | err = usersFp.Truncate(0) 66 | handleError(err) 67 | _, err = usersFp.WriteAt(marshaledContacts, 0) 68 | handleError(err) 69 | fmt.Println(*users, "is generated") 70 | } 71 | 72 | func handleError(err error) { 73 | if err != nil { 74 | fmt.Println(err) 75 | fmt.Println("see", logfile) 76 | os.Exit(1) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tools/dumplayer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cjongseok/mtproto" 6 | "github.com/cjongseok/slog" 7 | "os" 8 | "sync" 9 | ) 10 | 11 | func usage() { 12 | fmt.Println("USAGE: ./playdump ") 13 | fmt.Println("") 14 | } 15 | 16 | func handleError(err error) { 17 | if err != nil { 18 | fmt.Println(err) 19 | usage() 20 | os.Exit(1) 21 | } 22 | } 23 | 24 | type callback struct{} 25 | 26 | func (cb *callback) OnUpdate(u mtproto.Update) { 27 | fmt.Println("update:", slog.Stringify(u)) 28 | } 29 | 30 | func main() { 31 | if len(os.Args) != 3 { 32 | usage() 33 | return 34 | } 35 | 36 | authFileName := os.Args[1] 37 | dumpFilename := os.Args[2] 38 | slog.SetLogOutputAsFile("dump_" + dumpFilename + "_") 39 | handlerWg := sync.WaitGroup{} 40 | handlerWg.Add(1) 41 | out := make(chan interface{}) 42 | go func() { 43 | defer handlerWg.Done() 44 | for { 45 | select { 46 | case u := <-out: 47 | if u == nil { 48 | return 49 | } 50 | fmt.Println("update:", slog.Stringify(u)) 51 | } 52 | } 53 | }() 54 | dump, err := mtproto.NewDump(authFileName, dumpFilename, out) 55 | handleError(err) 56 | dump.Play() 57 | dump.Wait() 58 | handlerWg.Wait() 59 | } 60 | -------------------------------------------------------------------------------- /tools/keygen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cjongseok/mtproto" 6 | "os" 7 | "strconv" 8 | "github.com/cjongseok/slog" 9 | ) 10 | 11 | func usage() { 12 | fmt.Println(`Usage: keygen [KEY_NAME] 13 | 14 | Params: 15 | APIID means Telegram API id. If you do not have it yet, go "https://my.telegram.org/apps". 16 | APIHASH means hashcode of . It is published together with API id. 17 | PHONE means phone number in international format w/o hyphen. e.g., "+15417543010". 18 | 19 | Options: 20 | KEY_NAME means key file name to be generated. Default is "credentials.json". 21 | `) 22 | } 23 | 24 | const ( 25 | defaultNewKeyFile = "credentials.json" 26 | appVersion = "0.0.1" 27 | deviceModel = "" 28 | systemVersion = "" 29 | language = "" 30 | ip = "149.154.167.50" 31 | port = 443 32 | ) 33 | 34 | func main() { 35 | slog.DisableLogging() 36 | 37 | // parse arguments 38 | apiID, apiHash, phone, key, err := parseArgs() 39 | if err != nil { 40 | usage() 41 | handleError(err) 42 | } 43 | if key == "" { 44 | key = defaultNewKeyFile 45 | } 46 | config, err := mtproto.NewConfiguration(appVersion, deviceModel, systemVersion, language, 0, 0, key) 47 | handleError(err) 48 | 49 | // check if the file exists 50 | _, err = os.Stat(key) 51 | sessionExists := !os.IsNotExist(err) 52 | if sessionExists { 53 | handleError(fmt.Errorf("\"%s\" already exists\n", key)) 54 | } 55 | 56 | // request to send authentication code to the phone 57 | var manager *mtproto.Manager 58 | var mconn *mtproto.Conn 59 | var sentCode *mtproto.TypeAuthSentCode 60 | manager, err = mtproto.NewManager(config) 61 | handleError(err) 62 | mconn, sentCode, err = manager.NewAuthentication(phone, apiID, apiHash, ip, port) 63 | handleError(err) 64 | 65 | // sign-in with the code from the user input 66 | var code string 67 | fmt.Printf("Enter Code: ") 68 | fmt.Scanf("%s", &code) 69 | auth, err := mconn.SignIn(phone, code, sentCode.GetValue().PhoneCodeHash) 70 | handleError(err) 71 | if auth.Value.GetUser().GetUser() != nil { 72 | user := auth.Value.GetUser().GetUser() 73 | fmt.Printf("Signed in as \"%s\"\n", user.FirstName) 74 | } else if auth.Value.GetUser().GetUserEmpty() != nil { 75 | fmt.Println("Signed in with empty user") 76 | } else { 77 | fmt.Println("Signed in without user response: neither user nor user empty") 78 | } 79 | fmt.Println("Key is generated.") 80 | } 81 | 82 | func handleError(err error) { 83 | if err != nil { 84 | fmt.Println("error:", err) 85 | os.Exit(1) 86 | } 87 | } 88 | 89 | func parseArgs() (apiId int32, apiHash, phoneNumber, key string, err error){ 90 | switch len(os.Args) { 91 | case 4: 92 | case 5: 93 | key = os.Args[4] 94 | default: 95 | err = fmt.Errorf("invalid number of arguments") 96 | return 97 | } 98 | 99 | var apiId64 int64 100 | apiId64, err = strconv.ParseInt(os.Args[1], 10, 32) 101 | if err != nil { 102 | return 103 | } 104 | apiId = int32(apiId64) 105 | apiHash = os.Args[2] 106 | phoneNumber = os.Args[3] 107 | return 108 | } 109 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package mtproto 2 | 3 | const ( 4 | SESSION EventType = "session" 5 | MCONN EventType = "mconn" 6 | ) 7 | 8 | type EventType string 9 | type Event interface { 10 | Type() EventType 11 | } 12 | 13 | // Session Events 14 | type newsession struct { 15 | // If connID is zero, Manager makes new connection and assigns it the new session. 16 | // Otherwise, the new session is allocated to the connection of connID. 17 | connId int32 18 | //phonenumber string 19 | //addr string 20 | //useIPv6 bool 21 | phone string 22 | apiid int32 23 | apihash string 24 | ip string 25 | port int 26 | resp chan sessionResponse 27 | } 28 | 29 | type loadsession struct { 30 | // If connID is zero, Manager makes new connection and assigns it the loaded session. 31 | // Otherwise, the loaded session is allocated to the connection of connID. 32 | connId int32 33 | //phone string 34 | //preferredAddr string 35 | policy retryPolicy 36 | resp chan sessionResponse 37 | } 38 | 39 | type sessionResponse struct { 40 | connId int32 41 | session *Session 42 | err error 43 | } 44 | 45 | // Established = made + bound 46 | type SessionEstablished struct { 47 | session *Session 48 | } 49 | 50 | type discardSession struct { 51 | connId int32 52 | sessionId int64 53 | resp chan sessionResponse 54 | } 55 | 56 | type SessionDiscarded struct { 57 | boundConnId int32 58 | discardedSessionId int64 59 | discardedSessionUpdatesState *PredUpdatesState 60 | } 61 | 62 | // discardSession + newsession 63 | type renewSession struct { 64 | sessionId int64 65 | phone string 66 | apiID int32 67 | apiHash string 68 | ip string 69 | port int 70 | resp chan sessionResponse 71 | } 72 | 73 | // discardSession + loadsession 74 | type refreshSession struct { 75 | sessionId int64 76 | phone string 77 | policy retryPolicy 78 | resp chan sessionResponse 79 | } 80 | type retryPolicy string 81 | 82 | const noRetry retryPolicy = "noRetry" 83 | const untilSuccess retryPolicy = "untilSuccess" 84 | 85 | // Connection Events 86 | type ConnectionOpened struct { 87 | mconn *Conn 88 | sessionID int64 89 | } 90 | type sessionBound struct { 91 | mconn *Conn 92 | sessionID int64 93 | } 94 | type sessionUnbound struct { 95 | mconn *Conn 96 | unboundSessionID int64 97 | } 98 | type closeConnection struct { 99 | connId int32 100 | resp chan error 101 | } 102 | type connectionClosed struct { 103 | closedConnId int32 104 | } 105 | 106 | // Update Event 107 | type updateReceived struct { 108 | update Update 109 | } 110 | 111 | func (e newsession) Type() EventType { return SESSION } 112 | func (e loadsession) Type() EventType { return SESSION } 113 | func (e SessionEstablished) Type() EventType { return SESSION } 114 | func (e renewSession) Type() EventType { return SESSION } 115 | func (e refreshSession) Type() EventType { return SESSION } 116 | func (e discardSession) Type() EventType { return SESSION } 117 | func (e SessionDiscarded) Type() EventType { return SESSION } 118 | func (e ConnectionOpened) Type() EventType { return MCONN } 119 | func (e sessionBound) Type() EventType { return MCONN } 120 | func (e sessionUnbound) Type() EventType { return MCONN } 121 | func (e closeConnection) Type() EventType { return MCONN } 122 | func (e connectionClosed) Type() EventType { return MCONN } 123 | func (e updateReceived) Type() EventType { return SESSION } 124 | 125 | //func (e newsession) SessionId() (int64) {return 0} 126 | //func (e loadsession) SessionId() (int64) {return 0} 127 | //func (e SessionEstablished) SessionId() (int64) {return e.session.sessionID} 128 | //func (e renewSession) SessionId() (int64) {return e.sessionID} 129 | //func (e refreshSession) SessionId() (int64) {return e.sessionID} 130 | //func (e discardSession) SessionId() (int64) {return e.sessionID} 131 | //func (e SessionDiscarded) SessionId() (int64) {return e.discardedSessionId} 132 | //func (e ConnectionOpened) SessionId() (int64) {return 0} 133 | //func (e sessionBound) SessionId() (int64) { 134 | // session, err := e.mconn.Session() 135 | // if err != nil { 136 | // return 0 137 | // } 138 | // return session.sessionID 139 | //} 140 | //func (e sessionUnbound) SessionId() (int64) {return e.unboundSessionID} 141 | //func (e closeConnection) SessionId() (int64) {return 0} 142 | //func (e connectionClosed) SessionId() (int64) {return 0} 143 | // 144 | //func (e newsession) ConnectionId() (int32) {return 0} 145 | //func (e loadsession) ConnectionId() (int32) {return 0} 146 | //func (e SessionEstablished) ConnectionId() (int32) {return e.connID} 147 | //func (e renewSession) ConnectionId() (int32) {return e.connID} 148 | //func (e refreshSession) ConnectionId() (int32) {return e.connID} 149 | //func (e discardSession) ConnectionId() (int32) {return e.connID} 150 | //func (e SessionDiscarded) ConnectionId() (int32) {return e.boundConnId} 151 | //func (e ConnectionOpened) ConnectionId() (int32) {return e.mconn.connID} 152 | //func (e sessionBound) ConnectionId() (int32) {return e.mconn.connID} 153 | //func (e sessionUnbound) ConnectionId() (int32) {return e.mconn.connID} 154 | //func (e closeConnection) ConnectionId() (int32) {return e.connID} 155 | //func (e connectionClosed) ConnectionId() (int32) {return e.closedConnId} 156 | 157 | type Update interface { 158 | Predicate 159 | UpdateDate() int32 160 | } 161 | 162 | type UpdateCallback interface { 163 | OnUpdate(update Update) 164 | } 165 | 166 | //PredUpdatesState Value = 1; 167 | 168 | //PredUpdatesTooLong UpdatesTooLong = 1; 169 | //PredUpdateShortMessage UpdateShortMessage = 2; 170 | //PredUpdateShortChatMessage UpdateShortChatMessage = 3; 171 | //PredUpdateShort UpdateShort = 4; 172 | //PredUpdatesCombined UpdatesCombined = 5; 173 | //PredUpdates Updates = 6; 174 | //PredUpdateShortSentMessage UpdateShortSentMessage = 7; 175 | 176 | func (u *PredUpdatesState) UpdateDate() int32 { return u.Date } 177 | 178 | func (u *PredUpdateShortMessage) UpdateDate() int32 { return u.Date } 179 | func (u *PredUpdateShortChatMessage) UpdateDate() int32 { return u.Date } 180 | func (u *PredUpdateShort) UpdateDate() int32 { return u.Date } 181 | func (u *PredUpdates) UpdateDate() int32 { return u.Date } 182 | func (u *PredUpdateShortSentMessage) UpdateDate() int32 { return u.Date } 183 | 184 | func (u *PredUpdatesDifference) UpdateDate() int32 { return 0 } 185 | func (u *PredUpdatesDifferenceSlice) UpdateDate() int32 { return 0 } 186 | 187 | //func (u US_updates_difference) UpdateDate() int32 { return 0 } 188 | func (u *PredUpdateNewMessage) UpdateDate() int32 { return 0 } 189 | func (u *PredUpdateReadMessagesContents) UpdateDate() int32 { return 0 } 190 | func (u *PredUpdateDeleteMessages) UpdateDate() int32 { return 0 } 191 | func (u *PredUpdateNewEncryptedMessage) UpdateDate() int32 { return 0 } 192 | 193 | func (u *PredUpdateChannel) UpdateDate() int32 { return 0 } 194 | func (u *PredUpdateChannelMessageViews) UpdateDate() int32 { return 0 } 195 | func (u *PredUpdateChannelTooLong) UpdateDate() int32 { return 0 } 196 | func (u *PredUpdateReadChannelInbox) UpdateDate() int32 { return 0 } 197 | func (u *PredUpdateReadChannelOutbox) UpdateDate() int32 { return 0 } 198 | func (u *PredUpdateNewChannelMessage) UpdateDate() int32 { return 0 } 199 | --------------------------------------------------------------------------------