goygram 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
goygram-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: goygram
3
+ Version: 0.1.0
4
+ Requires-Dist: aiohttp>=3.9,<4.0
5
+ Requires-Dist: pydantic>=2.7,<3.0
6
+ Summary: Goygram: unified Telegram runtime on Python and Rust
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
9
+
10
+ # GoyGram
11
+
12
+ [![License: AGPLv3](https://img.shields.io/badge/license-AGPLv3-black.svg)](LICENSE)
13
+ [![Python](https://img.shields.io/badge/python-3.11%2B-1f6feb.svg)](https://www.python.org/)
14
+ [![Rust](https://img.shields.io/badge/rust-1.78%2B-b7410e.svg)](https://www.rust-lang.org/)
15
+ [![Build Status](https://img.shields.io/badge/build-maturin-ready-2ea043.svg)](#build)
16
+
17
+ GoyGram is a Telegram framework with one clean entrypoint for user code and split low-level lanes under the hood: Bot API on `aiohttp`, MT transport on raw sockets, Rust for frame and crypto primitives.
18
+
19
+ ## Build
20
+
21
+ ```bash
22
+ python -m venv .venv
23
+ source .venv/bin/activate
24
+ pip install -U pip maturin aiohttp pydantic
25
+ maturin develop
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```python
31
+ import asyncio
32
+
33
+ from goygram import GoyGram, InlineKbd, LinkOpts
34
+
35
+
36
+ app = GoyGram(
37
+ bot_token="123456:BOT_TOKEN",
38
+ )
39
+
40
+ kbd = InlineKbd().add_btn("Ping", data="ping")
41
+ links = LinkOpts(above=True, large=True)
42
+
43
+
44
+ @app.on_msg
45
+ async def echo(msg):
46
+ if not msg.text:
47
+ return
48
+ if msg.text == "/start":
49
+ await app.send_msg(msg.chat_id, "hello https://example.com", kbd=kbd, link_options=links, via=msg.src)
50
+ return
51
+ await msg.reply(f"rx: {msg.text}")
52
+
53
+
54
+ asyncio.run(app.run())
55
+ ```
56
+
57
+ ### Commands And Callbacks
58
+
59
+ ```python
60
+ @app.on_cmd("start", "help")
61
+ async def boot(msg):
62
+ await msg.reply("ready")
63
+
64
+
65
+ @app.on_cb
66
+ async def taps(cb):
67
+ if cb.data == "ping":
68
+ await cb.answer("pong")
69
+ await cb.edit("updated")
70
+ ```
71
+
72
+ ## Public API
73
+
74
+ ```python
75
+ from goygram import GoyGram, InlineKbd, ReplyKbd, Btn
76
+ ```
77
+
78
+ `GoyGram(...)` accepts flat config:
79
+
80
+ - `bot_token`
81
+ - `mt_host`
82
+ - `mt_port`
83
+ - `mt_key`
84
+ - `mt_iv`
85
+
86
+ Main calls:
87
+
88
+ - `@app.on_msg`
89
+ - `@app.on_cmd("start")`
90
+ - `@app.on_cb`
91
+ - `@app.on_poll`
92
+ - `@app.on_member`
93
+ - `await app.send_msg(chat_id, text, kbd=..., via=...)`
94
+ - `await app.send_msg(chat_id, text, link_options=...)`
95
+ - `await app.send_photo(chat_id, photo, caption=...)`
96
+ - `await app.send_doc(chat_id, document, caption=...)`
97
+ - `await app.send_media_group(chat_id, media)`
98
+ - `await app.answer_cb(cb_id, text=...)`
99
+ - `await app.edit_text(chat_id, msg_id, text)`
100
+ - `await app.set_webhook(url)`
101
+ - `await app.delete_webhook()`
102
+ - `await app.get_webhook_info()`
103
+ - `await msg.reply(text, kbd=...)`
104
+ - `await msg.delete()`
105
+ - `await app.run()`
106
+
107
+ Extra UI helpers:
108
+
109
+ - `InlineKbd`
110
+ - `ReplyKbd`
111
+ - `ForceReply`
112
+ - `ReplyGone`
113
+
114
+ ## Package Layout
115
+
116
+ ```text
117
+ .
118
+ ├── ext_rust
119
+ │ ├── Cargo.toml
120
+ │ └── src/lib.rs
121
+ ├── goygram
122
+ │ ├── __init__.py
123
+ │ ├── client.py
124
+ │ ├── api
125
+ │ ├── core
126
+ │ ├── tl
127
+ │ ├── types
128
+ │ └── vendor
129
+ ├── tools
130
+ │ ├── gen_botapi.py
131
+ │ └── gen_mtproto.py
132
+ └── tests
133
+ ```
134
+
135
+ ## Codegen
136
+
137
+ ```bash
138
+ python tools/gen_botapi.py
139
+ python tools/gen_mtproto.py
140
+ ```
141
+
142
+ Generated targets:
143
+
144
+ - `goygram/api/types.py`
145
+ - `goygram/api/methods.py`
146
+ - `goygram/tl/schema.py`
147
+
148
+ ## Tests
149
+
150
+ ```bash
151
+ python -m unittest tests.test_matrix -v
152
+ ```
153
+
@@ -0,0 +1,143 @@
1
+ # GoyGram
2
+
3
+ [![License: AGPLv3](https://img.shields.io/badge/license-AGPLv3-black.svg)](LICENSE)
4
+ [![Python](https://img.shields.io/badge/python-3.11%2B-1f6feb.svg)](https://www.python.org/)
5
+ [![Rust](https://img.shields.io/badge/rust-1.78%2B-b7410e.svg)](https://www.rust-lang.org/)
6
+ [![Build Status](https://img.shields.io/badge/build-maturin-ready-2ea043.svg)](#build)
7
+
8
+ GoyGram is a Telegram framework with one clean entrypoint for user code and split low-level lanes under the hood: Bot API on `aiohttp`, MT transport on raw sockets, Rust for frame and crypto primitives.
9
+
10
+ ## Build
11
+
12
+ ```bash
13
+ python -m venv .venv
14
+ source .venv/bin/activate
15
+ pip install -U pip maturin aiohttp pydantic
16
+ maturin develop
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```python
22
+ import asyncio
23
+
24
+ from goygram import GoyGram, InlineKbd, LinkOpts
25
+
26
+
27
+ app = GoyGram(
28
+ bot_token="123456:BOT_TOKEN",
29
+ )
30
+
31
+ kbd = InlineKbd().add_btn("Ping", data="ping")
32
+ links = LinkOpts(above=True, large=True)
33
+
34
+
35
+ @app.on_msg
36
+ async def echo(msg):
37
+ if not msg.text:
38
+ return
39
+ if msg.text == "/start":
40
+ await app.send_msg(msg.chat_id, "hello https://example.com", kbd=kbd, link_options=links, via=msg.src)
41
+ return
42
+ await msg.reply(f"rx: {msg.text}")
43
+
44
+
45
+ asyncio.run(app.run())
46
+ ```
47
+
48
+ ### Commands And Callbacks
49
+
50
+ ```python
51
+ @app.on_cmd("start", "help")
52
+ async def boot(msg):
53
+ await msg.reply("ready")
54
+
55
+
56
+ @app.on_cb
57
+ async def taps(cb):
58
+ if cb.data == "ping":
59
+ await cb.answer("pong")
60
+ await cb.edit("updated")
61
+ ```
62
+
63
+ ## Public API
64
+
65
+ ```python
66
+ from goygram import GoyGram, InlineKbd, ReplyKbd, Btn
67
+ ```
68
+
69
+ `GoyGram(...)` accepts flat config:
70
+
71
+ - `bot_token`
72
+ - `mt_host`
73
+ - `mt_port`
74
+ - `mt_key`
75
+ - `mt_iv`
76
+
77
+ Main calls:
78
+
79
+ - `@app.on_msg`
80
+ - `@app.on_cmd("start")`
81
+ - `@app.on_cb`
82
+ - `@app.on_poll`
83
+ - `@app.on_member`
84
+ - `await app.send_msg(chat_id, text, kbd=..., via=...)`
85
+ - `await app.send_msg(chat_id, text, link_options=...)`
86
+ - `await app.send_photo(chat_id, photo, caption=...)`
87
+ - `await app.send_doc(chat_id, document, caption=...)`
88
+ - `await app.send_media_group(chat_id, media)`
89
+ - `await app.answer_cb(cb_id, text=...)`
90
+ - `await app.edit_text(chat_id, msg_id, text)`
91
+ - `await app.set_webhook(url)`
92
+ - `await app.delete_webhook()`
93
+ - `await app.get_webhook_info()`
94
+ - `await msg.reply(text, kbd=...)`
95
+ - `await msg.delete()`
96
+ - `await app.run()`
97
+
98
+ Extra UI helpers:
99
+
100
+ - `InlineKbd`
101
+ - `ReplyKbd`
102
+ - `ForceReply`
103
+ - `ReplyGone`
104
+
105
+ ## Package Layout
106
+
107
+ ```text
108
+ .
109
+ ├── ext_rust
110
+ │ ├── Cargo.toml
111
+ │ └── src/lib.rs
112
+ ├── goygram
113
+ │ ├── __init__.py
114
+ │ ├── client.py
115
+ │ ├── api
116
+ │ ├── core
117
+ │ ├── tl
118
+ │ ├── types
119
+ │ └── vendor
120
+ ├── tools
121
+ │ ├── gen_botapi.py
122
+ │ └── gen_mtproto.py
123
+ └── tests
124
+ ```
125
+
126
+ ## Codegen
127
+
128
+ ```bash
129
+ python tools/gen_botapi.py
130
+ python tools/gen_mtproto.py
131
+ ```
132
+
133
+ Generated targets:
134
+
135
+ - `goygram/api/types.py`
136
+ - `goygram/api/methods.py`
137
+ - `goygram/tl/schema.py`
138
+
139
+ ## Tests
140
+
141
+ ```bash
142
+ python -m unittest tests.test_matrix -v
143
+ ```
@@ -0,0 +1,317 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "aes"
7
+ version = "0.8.4"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
10
+ dependencies = [
11
+ "cfg-if",
12
+ "cipher",
13
+ "cpufeatures",
14
+ ]
15
+
16
+ [[package]]
17
+ name = "autocfg"
18
+ version = "1.5.0"
19
+ source = "registry+https://github.com/rust-lang/crates.io-index"
20
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
21
+
22
+ [[package]]
23
+ name = "bitflags"
24
+ version = "2.11.1"
25
+ source = "registry+https://github.com/rust-lang/crates.io-index"
26
+ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
27
+
28
+ [[package]]
29
+ name = "cfg-if"
30
+ version = "1.0.4"
31
+ source = "registry+https://github.com/rust-lang/crates.io-index"
32
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
33
+
34
+ [[package]]
35
+ name = "cipher"
36
+ version = "0.4.4"
37
+ source = "registry+https://github.com/rust-lang/crates.io-index"
38
+ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
39
+ dependencies = [
40
+ "crypto-common",
41
+ "inout",
42
+ ]
43
+
44
+ [[package]]
45
+ name = "cpufeatures"
46
+ version = "0.2.17"
47
+ source = "registry+https://github.com/rust-lang/crates.io-index"
48
+ checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
49
+ dependencies = [
50
+ "libc",
51
+ ]
52
+
53
+ [[package]]
54
+ name = "crypto-common"
55
+ version = "0.1.7"
56
+ source = "registry+https://github.com/rust-lang/crates.io-index"
57
+ checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
58
+ dependencies = [
59
+ "generic-array",
60
+ "typenum",
61
+ ]
62
+
63
+ [[package]]
64
+ name = "generic-array"
65
+ version = "0.14.7"
66
+ source = "registry+https://github.com/rust-lang/crates.io-index"
67
+ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
68
+ dependencies = [
69
+ "typenum",
70
+ "version_check",
71
+ ]
72
+
73
+ [[package]]
74
+ name = "goygram_ext"
75
+ version = "0.1.0"
76
+ dependencies = [
77
+ "aes",
78
+ "pyo3",
79
+ ]
80
+
81
+ [[package]]
82
+ name = "heck"
83
+ version = "0.4.1"
84
+ source = "registry+https://github.com/rust-lang/crates.io-index"
85
+ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
86
+
87
+ [[package]]
88
+ name = "indoc"
89
+ version = "2.0.7"
90
+ source = "registry+https://github.com/rust-lang/crates.io-index"
91
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
92
+ dependencies = [
93
+ "rustversion",
94
+ ]
95
+
96
+ [[package]]
97
+ name = "inout"
98
+ version = "0.1.4"
99
+ source = "registry+https://github.com/rust-lang/crates.io-index"
100
+ checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
101
+ dependencies = [
102
+ "generic-array",
103
+ ]
104
+
105
+ [[package]]
106
+ name = "libc"
107
+ version = "0.2.186"
108
+ source = "registry+https://github.com/rust-lang/crates.io-index"
109
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
110
+
111
+ [[package]]
112
+ name = "lock_api"
113
+ version = "0.4.14"
114
+ source = "registry+https://github.com/rust-lang/crates.io-index"
115
+ checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
116
+ dependencies = [
117
+ "scopeguard",
118
+ ]
119
+
120
+ [[package]]
121
+ name = "memoffset"
122
+ version = "0.9.1"
123
+ source = "registry+https://github.com/rust-lang/crates.io-index"
124
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
125
+ dependencies = [
126
+ "autocfg",
127
+ ]
128
+
129
+ [[package]]
130
+ name = "once_cell"
131
+ version = "1.21.4"
132
+ source = "registry+https://github.com/rust-lang/crates.io-index"
133
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
134
+
135
+ [[package]]
136
+ name = "parking_lot"
137
+ version = "0.12.5"
138
+ source = "registry+https://github.com/rust-lang/crates.io-index"
139
+ checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
140
+ dependencies = [
141
+ "lock_api",
142
+ "parking_lot_core",
143
+ ]
144
+
145
+ [[package]]
146
+ name = "parking_lot_core"
147
+ version = "0.9.12"
148
+ source = "registry+https://github.com/rust-lang/crates.io-index"
149
+ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
150
+ dependencies = [
151
+ "cfg-if",
152
+ "libc",
153
+ "redox_syscall",
154
+ "smallvec",
155
+ "windows-link",
156
+ ]
157
+
158
+ [[package]]
159
+ name = "portable-atomic"
160
+ version = "1.13.1"
161
+ source = "registry+https://github.com/rust-lang/crates.io-index"
162
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
163
+
164
+ [[package]]
165
+ name = "proc-macro2"
166
+ version = "1.0.106"
167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
168
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
169
+ dependencies = [
170
+ "unicode-ident",
171
+ ]
172
+
173
+ [[package]]
174
+ name = "pyo3"
175
+ version = "0.21.2"
176
+ source = "registry+https://github.com/rust-lang/crates.io-index"
177
+ checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8"
178
+ dependencies = [
179
+ "cfg-if",
180
+ "indoc",
181
+ "libc",
182
+ "memoffset",
183
+ "parking_lot",
184
+ "portable-atomic",
185
+ "pyo3-build-config",
186
+ "pyo3-ffi",
187
+ "pyo3-macros",
188
+ "unindent",
189
+ ]
190
+
191
+ [[package]]
192
+ name = "pyo3-build-config"
193
+ version = "0.21.2"
194
+ source = "registry+https://github.com/rust-lang/crates.io-index"
195
+ checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50"
196
+ dependencies = [
197
+ "once_cell",
198
+ "target-lexicon",
199
+ ]
200
+
201
+ [[package]]
202
+ name = "pyo3-ffi"
203
+ version = "0.21.2"
204
+ source = "registry+https://github.com/rust-lang/crates.io-index"
205
+ checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403"
206
+ dependencies = [
207
+ "libc",
208
+ "pyo3-build-config",
209
+ ]
210
+
211
+ [[package]]
212
+ name = "pyo3-macros"
213
+ version = "0.21.2"
214
+ source = "registry+https://github.com/rust-lang/crates.io-index"
215
+ checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c"
216
+ dependencies = [
217
+ "proc-macro2",
218
+ "pyo3-macros-backend",
219
+ "quote",
220
+ "syn",
221
+ ]
222
+
223
+ [[package]]
224
+ name = "pyo3-macros-backend"
225
+ version = "0.21.2"
226
+ source = "registry+https://github.com/rust-lang/crates.io-index"
227
+ checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c"
228
+ dependencies = [
229
+ "heck",
230
+ "proc-macro2",
231
+ "pyo3-build-config",
232
+ "quote",
233
+ "syn",
234
+ ]
235
+
236
+ [[package]]
237
+ name = "quote"
238
+ version = "1.0.45"
239
+ source = "registry+https://github.com/rust-lang/crates.io-index"
240
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
241
+ dependencies = [
242
+ "proc-macro2",
243
+ ]
244
+
245
+ [[package]]
246
+ name = "redox_syscall"
247
+ version = "0.5.18"
248
+ source = "registry+https://github.com/rust-lang/crates.io-index"
249
+ checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
250
+ dependencies = [
251
+ "bitflags",
252
+ ]
253
+
254
+ [[package]]
255
+ name = "rustversion"
256
+ version = "1.0.22"
257
+ source = "registry+https://github.com/rust-lang/crates.io-index"
258
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
259
+
260
+ [[package]]
261
+ name = "scopeguard"
262
+ version = "1.2.0"
263
+ source = "registry+https://github.com/rust-lang/crates.io-index"
264
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
265
+
266
+ [[package]]
267
+ name = "smallvec"
268
+ version = "1.15.1"
269
+ source = "registry+https://github.com/rust-lang/crates.io-index"
270
+ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
271
+
272
+ [[package]]
273
+ name = "syn"
274
+ version = "2.0.117"
275
+ source = "registry+https://github.com/rust-lang/crates.io-index"
276
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
277
+ dependencies = [
278
+ "proc-macro2",
279
+ "quote",
280
+ "unicode-ident",
281
+ ]
282
+
283
+ [[package]]
284
+ name = "target-lexicon"
285
+ version = "0.12.16"
286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
287
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
288
+
289
+ [[package]]
290
+ name = "typenum"
291
+ version = "1.20.0"
292
+ source = "registry+https://github.com/rust-lang/crates.io-index"
293
+ checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
294
+
295
+ [[package]]
296
+ name = "unicode-ident"
297
+ version = "1.0.24"
298
+ source = "registry+https://github.com/rust-lang/crates.io-index"
299
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
300
+
301
+ [[package]]
302
+ name = "unindent"
303
+ version = "0.2.4"
304
+ source = "registry+https://github.com/rust-lang/crates.io-index"
305
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
306
+
307
+ [[package]]
308
+ name = "version_check"
309
+ version = "0.9.5"
310
+ source = "registry+https://github.com/rust-lang/crates.io-index"
311
+ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
312
+
313
+ [[package]]
314
+ name = "windows-link"
315
+ version = "0.2.1"
316
+ source = "registry+https://github.com/rust-lang/crates.io-index"
317
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
@@ -0,0 +1,10 @@
1
+ # Copyleft 2026 github.com/sepiol026-wq | telegram:@samsepi0l_ovf. Licensed under AGPLv3.
2
+ [package]
3
+ name = "goygram_ext"
4
+ version = "0.1.0"
5
+ edition = "2021"
6
+
7
+ [dependencies]
8
+ aes = "0.8"
9
+ pyo3 = { version = "0.21", features = ["extension-module"] }
10
+
@@ -0,0 +1,4 @@
1
+ # Copyleft 2026 github.com/sepiol026-wq | telegram:@samsepi0l_ovf. Licensed under AGPLv3.
2
+ from ext_rust._ext import aes_ige_dec, aes_ige_enc, cut, pack
3
+
4
+ __all__ = ["aes_ige_dec", "aes_ige_enc", "cut", "pack"]
@@ -0,0 +1,139 @@
1
+ // Copyleft 2026 github.com/sepiol026-wq | telegram:@samsepi0l_ovf. Licensed under AGPLv3.
2
+ use aes::cipher::generic_array::GenericArray;
3
+ use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
4
+ use aes::Aes256;
5
+ use pyo3::exceptions::PyValueError;
6
+ use pyo3::prelude::*;
7
+ use pyo3::types::PyBytes;
8
+
9
+ fn pad(src: &[u8]) -> Vec<u8> {
10
+ let n = 16 - (src.len() % 16);
11
+ let p = if n == 0 { 16 } else { n };
12
+ let mut out = Vec::with_capacity(src.len() + p);
13
+ out.extend_from_slice(src);
14
+ out.resize(src.len() + p, p as u8);
15
+ out
16
+ }
17
+
18
+ fn unpad(src: &[u8]) -> PyResult<Vec<u8>> {
19
+ if src.is_empty() {
20
+ return Ok(Vec::new());
21
+ }
22
+ let p = *src.last().unwrap() as usize;
23
+ if p == 0 || p > 16 || src.len() < p {
24
+ return Err(PyValueError::new_err("bad pad"));
25
+ }
26
+ if src[src.len() - p..].iter().any(|b| *b as usize != p) {
27
+ return Err(PyValueError::new_err("bad pad"));
28
+ }
29
+ Ok(src[..src.len() - p].to_vec())
30
+ }
31
+
32
+ fn chk(key: &[u8], iv: &[u8], data: &[u8]) -> PyResult<()> {
33
+ if key.len() != 32 {
34
+ return Err(PyValueError::new_err("key must be 32 bytes"));
35
+ }
36
+ if iv.len() != 32 {
37
+ return Err(PyValueError::new_err("iv must be 32 bytes"));
38
+ }
39
+ if data.len() % 16 != 0 {
40
+ return Err(PyValueError::new_err("data must be multiple of 16"));
41
+ }
42
+ Ok(())
43
+ }
44
+
45
+ fn enc_raw(key: &[u8], iv: &[u8], data: &[u8]) -> PyResult<Vec<u8>> {
46
+ chk(key, iv, data)?;
47
+ let aes = Aes256::new_from_slice(key).map_err(|_| PyValueError::new_err("bad key"))?;
48
+ let mut x = iv[..16].to_vec();
49
+ let mut y = iv[16..].to_vec();
50
+ let mut out = Vec::with_capacity(data.len());
51
+ for blk in data.chunks_exact(16) {
52
+ let mut tmp = [0u8; 16];
53
+ for i in 0..16 {
54
+ tmp[i] = blk[i] ^ y[i];
55
+ }
56
+ let mut ga = GenericArray::clone_from_slice(&tmp);
57
+ aes.encrypt_block(&mut ga);
58
+ let mut c = [0u8; 16];
59
+ for i in 0..16 {
60
+ c[i] = ga[i] ^ x[i];
61
+ }
62
+ x.copy_from_slice(blk);
63
+ y.copy_from_slice(&c);
64
+ out.extend_from_slice(&c);
65
+ }
66
+ Ok(out)
67
+ }
68
+
69
+ fn dec_raw(key: &[u8], iv: &[u8], data: &[u8]) -> PyResult<Vec<u8>> {
70
+ chk(key, iv, data)?;
71
+ let aes = Aes256::new_from_slice(key).map_err(|_| PyValueError::new_err("bad key"))?;
72
+ let mut x = iv[..16].to_vec();
73
+ let mut y = iv[16..].to_vec();
74
+ let mut out = Vec::with_capacity(data.len());
75
+ for blk in data.chunks_exact(16) {
76
+ let mut tmp = [0u8; 16];
77
+ for i in 0..16 {
78
+ tmp[i] = blk[i] ^ x[i];
79
+ }
80
+ let mut ga = GenericArray::clone_from_slice(&tmp);
81
+ aes.decrypt_block(&mut ga);
82
+ let mut p = [0u8; 16];
83
+ for i in 0..16 {
84
+ p[i] = ga[i] ^ y[i];
85
+ }
86
+ x.copy_from_slice(&p);
87
+ y.copy_from_slice(blk);
88
+ out.extend_from_slice(&p);
89
+ }
90
+ Ok(out)
91
+ }
92
+
93
+ #[pyfunction]
94
+ fn aes_ige_enc(data: &[u8], key: &[u8], iv: &[u8]) -> PyResult<Vec<u8>> {
95
+ let raw = pad(data);
96
+ enc_raw(key, iv, &raw)
97
+ }
98
+
99
+ #[pyfunction]
100
+ fn aes_ige_dec(data: &[u8], key: &[u8], iv: &[u8]) -> PyResult<Vec<u8>> {
101
+ let raw = dec_raw(key, iv, data)?;
102
+ unpad(&raw)
103
+ }
104
+
105
+ #[pyfunction]
106
+ fn cut(py: Python<'_>, buf: &[u8]) -> PyResult<(Vec<Py<PyBytes>>, Py<PyBytes>)> {
107
+ let mut i = 0usize;
108
+ let mut out = Vec::new();
109
+ while i + 4 <= buf.len() {
110
+ let n = u32::from_le_bytes([buf[i], buf[i + 1], buf[i + 2], buf[i + 3]]) as usize;
111
+ if n == 0 {
112
+ return Err(PyValueError::new_err("zero frame"));
113
+ }
114
+ if i + 4 + n > buf.len() {
115
+ break;
116
+ }
117
+ out.push(PyBytes::new(py, &buf[i + 4..i + 4 + n]).into());
118
+ i += 4 + n;
119
+ }
120
+ Ok((out, PyBytes::new(py, &buf[i..]).into()))
121
+ }
122
+
123
+ #[pyfunction]
124
+ fn pack(data: &[u8]) -> Vec<u8> {
125
+ let mut out = Vec::with_capacity(data.len() + 4);
126
+ out.extend_from_slice(&(data.len() as u32).to_le_bytes());
127
+ out.extend_from_slice(data);
128
+ out
129
+ }
130
+
131
+ #[pymodule]
132
+ fn _ext(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
133
+ m.add_function(wrap_pyfunction!(aes_ige_enc, m)?)?;
134
+ m.add_function(wrap_pyfunction!(aes_ige_dec, m)?)?;
135
+ m.add_function(wrap_pyfunction!(cut, m)?)?;
136
+ m.add_function(wrap_pyfunction!(pack, m)?)?;
137
+ Ok(())
138
+ }
139
+
@@ -0,0 +1,19 @@
1
+ # Copyleft 2026 github.com/sepiol026-wq | telegram:@samsepi0l_ovf. Licensed under AGPLv3.
2
+ [build-system]
3
+ requires = ["maturin>=1.7,<2.0"]
4
+ build-backend = "maturin"
5
+
6
+ [project]
7
+ name = "goygram"
8
+ version = "0.1.0"
9
+ description = "Goygram: unified Telegram runtime on Python and Rust"
10
+ readme = "README.md"
11
+ requires-python = ">=3.11"
12
+ dependencies = ["aiohttp>=3.9,<4.0", "pydantic>=2.7,<3.0"]
13
+
14
+ [tool.maturin]
15
+ manifest-path = "ext_rust/Cargo.toml"
16
+ python-source = "."
17
+ module-name = "ext_rust._ext"
18
+ bindings = "pyo3"
19
+