ferogram 0.1.9__tar.gz → 0.2.1__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.
Files changed (92) hide show
  1. {ferogram-0.1.9 → ferogram-0.2.1}/Cargo.lock +73 -17
  2. {ferogram-0.1.9 → ferogram-0.2.1}/Cargo.toml +2 -2
  3. {ferogram-0.1.9 → ferogram-0.2.1}/FEATURES.md +34 -33
  4. {ferogram-0.1.9 → ferogram-0.2.1}/PKG-INFO +1 -1
  5. {ferogram-0.1.9 → ferogram-0.2.1}/pyproject.toml +1 -1
  6. {ferogram-0.1.9 → ferogram-0.2.1}/src/auth.rs +23 -7
  7. {ferogram-0.1.9 → ferogram-0.2.1}/src/client.rs +121 -39
  8. {ferogram-0.1.9 → ferogram-0.2.1}/src/lib.rs +3 -1
  9. {ferogram-0.1.9 → ferogram-0.2.1}/src/message.rs +1 -1
  10. {ferogram-0.1.9 → ferogram-0.2.1}/src/updates.rs +36 -15
  11. {ferogram-0.1.9 → ferogram-0.2.1}/.github/workflows/publish.yml +0 -0
  12. {ferogram-0.1.9 → ferogram-0.2.1}/.gitignore +0 -0
  13. {ferogram-0.1.9 → ferogram-0.2.1}/LICENSE-APACHE +0 -0
  14. {ferogram-0.1.9 → ferogram-0.2.1}/LICENSE-MIT +0 -0
  15. {ferogram-0.1.9 → ferogram-0.2.1}/README.md +0 -0
  16. {ferogram-0.1.9 → ferogram-0.2.1}/examples/admin_tools.py +0 -0
  17. {ferogram-0.1.9 → ferogram-0.2.1}/examples/command_bot.py +0 -0
  18. {ferogram-0.1.9 → ferogram-0.2.1}/examples/echo_bot.py +0 -0
  19. {ferogram-0.1.9 → ferogram-0.2.1}/examples/group_management.py +0 -0
  20. {ferogram-0.1.9 → ferogram-0.2.1}/examples/media_bot.py +0 -0
  21. {ferogram-0.1.9 → ferogram-0.2.1}/examples/raw_invoke.py +0 -0
  22. {ferogram-0.1.9 → ferogram-0.2.1}/examples/search_bot.py +0 -0
  23. {ferogram-0.1.9 → ferogram-0.2.1}/examples/send_hi.py +0 -0
  24. {ferogram-0.1.9 → ferogram-0.2.1}/examples/send_media.py +0 -0
  25. {ferogram-0.1.9 → ferogram-0.2.1}/examples/send_message.py +0 -0
  26. {ferogram-0.1.9 → ferogram-0.2.1}/examples/update_handlers.py +0 -0
  27. {ferogram-0.1.9 → ferogram-0.2.1}/examples/user_management.py +0 -0
  28. {ferogram-0.1.9 → ferogram-0.2.1}/examples/userbot.py +0 -0
  29. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/__init__.py +0 -0
  30. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/client.py +0 -0
  31. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/filters.py +0 -0
  32. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/logging.py +0 -0
  33. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/py.typed +0 -0
  34. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/__init__.py +0 -0
  35. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/api/__init__.py +0 -0
  36. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/api/functions.py +0 -0
  37. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/api/types.py +0 -0
  38. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/codegen.py +0 -0
  39. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/__init__.py +0 -0
  40. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/_tl_schema.py +0 -0
  41. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/__init__.py +0 -0
  42. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/account.py +0 -0
  43. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/auth.py +0 -0
  44. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/bots.py +0 -0
  45. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/channels.py +0 -0
  46. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/chatlists.py +0 -0
  47. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/contacts.py +0 -0
  48. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/folders.py +0 -0
  49. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/fragment.py +0 -0
  50. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/help.py +0 -0
  51. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/langpack.py +0 -0
  52. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/messages.py +0 -0
  53. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/payments.py +0 -0
  54. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/phone.py +0 -0
  55. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/photos.py +0 -0
  56. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/premium.py +0 -0
  57. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/smsjobs.py +0 -0
  58. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/stats.py +0 -0
  59. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/stickers.py +0 -0
  60. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/stories.py +0 -0
  61. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/updates.py +0 -0
  62. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/upload.py +0 -0
  63. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/functions/users.py +0 -0
  64. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/__init__.py +0 -0
  65. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/_base.py +0 -0
  66. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/account.py +0 -0
  67. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/auth.py +0 -0
  68. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/bots.py +0 -0
  69. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/channels.py +0 -0
  70. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/chatlists.py +0 -0
  71. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/contacts.py +0 -0
  72. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/fragment.py +0 -0
  73. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/help.py +0 -0
  74. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/messages.py +0 -0
  75. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/payments.py +0 -0
  76. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/phone.py +0 -0
  77. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/photos.py +0 -0
  78. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/premium.py +0 -0
  79. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/smsjobs.py +0 -0
  80. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/stats.py +0 -0
  81. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/stickers.py +0 -0
  82. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/storage.py +0 -0
  83. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/stories.py +0 -0
  84. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/updates.py +0 -0
  85. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/upload.py +0 -0
  86. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/generated/types/users.py +0 -0
  87. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/proxy.py +0 -0
  88. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw/tl.py +0 -0
  89. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/raw_api.tl +0 -0
  90. {ferogram-0.1.9 → ferogram-0.2.1}/ferogram/types.py +0 -0
  91. {ferogram-0.1.9 → ferogram-0.2.1}/src/raw.rs +0 -0
  92. {ferogram-0.1.9 → ferogram-0.2.1}/src/types.rs +0 -0
@@ -274,16 +274,19 @@ dependencies = [
274
274
 
275
275
  [[package]]
276
276
  name = "ferogram"
277
- version = "0.3.6"
277
+ version = "0.4.0"
278
278
  source = "registry+https://github.com/rust-lang/crates.io-index"
279
- checksum = "fcdbb324f99754ac070a6222d288fd84b4e7d2a32e035cbbca0326c4ec11fbbc"
279
+ checksum = "08d5a9e0d5162e8eb981904b15c41d7f3089640632d51a3ffcab7713c4df409b"
280
280
  dependencies = [
281
281
  "async-trait",
282
282
  "base64",
283
283
  "chrono",
284
284
  "dashmap",
285
+ "ferogram-connect",
285
286
  "ferogram-crypto",
287
+ "ferogram-fsm",
286
288
  "ferogram-mtproto",
289
+ "ferogram-mtsender",
287
290
  "ferogram-parsers",
288
291
  "ferogram-session",
289
292
  "ferogram-tl-types",
@@ -309,11 +312,31 @@ dependencies = [
309
312
  "tracing",
310
313
  ]
311
314
 
315
+ [[package]]
316
+ name = "ferogram-connect"
317
+ version = "0.4.0"
318
+ source = "registry+https://github.com/rust-lang/crates.io-index"
319
+ checksum = "8cdc49607faad2862b676b010b02e27d13ba2047d2253362cdf958c10efff92a"
320
+ dependencies = [
321
+ "ferogram-crypto",
322
+ "ferogram-mtproto",
323
+ "ferogram-tl-types",
324
+ "flate2",
325
+ "getrandom 0.2.17",
326
+ "hmac",
327
+ "metrics",
328
+ "sha2",
329
+ "socket2 0.5.10",
330
+ "tokio",
331
+ "tokio-socks",
332
+ "tracing",
333
+ ]
334
+
312
335
  [[package]]
313
336
  name = "ferogram-crypto"
314
- version = "0.3.6"
337
+ version = "0.4.0"
315
338
  source = "registry+https://github.com/rust-lang/crates.io-index"
316
- checksum = "f485d25585c155a446e50d7a04e16f303f6c718166b0a714d6671e3c638b9bb7"
339
+ checksum = "d9df71920e40c6b6938df3ac0f4e85fe13968b48f0cbb0d0c06d98020384b02b"
317
340
  dependencies = [
318
341
  "aes",
319
342
  "ctr",
@@ -324,11 +347,24 @@ dependencies = [
324
347
  "sha2",
325
348
  ]
326
349
 
350
+ [[package]]
351
+ name = "ferogram-fsm"
352
+ version = "0.4.0"
353
+ source = "registry+https://github.com/rust-lang/crates.io-index"
354
+ checksum = "751b3d201d60c38679fb363022bc65646144943b79bc17e37997d8738e2f5143"
355
+ dependencies = [
356
+ "async-trait",
357
+ "dashmap",
358
+ "serde",
359
+ "serde_json",
360
+ "tokio",
361
+ ]
362
+
327
363
  [[package]]
328
364
  name = "ferogram-mtproto"
329
- version = "0.3.6"
365
+ version = "0.4.0"
330
366
  source = "registry+https://github.com/rust-lang/crates.io-index"
331
- checksum = "9afa1f669cd3182819e591125387f89ba9bf8844ead834dbd0adf846c2f731c5"
367
+ checksum = "b4aa9b9d4bf5a80270e8ee81e65055c8ed7de9b4887df7d94de48d18564c692c"
332
368
  dependencies = [
333
369
  "ferogram-crypto",
334
370
  "ferogram-tl-types",
@@ -339,11 +375,31 @@ dependencies = [
339
375
  "sha1",
340
376
  ]
341
377
 
378
+ [[package]]
379
+ name = "ferogram-mtsender"
380
+ version = "0.4.0"
381
+ source = "registry+https://github.com/rust-lang/crates.io-index"
382
+ checksum = "c577e17c573111afe208b935c0a91ab51ab4df4040366010b3b4418d1fbdfecc"
383
+ dependencies = [
384
+ "ferogram-connect",
385
+ "ferogram-crypto",
386
+ "ferogram-mtproto",
387
+ "ferogram-session",
388
+ "ferogram-tl-types",
389
+ "flate2",
390
+ "getrandom 0.2.17",
391
+ "metrics",
392
+ "sha2",
393
+ "socket2 0.5.10",
394
+ "tokio",
395
+ "tracing",
396
+ ]
397
+
342
398
  [[package]]
343
399
  name = "ferogram-parsers"
344
- version = "0.3.6"
400
+ version = "0.4.0"
345
401
  source = "registry+https://github.com/rust-lang/crates.io-index"
346
- checksum = "ca6af91b70fed33554e85c4e49a852cb457fc5285f201d523d868be918ce0573"
402
+ checksum = "2616847253ad2b3c90b18f18d8f9239275c295a378ed4cd24bddaec50df3550e"
347
403
  dependencies = [
348
404
  "ferogram-tl-types",
349
405
  "pulldown-cmark",
@@ -351,7 +407,7 @@ dependencies = [
351
407
 
352
408
  [[package]]
353
409
  name = "ferogram-py"
354
- version = "0.1.9"
410
+ version = "0.2.1"
355
411
  dependencies = [
356
412
  "ferogram",
357
413
  "hex",
@@ -362,9 +418,9 @@ dependencies = [
362
418
 
363
419
  [[package]]
364
420
  name = "ferogram-session"
365
- version = "0.3.6"
421
+ version = "0.4.0"
366
422
  source = "registry+https://github.com/rust-lang/crates.io-index"
367
- checksum = "0ec78ed52c7754d273a3e9e04969607775ff94f236b2406dee286076327209f4"
423
+ checksum = "de8ac1fbffd7bb698fe3d78038edae17544220d2cf41fe583304efa61a551e94"
368
424
  dependencies = [
369
425
  "base64",
370
426
  "tracing",
@@ -372,24 +428,24 @@ dependencies = [
372
428
 
373
429
  [[package]]
374
430
  name = "ferogram-tl-gen"
375
- version = "0.3.6"
431
+ version = "0.4.0"
376
432
  source = "registry+https://github.com/rust-lang/crates.io-index"
377
- checksum = "a41e71db9a199a5a5af641280738d514dd49e764c51ba4c0cbac0f10ed0c7a46"
433
+ checksum = "f9278407f5912be09141fa7827e39dd72851213ecf4b7c1462094a1012ffdef9"
378
434
  dependencies = [
379
435
  "ferogram-tl-parser",
380
436
  ]
381
437
 
382
438
  [[package]]
383
439
  name = "ferogram-tl-parser"
384
- version = "0.3.6"
440
+ version = "0.4.0"
385
441
  source = "registry+https://github.com/rust-lang/crates.io-index"
386
- checksum = "ed6b0a007fb90b406669d5cadd9ef993cbea12c67132f7493a75d1acac80088c"
442
+ checksum = "a2a592a2437f71ddda9ba45971bb70019c16a4cf5dd9afe941a7125232db1ff8"
387
443
 
388
444
  [[package]]
389
445
  name = "ferogram-tl-types"
390
- version = "0.3.6"
446
+ version = "0.4.0"
391
447
  source = "registry+https://github.com/rust-lang/crates.io-index"
392
- checksum = "cca9c9085ef9b2b636c3819470bd84b168cc7f58639989ab4c0996eff556ebea"
448
+ checksum = "f1b23759e460988fda70e73ed25ae4bf633e3430c60f8fbe4620198536238bd7"
393
449
  dependencies = [
394
450
  "ferogram-tl-gen",
395
451
  "ferogram-tl-parser",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "ferogram-py"
3
- version = "0.1.9"
3
+ version = "0.2.1"
4
4
  edition = "2024"
5
5
  description = "Python bindings for ferogram (Rust MTProto)"
6
6
  license = "MIT OR Apache-2.0"
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
12
12
 
13
13
  [dependencies]
14
14
  hex = "0.4"
15
- ferogram = "0.3.6"
15
+ ferogram = "0.4.0"
16
16
  pyo3 = { version = "0.24", features = ["extension-module", "abi3-py313"] }
17
17
  pyo3-async-runtimes = { version = "0.24", features = ["tokio-runtime"] }
18
18
  tokio = { version = "1", features = ["full"] }
@@ -4,7 +4,6 @@ Python bindings for the ferogram MTProto client.
4
4
 
5
5
  All client methods are async. The `peer` parameter accepts "@username", "me", or a numeric ID (int or string).
6
6
 
7
- ---
8
7
 
9
8
  ## Imports
10
9
 
@@ -36,7 +35,6 @@ from ferogram.raw.generated.functions.messages import SendMessage
36
35
  from ferogram.raw.generated.types.messages import Messages
37
36
  ```
38
37
 
39
- ---
40
38
 
41
39
  ## Client Setup
42
40
 
@@ -61,7 +59,6 @@ async with app as client:
61
59
 
62
60
  Credentials can also come from env vars: `API_ID`, `API_HASH`, `BOT_TOKEN`.
63
61
 
64
- ---
65
62
 
66
63
  ## Event Handlers
67
64
 
@@ -84,12 +81,12 @@ Decorators to register handlers. Each accepts zero or more filters.
84
81
  @app.on_shipping_query(*filters)
85
82
  @app.on_pre_checkout_query(*filters)
86
83
  @app.on_chat_boost(*filters)
84
+ @app.on_guest_chat_query(*filters)
87
85
  @app.on_raw_update(*filters)
88
86
  ```
89
87
 
90
88
  Handler signature: `async def handler(client, update):`
91
89
 
92
- ---
93
90
 
94
91
  ## Filters
95
92
 
@@ -184,13 +181,23 @@ filters.or_(f1, f2)
184
181
  filters.not_(f1)
185
182
  ```
186
183
 
187
- ---
188
184
 
189
185
  ## Messaging
190
186
 
191
187
  ```python
192
188
  await client.send_message(peer, text, parse_mode=None)
193
189
  # parse_mode: None (plain) | "html" | "markdown"
190
+ # Note: "markdown" uses MarkdownV2 format since ferogram 0.3.9.
191
+ # __text__ = Underline (was Italic in V1)
192
+ # ~text~ = Strike (was ~~text~~ in V1)
193
+ # > text = Blockquote (new)
194
+ # **> text = Expandable blockquote (new)
195
+ # HTML parse_mode supports these tags:
196
+ # <b>, <strong>, <i>, <em>, <u>, <ins>, <s>, <del>, <strike>
197
+ # <tg-spoiler>, <span class="tg-spoiler">
198
+ # <blockquote>, <blockquote expandable>
199
+ # <tg-time unix="N" format="F">, <tg-emoji emoji-id="N">
200
+ # <code>, <pre>, <pre><code class="language-X">
194
201
 
195
202
  await client.send_to_self(text)
196
203
  await client.edit_message(peer, message_id, new_text)
@@ -210,6 +217,7 @@ await client.send_reaction(peer, message_id, emoji)
210
217
  await client.read_reactions(peer)
211
218
  await client.clear_recent_reactions()
212
219
  await client.get_reaction_list(peer, msg_id, limit=100) # -> [(peer_id, emoji)]
220
+ await client.delete_reaction(peer, msg_id, participant) # report/remove a user's reaction
213
221
  await client.mark_as_read(peer)
214
222
  await client.clear_mentions(peer)
215
223
  await client.send_chat_action(peer, "typing") # or ChatAction.TYPING
@@ -236,7 +244,6 @@ await message.react(emoji)
236
244
  `reply_to_message_id`, `via_bot_id`, `grouped_id`, `has_media`, `has_photo`, `has_document`,
237
245
  `is_forwarded`, `post_author`, `view_count`, `reply_count`
238
246
 
239
- ---
240
247
 
241
248
  ## Media
242
249
 
@@ -254,7 +261,6 @@ await client.edit_chat_photo(peer, path)
254
261
  await client.delete_profile_photos()
255
262
  ```
256
263
 
257
- ---
258
264
 
259
265
  ## Polls
260
266
 
@@ -262,12 +268,19 @@ await client.delete_profile_photos()
262
268
  await client.send_poll(
263
269
  peer, question, answers=["A", "B", "C"],
264
270
  quiz=False, correct_index=None, multiple_choice=False,
271
+ public_voters=False, shuffle_answers=False,
272
+ hide_results_until_close=False,
273
+ close_period=None, # auto-close after N seconds (1-600)
274
+ close_date=None, # auto-close at unix timestamp
275
+ solution=None, # explanation shown after quiz answer
265
276
  )
266
277
  await client.send_vote(peer, msg_id, options=[b"\x00"])
267
278
  await client.get_poll_votes(peer, msg_id, limit=100) # -> [(user_id, option_bytes)]
279
+ await client.get_poll_results(peer, msg_id, poll_hash)
280
+ await client.get_poll_stats(peer, msg_id) # -> views count (int)
281
+ await client.delete_reaction(peer, msg_id, participant) # report/remove a user's reaction
268
282
  ```
269
283
 
270
- ---
271
284
 
272
285
  ## Inline Bots
273
286
 
@@ -286,7 +299,6 @@ from ferogram import InlineMessageId
286
299
  await client.edit_inline_message(InlineMessageId(dc_id=2, id_bytes=b"..."), "new text")
287
300
  ```
288
301
 
289
- ---
290
302
 
291
303
  ## Chats & Groups
292
304
 
@@ -324,7 +336,6 @@ await client.mark_dialog_read(peer)
324
336
 
325
337
  `user_id`, `first_name`, `last_name`, `username`, `bot`, `status`, `admin_rank`, `full_name`
326
338
 
327
- ---
328
339
 
329
340
  ## Forum Topics
330
341
 
@@ -335,7 +346,6 @@ await client.edit_forum_topic(peer, topic_id, title=None, closed=None, hidden=No
335
346
  await client.delete_forum_topic_history(peer, top_msg_id)
336
347
  ```
337
348
 
338
- ---
339
349
 
340
350
  ## Join Requests
341
351
 
@@ -344,7 +354,6 @@ await client.join_request(peer, user_id, approve=True)
344
354
  await client.all_join_requests(peer, approve=True, link=None)
345
355
  ```
346
356
 
347
- ---
348
357
 
349
358
  ## Account & Profile
350
359
 
@@ -364,7 +373,6 @@ await client.export_session_string() # -> str
364
373
 
365
374
  `id`, `first_name`, `last_name`, `username`, `phone`, `bot`, `full_name`, `mention`
366
375
 
367
- ---
368
376
 
369
377
  ## Contacts & Blocking
370
378
 
@@ -378,7 +386,6 @@ await client.unblock_user(peer)
378
386
  await client.get_blocked_users(limit=100) # -> [int]
379
387
  ```
380
388
 
381
- ---
382
389
 
383
390
  ## Search
384
391
 
@@ -387,7 +394,6 @@ await client.search_messages(peer, query, limit=100)
387
394
  await client.search_global(query, limit=100)
388
395
  ```
389
396
 
390
- ---
391
397
 
392
398
  ## Drafts
393
399
 
@@ -397,7 +403,6 @@ await client.clear_all_drafts()
397
403
  await client.sync_drafts()
398
404
  ```
399
405
 
400
- ---
401
406
 
402
407
  ## Notifications
403
408
 
@@ -408,7 +413,6 @@ await client.get_notify_settings(peer)
408
413
  await client.update_notify_settings(peer, mute_until=None, silent=None, show_previews=None)
409
414
  ```
410
415
 
411
- ---
412
416
 
413
417
  ## Privacy
414
418
 
@@ -424,7 +428,6 @@ await client.set_privacy(PrivacyKey.PHONE_NUMBER, PrivacyRule.ALLOW_CONTACTS)
424
428
 
425
429
  **PrivacyRule:** `ALLOW_ALL`, `ALLOW_CONTACTS`, `DISALLOW_ALL`, `DISALLOW_CONTACTS`
426
430
 
427
- ---
428
431
 
429
432
  ## Sessions & Auth
430
433
 
@@ -437,7 +440,6 @@ token, expires = await client.export_login_token()
437
440
  username = await client.check_qr_login(token) # None if still pending
438
441
  ```
439
442
 
440
- ---
441
443
 
442
444
  ## Bot Management
443
445
 
@@ -449,7 +451,6 @@ await client.get_bot_info(lang_code="")
449
451
  await client.open_mini_app(peer, app_type="main", app_value="") # -> MiniAppSession
450
452
  ```
451
453
 
452
- ---
453
454
 
454
455
  ## Stats
455
456
 
@@ -457,9 +458,9 @@ await client.open_mini_app(peer, app_type="main", app_value="") # -> MiniAppSe
457
458
  await client.get_broadcast_stats(peer)
458
459
  await client.get_megagroup_stats(peer)
459
460
  await client.get_game_high_scores(peer, msg_id, user_id) # -> [(position, user_id, score)]
461
+ await client.get_poll_stats(peer, msg_id) # -> views count (int)
460
462
  ```
461
463
 
462
- ---
463
464
 
464
465
  ## Payments
465
466
 
@@ -473,7 +474,6 @@ await client.send_invoice(
473
474
  )
474
475
  ```
475
476
 
476
- ---
477
477
 
478
478
  ## Peer Resolution
479
479
 
@@ -483,7 +483,6 @@ await client.resolve_username(username) # -> int
483
483
  await client.warm_peer_cache_from_dialogs()
484
484
  ```
485
485
 
486
- ---
487
486
 
488
487
  ## Raw API
489
488
 
@@ -498,7 +497,6 @@ All four styles produce identical TL requests. The difference is only ergonomics
498
497
  | `from ferogram.raw.api import functions` | Compatibility only. Do not use for new code. |
499
498
  | Direct `generated` import | Advanced use: tooling, debugging, type checking. |
500
499
 
501
- ---
502
500
 
503
501
  ### 1. Namespace proxy (recommended)
504
502
 
@@ -524,7 +522,6 @@ result = await client.raw.channels.GetFullChannel(
524
522
  )
525
523
  ```
526
524
 
527
- ---
528
525
 
529
526
  ### 2. `functions` import (recommended for explicit control)
530
527
 
@@ -561,7 +558,6 @@ result = await client.invoke(
561
558
  result = await client(functions.users.GetFullUser(id=await client.resolve_peer("@user")))
562
559
  ```
563
560
 
564
- ---
565
561
 
566
562
  ### 3. `api` import (compatibility only)
567
563
 
@@ -579,7 +575,6 @@ result = await client.invoke(
579
575
  )
580
576
  ```
581
577
 
582
- ---
583
578
 
584
579
  ### 4. Direct class import (advanced)
585
580
 
@@ -605,7 +600,6 @@ result = await client.invoke(
605
600
 
606
601
  The `generated/` directory is internal codegen output. Direct imports from it are considered advanced usage and may change between versions.
607
602
 
608
- ---
609
603
 
610
604
  ## Logging
611
605
 
@@ -616,7 +610,18 @@ fero_log.setup() # INFO to stderr
616
610
  fero_log.setup(level=10) # DEBUG
617
611
  ```
618
612
 
619
- ---
613
+
614
+ ## GuestChatQuery
615
+
616
+ Fired when a bot receives a guest-chat inline query (`updateBotGuestChatQuery`). Bots only.
617
+
618
+ ```python
619
+ @app.on_guest_chat_query()
620
+ async def handler(client, query):
621
+ # query.query_id int
622
+ # query.qts int
623
+ pass
624
+ ```
620
625
 
621
626
  ## ChatAction
622
627
 
@@ -635,8 +640,4 @@ ChatAction.RECORD_ROUND
635
640
  ChatAction.UPLOAD_ROUND
636
641
  ChatAction.CANCEL
637
642
  ```
638
- ---
639
-
640
- Thanks for reading.
641
643
 
642
- Have a great experience with ferogram.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ferogram
3
- Version: 0.1.9
3
+ Version: 0.2.1
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "ferogram"
7
- version = "0.1.9"
7
+ version = "0.2.1"
8
8
  description = "Python wrapper for ferogram, blazing-fast Rust MTProto library for Telegram."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT OR Apache-2.0" }
@@ -45,21 +45,37 @@ pub struct ClientBuilder {
45
45
  pub api_id: i32,
46
46
  pub api_hash: String,
47
47
  pub session: String,
48
+ pub allow_zero_hash: bool,
48
49
  }
49
50
 
50
51
  #[pymethods]
51
52
  impl ClientBuilder {
53
+ /// For bots only: skip needing a cached access hash.
54
+ /// Do NOT enable on user accounts.
55
+ fn experimental_allow_zero_hash(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
56
+ slf.allow_zero_hash = true;
57
+ slf
58
+ }
59
+
52
60
  fn connect<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
53
- let (api_id, api_hash, session) =
54
- (self.api_id, self.api_hash.clone(), self.session.clone());
61
+ let (api_id, api_hash, session, allow_zero_hash) = (
62
+ self.api_id,
63
+ self.api_hash.clone(),
64
+ self.session.clone(),
65
+ self.allow_zero_hash,
66
+ );
55
67
  future_into_py(py, async move {
56
- let (client, shutdown) = ferogram::Client::builder()
68
+ let mut builder = ferogram::Client::builder()
57
69
  .api_id(api_id)
58
70
  .api_hash(api_hash)
59
- .session(session)
60
- .connect()
61
- .await
62
- .map_err(py_err)?;
71
+ .session(session);
72
+ if allow_zero_hash {
73
+ builder = builder.experimental_features(ferogram::ExperimentalFeatures {
74
+ allow_zero_hash: true,
75
+ ..Default::default()
76
+ });
77
+ }
78
+ let (client, shutdown) = builder.connect().await.map_err(py_err)?;
63
79
  Ok(crate::client::make_client(client, shutdown))
64
80
  })
65
81
  }
@@ -18,6 +18,7 @@ use std::sync::Arc;
18
18
  use tokio::sync::Mutex;
19
19
 
20
20
  use crate::{auth::*, message::from_incoming, py_err, types::*};
21
+ use ferogram::PeerExt;
21
22
  use ferogram::tl;
22
23
 
23
24
  #[pyclass]
@@ -78,6 +79,7 @@ impl Client {
78
79
  api_id,
79
80
  api_hash,
80
81
  session,
82
+ allow_zero_hash: false,
81
83
  }
82
84
  }
83
85
 
@@ -238,8 +240,8 @@ impl Client {
238
240
  fn send_to_self<'py>(&self, py: Python<'py>, text: String) -> PyResult<Bound<'py, PyAny>> {
239
241
  let c = Arc::clone(&self.inner);
240
242
  future_into_py(py, async move {
241
- c.send_to_self(text).await.map_err(py_err)?;
242
- Ok(())
243
+ let m = c.send_to_self(text).await.map_err(py_err)?;
244
+ Ok(from_incoming(m, Some(c)))
243
245
  })
244
246
  }
245
247
 
@@ -283,9 +285,14 @@ impl Client {
283
285
  ) -> PyResult<Bound<'py, PyAny>> {
284
286
  let c = Arc::clone(&self.inner);
285
287
  future_into_py(py, async move {
286
- c.forward_messages(destination, &message_ids, source)
287
- .await
288
- .map_err(py_err)?;
288
+ c.forward_messages(
289
+ destination,
290
+ &message_ids,
291
+ source,
292
+ ferogram::ForwardOptions::default(),
293
+ )
294
+ .await
295
+ .map_err(py_err)?;
289
296
  Ok(())
290
297
  })
291
298
  }
@@ -1117,7 +1124,18 @@ impl Client {
1117
1124
  // polls
1118
1125
 
1119
1126
  #[allow(clippy::too_many_arguments)]
1120
- #[pyo3(signature = (peer, question, answers, quiz = false, correct_index = None, multiple_choice = false))]
1127
+ #[pyo3(signature = (
1128
+ peer, question, answers,
1129
+ quiz = false,
1130
+ correct_index = None,
1131
+ multiple_choice = false,
1132
+ public_voters = false,
1133
+ shuffle_answers = false,
1134
+ hide_results_until_close = false,
1135
+ close_period = None,
1136
+ close_date = None,
1137
+ solution = None,
1138
+ ))]
1121
1139
  fn send_poll<'py>(
1122
1140
  &self,
1123
1141
  py: Python<'py>,
@@ -1127,17 +1145,87 @@ impl Client {
1127
1145
  quiz: bool,
1128
1146
  correct_index: Option<usize>,
1129
1147
  multiple_choice: bool,
1148
+ public_voters: bool,
1149
+ shuffle_answers: bool,
1150
+ hide_results_until_close: bool,
1151
+ close_period: Option<i32>,
1152
+ close_date: Option<i32>,
1153
+ solution: Option<String>,
1154
+ ) -> PyResult<Bound<'py, PyAny>> {
1155
+ let c = Arc::clone(&self.inner);
1156
+ future_into_py(py, async move {
1157
+ let mut builder = ferogram::PollBuilder::new(question).answers(answers);
1158
+ if quiz {
1159
+ builder = builder.quiz(true);
1160
+ }
1161
+ if let Some(idx) = correct_index {
1162
+ builder = builder.correct_index(idx);
1163
+ }
1164
+ if multiple_choice {
1165
+ builder = builder.multiple_choice(true);
1166
+ }
1167
+ if public_voters {
1168
+ builder = builder.public_voters(true);
1169
+ }
1170
+ if shuffle_answers {
1171
+ builder = builder.shuffle_answers(true);
1172
+ }
1173
+ if hide_results_until_close {
1174
+ builder = builder.hide_results_until_close(true);
1175
+ }
1176
+ if let Some(secs) = close_period {
1177
+ builder = builder.close_period(secs);
1178
+ }
1179
+ if let Some(ts) = close_date {
1180
+ builder = builder.close_date(ts);
1181
+ }
1182
+ if let Some(text) = solution {
1183
+ builder = builder.solution(text);
1184
+ }
1185
+ c.send_poll(peer, builder).await.map_err(py_err)?;
1186
+ Ok(())
1187
+ })
1188
+ }
1189
+
1190
+ fn delete_reaction<'py>(
1191
+ &self,
1192
+ py: Python<'py>,
1193
+ peer: String,
1194
+ msg_id: i32,
1195
+ participant: String,
1130
1196
  ) -> PyResult<Bound<'py, PyAny>> {
1131
1197
  let c = Arc::clone(&self.inner);
1132
1198
  future_into_py(py, async move {
1133
- let refs: Vec<&str> = answers.iter().map(String::as_str).collect();
1134
- c.send_poll(peer, question, &refs, quiz, correct_index, multiple_choice)
1199
+ c.delete_reaction(peer, msg_id, participant)
1135
1200
  .await
1136
1201
  .map_err(py_err)?;
1137
1202
  Ok(())
1138
1203
  })
1139
1204
  }
1140
1205
 
1206
+ fn get_poll_stats<'py>(
1207
+ &self,
1208
+ py: Python<'py>,
1209
+ peer: String,
1210
+ msg_id: i32,
1211
+ ) -> PyResult<Bound<'py, PyAny>> {
1212
+ let c = Arc::clone(&self.inner);
1213
+ future_into_py(py, async move {
1214
+ let stats = c.get_poll_stats(peer, msg_id).await.map_err(py_err)?;
1215
+ let json = match stats.votes_graph {
1216
+ tl::enums::StatsGraph::StatsGraph(g) => {
1217
+ let tl::enums::DataJson::DataJson(dj) = g.json;
1218
+ dj.data
1219
+ }
1220
+ tl::enums::StatsGraph::Async(a) => format!("async:{}", a.token),
1221
+ tl::enums::StatsGraph::Error(e) => {
1222
+ return Err(pyo3::exceptions::PyRuntimeError::new_err(e.error));
1223
+ }
1224
+ };
1225
+ Ok(json)
1226
+ })
1227
+ }
1228
+
1141
1229
  // options: list of 1-byte option indices (e.g. [b'\x00'] for first option)
1142
1230
  fn send_vote<'py>(
1143
1231
  &self,
@@ -1340,7 +1428,12 @@ impl Client {
1340
1428
  let c = Arc::clone(&self.inner);
1341
1429
  future_into_py(py, async move {
1342
1430
  let msgs = c
1343
- .forward_messages(destination, &message_ids, source)
1431
+ .forward_messages(
1432
+ destination,
1433
+ &message_ids,
1434
+ source,
1435
+ ferogram::ForwardOptions::default(),
1436
+ )
1344
1437
  .await
1345
1438
  .map_err(py_err)?;
1346
1439
  Ok(msgs
@@ -2408,8 +2501,8 @@ impl Client {
2408
2501
  silent: None,
2409
2502
  mute_until: Some(mute_until),
2410
2503
  sound: None,
2411
- stories_muted: false,
2412
- stories_hide_sender: false,
2504
+ stories_muted: Some(false),
2505
+ stories_hide_sender: Some(false),
2413
2506
  stories_sound: None,
2414
2507
  },
2415
2508
  );
@@ -2464,22 +2557,11 @@ impl Client {
2464
2557
  let pairs: Vec<(i64, Vec<u8>)> = result
2465
2558
  .votes
2466
2559
  .into_iter()
2467
- .map(|v| {
2468
- fn peer_id(p: &tl::enums::Peer) -> i64 {
2469
- match p {
2470
- tl::enums::Peer::User(u) => u.user_id,
2471
- tl::enums::Peer::Chat(c) => c.chat_id,
2472
- tl::enums::Peer::Channel(ch) => ch.channel_id,
2473
- }
2474
- }
2475
- match v {
2476
- tl::enums::MessagePeerVote::MessagePeerVote(x) => {
2477
- (peer_id(&x.peer), x.option)
2478
- }
2479
- tl::enums::MessagePeerVote::InputOption(x) => (peer_id(&x.peer), vec![]),
2480
- tl::enums::MessagePeerVote::Multiple(x) => {
2481
- (peer_id(&x.peer), x.options.into_iter().flatten().collect())
2482
- }
2560
+ .map(|v| match v {
2561
+ tl::enums::MessagePeerVote::MessagePeerVote(x) => (x.peer.bare_id(), x.option),
2562
+ tl::enums::MessagePeerVote::InputOption(x) => (x.peer.bare_id(), vec![]),
2563
+ tl::enums::MessagePeerVote::Multiple(x) => {
2564
+ (x.peer.bare_id(), x.options.into_iter().flatten().collect())
2483
2565
  }
2484
2566
  })
2485
2567
  .collect();
@@ -2551,22 +2633,22 @@ impl Client {
2551
2633
  ) -> PyResult<Bound<'py, PyAny>> {
2552
2634
  let c = Arc::clone(&self.inner);
2553
2635
  future_into_py(py, async move {
2554
- let price_refs: Vec<(&str, i64)> =
2555
- prices.iter().map(|(l, a)| (l.as_str(), *a)).collect();
2556
2636
  let msg = c
2557
2637
  .send_invoice(
2558
2638
  peer,
2559
2639
  title,
2560
2640
  description,
2561
2641
  payload,
2562
- currency,
2563
- &price_refs,
2564
- photo_url,
2565
- need_name,
2566
- need_phone,
2567
- need_email,
2568
- need_shipping_address,
2569
- is_flexible,
2642
+ ferogram::InvoiceOptions {
2643
+ currency,
2644
+ prices,
2645
+ photo_url,
2646
+ need_name,
2647
+ need_phone,
2648
+ need_email,
2649
+ need_shipping_address,
2650
+ is_flexible,
2651
+ },
2570
2652
  )
2571
2653
  .await
2572
2654
  .map_err(py_err)?;
@@ -2939,8 +3021,8 @@ impl Client {
2939
3021
  silent,
2940
3022
  mute_until,
2941
3023
  sound: None,
2942
- stories_muted: false,
2943
- stories_hide_sender: false,
3024
+ stories_muted: Some(false),
3025
+ stories_hide_sender: Some(false),
2944
3026
  stories_sound: None,
2945
3027
  },
2946
3028
  );
@@ -60,10 +60,12 @@ fn _ferogram(m: &Bound<'_, PyModule>) -> PyResult<()> {
60
60
  m.add_class::<updates::PollVote>()?;
61
61
  m.add_class::<updates::BotStopped>()?;
62
62
  m.add_class::<updates::RawUpdate>()?;
63
- // new in 0.3.6
63
+ // new in 0.3.6 / updated in 0.3.7 (binding v0.2.0)
64
64
  m.add_class::<types::ShippingQuery>()?;
65
65
  m.add_class::<types::PreCheckoutQuery>()?;
66
66
  m.add_class::<types::ChatBoost>()?;
67
67
  m.add_class::<types::MiniAppSession>()?;
68
+ // new in 0.3.9 (binding v0.2.1)
69
+ m.add_class::<updates::GuestChatQuery>()?;
68
70
  Ok(())
69
71
  }
@@ -203,7 +203,7 @@ impl Message {
203
203
  let ids = vec![self.id];
204
204
  future_into_py(py, async move {
205
205
  client
206
- .forward_messages(peer, &ids, src)
206
+ .forward_messages(peer, &ids, src, ferogram::ForwardOptions::default())
207
207
  .await
208
208
  .map_err(py_err)?;
209
209
  Ok(())
@@ -11,6 +11,7 @@
11
11
  // If you use or modify this code, keep this notice at the top of the file
12
12
  // and include the LICENSE-MIT or LICENSE-APACHE file from this repository.
13
13
 
14
+ use ferogram::PeerExt;
14
15
  use ferogram::tl;
15
16
  use pyo3::prelude::*;
16
17
  use pyo3_async_runtimes::tokio::future_into_py;
@@ -21,14 +22,6 @@ use crate::{message::from_incoming, py_err};
21
22
 
22
23
  // helpers
23
24
 
24
- fn peer_to_id(p: &tl::enums::Peer) -> i64 {
25
- match p {
26
- tl::enums::Peer::User(u) => u.user_id,
27
- tl::enums::Peer::Chat(c) => c.chat_id,
28
- tl::enums::Peer::Channel(c) => c.channel_id,
29
- }
30
- }
31
-
32
25
  fn reaction_str(r: &tl::enums::Reaction) -> String {
33
26
  match r {
34
27
  tl::enums::Reaction::Emoji(e) => e.emoticon.clone(),
@@ -326,6 +319,24 @@ impl BotStopped {
326
319
  }
327
320
  }
328
321
 
322
+ /// A bot received a guest-chat inline query (bots only).
323
+ #[pyclass]
324
+ pub struct GuestChatQuery {
325
+ #[pyo3(get)]
326
+ pub query_id: i64,
327
+ #[pyo3(get)]
328
+ pub qts: i32,
329
+ #[allow(dead_code)]
330
+ pub(crate) client: Arc<ferogram::Client>,
331
+ }
332
+
333
+ #[pymethods]
334
+ impl GuestChatQuery {
335
+ fn __repr__(&self) -> String {
336
+ format!("GuestChatQuery(query_id={})", self.query_id)
337
+ }
338
+ }
339
+
329
340
  // raw fallback for unmapped update types
330
341
  #[pyclass]
331
342
  pub struct RawUpdate {
@@ -378,7 +389,7 @@ pub fn update_to_py(
378
389
  )
379
390
  }
380
391
  ferogram::update::Update::CallbackQuery(q) => {
381
- let chat_id = q.chat_peer.as_ref().map(peer_to_id);
392
+ let chat_id = q.chat_peer.as_ref().map(|p| p.bare_id());
382
393
  ok!(
383
394
  "callback_query",
384
395
  CallbackQuery {
@@ -399,7 +410,7 @@ pub fn update_to_py(
399
410
  user_id: q.user_id,
400
411
  query: q.query,
401
412
  offset: q.offset,
402
- peer_id: q.peer.as_ref().map(peer_to_id),
413
+ peer_id: q.peer.as_ref().map(|p| p.bare_id()),
403
414
  }
404
415
  )
405
416
  }
@@ -437,7 +448,7 @@ pub fn update_to_py(
437
448
  ok!(
438
449
  "chat_action",
439
450
  ChatAction {
440
- peer_id: peer_to_id(&a.peer),
451
+ peer_id: a.peer.bare_id(),
441
452
  user_id: a.user_id,
442
453
  action: action_str(&a.action).to_string(),
443
454
  }
@@ -459,7 +470,7 @@ pub fn update_to_py(
459
470
  ok!(
460
471
  "join_request",
461
472
  JoinRequest {
462
- peer_id: peer_to_id(&r.peer),
473
+ peer_id: r.peer.bare_id(),
463
474
  user_id: r.user_id,
464
475
  about: r.about,
465
476
  date: r.date,
@@ -470,10 +481,10 @@ pub fn update_to_py(
470
481
  ok!(
471
482
  "message_reaction",
472
483
  MessageReaction {
473
- peer_id: peer_to_id(&r.peer),
484
+ peer_id: r.peer.bare_id(),
474
485
  msg_id: r.msg_id,
475
486
  date: r.date,
476
- actor_id: peer_to_id(&r.actor),
487
+ actor_id: r.actor.bare_id(),
477
488
  old_reactions: r.old_reactions.iter().map(reaction_str).collect(),
478
489
  new_reactions: r.new_reactions.iter().map(reaction_str).collect(),
479
490
  }
@@ -484,7 +495,7 @@ pub fn update_to_py(
484
495
  "poll_vote",
485
496
  PollVote {
486
497
  poll_id: v.poll_id,
487
- peer_id: peer_to_id(&v.peer),
498
+ peer_id: v.peer.bare_id(),
488
499
  positions: v.positions,
489
500
  }
490
501
  )
@@ -540,6 +551,16 @@ pub fn update_to_py(
540
551
  }
541
552
  )
542
553
  }
554
+ ferogram::update::Update::GuestChatQuery(q) => {
555
+ ok!(
556
+ "guest_chat_query",
557
+ GuestChatQuery {
558
+ query_id: q.query_id,
559
+ qts: q.qts,
560
+ client,
561
+ }
562
+ )
563
+ }
543
564
  ferogram::update::Update::Raw(r) => {
544
565
  let name = format!("{:?}", r.inner)
545
566
  .split('(')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes