telegem 3.3.0 → 3.4.0

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.
data/lib/core/composer.rb CHANGED
@@ -1,4 +1,4 @@
1
- # lib/core/composer.rb - HTTPX VERSION (NO ASYNC GEM)
1
+ # lib/core/composer.rb
2
2
  module Telegem
3
3
  module Core
4
4
  class Composer
@@ -40,7 +40,7 @@ module Telegem
40
40
  # Call the middleware with next in chain
41
41
  middleware_instance.call(context, next_middleware)
42
42
  else
43
- raise "Invalid middleware: #{middleware.class}"
43
+ raise "Invalid middleware: #{middleware.inspect} does not respond to :call"
44
44
  end
45
45
  end
46
46
  end
@@ -58,4 +58,4 @@ module Telegem
58
58
  end
59
59
  end
60
60
  end
61
- end
61
+ end
data/lib/core/context.rb CHANGED
@@ -436,16 +436,8 @@ module Telegem
436
436
  scene&.next_step(self, step_name)
437
437
  end
438
438
  def with_typing(&block)
439
- thread = Thread.new do
440
- while @typing_active
441
- typing
442
- sleep 5
443
- end
444
- end
445
- result = block.call
446
- @typing_active = false
447
- thread.join
448
- result
439
+ typing
440
+ block.call
449
441
  end
450
442
 
451
443
  def command?
@@ -463,6 +455,20 @@ module Telegem
463
455
  scene.enter(self, options[:step], options.except(:step))
464
456
  scene_name
465
457
  end
458
+
459
+ # Essential 9.6 methods for context.rb
460
+ def business_connection_id
461
+ message&.business_connection_id || @update.business_connection_id
462
+ end
463
+
464
+ def reply_draft(text, **options)
465
+ return unless chat
466
+ @bot.api.call('sendMessageDraft', { chat_id: chat.id, text: text }.merge(options))
467
+ end
468
+
469
+ def managed_bot
470
+ @update.managed_bot
471
+ end
466
472
 
467
473
  def logger
468
474
  @bot.logger
@@ -488,4 +494,4 @@ module Telegem
488
494
  end
489
495
  end
490
496
  end
491
- end
497
+ end
data/lib/plugins/cc ADDED
@@ -0,0 +1,3 @@
1
+ curl -H "Content-Type: application/json" \
2
+ -d '{"world": "hello world", "from": "es", "to": "fr"}' \
3
+ https://zen-drx-api.onrender.com
@@ -179,5 +179,5 @@ module Telegem
179
179
  end
180
180
  end
181
181
  end
182
- end
183
- end
182
+ end
183
+ end
@@ -0,0 +1,43 @@
1
+ require "httparty"
2
+
3
+ module Telegem
4
+ module Plugins
5
+ class Translate
6
+ def initialize(word, from, to)
7
+ @word = word
8
+ @from = from
9
+ @to = to
10
+ start_translating
11
+ end
12
+
13
+ def start_translating
14
+ url = "https://zen-drx-api.onrender.com/api/translate"
15
+ options = {
16
+ body: {
17
+ word: @word,
18
+ from: @from,
19
+ to: @to
20
+ }.to_json,
21
+ headers: {
22
+ 'Content-Type' => 'application/json'
23
+ }
24
+ }
25
+ response = HTTParty.post(url, options)
26
+ if response.success?
27
+ translation = response.parsed_response["translation"]
28
+ {
29
+ error: "false",
30
+ translation: translation
31
+ }
32
+ else
33
+ {
34
+ error: "an error occurred",
35
+ code: "#{response.code}"
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+
@@ -1,125 +1,108 @@
1
- # lib/session/memory_store.rb - PRODUCTION READY
1
+ # lib/session/memory_store.rb
2
+ require 'json'
3
+ require 'time'
4
+ require 'fileutils'
5
+
2
6
  module Telegem
3
7
  module Session
4
8
  class MemoryStore
5
- def initialize
9
+ def initialize(
10
+ default_ttl: 300,
11
+ cleanup_interval: 300,
12
+ backup_path: nil,
13
+ backup_interval: 60
14
+ )
6
15
  @store = {}
7
16
  @ttls = {}
8
- @default_ttl = 300 # 5 minutes
9
- @cleanup_interval = 60 # Clean expired every minute
17
+ @default_ttl = default_ttl
18
+ @cleanup_interval = cleanup_interval
19
+ @backup_path = backup_path
20
+ @backup_interval = backup_interval
21
+
10
22
  @last_cleanup = Time.now
23
+ @last_backup = Time.now
24
+
25
+ restore! if @backup_path && File.exist?(@backup_path)
11
26
  end
12
27
 
13
- # Store with optional TTL
14
28
  def set(key, value, ttl: nil)
15
- auto_cleanup
16
- key_s = key.to_s
17
- @store[key_s] = value
18
- @ttls[key_s] = Time.now + (ttl || @default_ttl)
19
- value
20
- end
29
+ auto_cleanup
30
+ key_s = key.to_s
21
31
 
22
- # Get value if not expired
23
- def get(key)
24
- key_s = key.to_s
25
- return nil unless @store.key?(key_s)
26
-
27
- # Auto-clean if expired
28
- if expired?(key_s)
29
- delete(key_s)
30
- return nil
31
- end
32
-
33
- @store[key_s]
34
- end
32
+ @store[key_s] = value
33
+ @ttls[key_s] = Time.now + (ttl || @default_ttl)
35
34
 
36
- # Check if key exists and not expired
37
- def exist?(key)
38
- key_s = key.to_s
39
- return false unless @store.key?(key_s)
40
- !expired?(key_s)
41
- end
35
+ auto_backup
36
+ value
37
+ end
42
38
 
43
- # Delete key
44
- def delete(key)
45
- key_s = key.to_s
46
- @store.delete(key_s)
47
- @ttls.delete(key_s)
48
- true
49
- end
39
+ def get(key)
40
+ key_s = key.to_s
41
+ return nil unless @store.key?(key_s)
50
42
 
51
- # Increment counter (for rate limiting)
52
- def increment(key, amount = 1, ttl: nil)
53
- key_s = key.to_s
54
- current = get(key_s) || 0
55
- new_value = current + amount
56
- set(key_s, new_value, ttl: ttl)
57
- new_value
43
+ if expired?(key_s)
44
+ delete(key_s)
45
+ return nil
58
46
  end
59
47
 
60
- # Decrement counter
61
- def decrement(key, amount = 1)
62
- increment(key, -amount)
48
+ @store[key_s]
63
49
  end
64
50
 
65
- # Clear expired entries (auto-called)
66
- def cleanup
67
- now = Time.now
68
- @ttls.each do |key, expires|
69
- if now > expires
70
- @store.delete(key)
71
- @ttls.delete(key)
72
- end
73
- end
74
- @last_cleanup = now
75
- end
76
-
77
- # Clear everything
78
- def clear
79
- @store.clear
80
- @ttls.clear
81
- @last_cleanup = Time.now
82
- end
83
-
84
- # Get all keys (non-expired)
85
- def keys
86
- auto_cleanup
87
- @store.keys.select { |k| !expired?(k) }
88
- end
51
+ def delete(key)
52
+ key_s = key.to_s
53
+ @store.delete(key_s)
54
+ @ttls.delete(key_s)
55
+ true
56
+ end
89
57
 
90
- # Get size (non-expired entries)
91
- def size
92
- keys.size
58
+ def increment(key, amount = 1, ttl: nil)
59
+ current = get(key) || 0
60
+ # Ensure we are working with numbers
61
+ val = current.to_i rescue 0
62
+ new_val = val + amount
63
+ set(key, new_val, ttl: ttl)
64
+ new_val
93
65
  end
94
66
 
95
- def empty?
96
- size == 0
67
+ # --- Persistence Logic (The "Telecr" Way) ---
68
+
69
+ def backup!
70
+ return unless @backup_path
71
+
72
+ # 1. Prepare data
73
+ data = {
74
+ "store" => @store,
75
+ "ttls" => @ttls.transform_values(&:to_i), # Save as Unix timestamp
76
+ "timestamp" => Time.now.to_i
77
+ }
78
+
79
+ # 2. Ensure directory exists
80
+ FileUtils.mkdir_p(File.dirname(@backup_path))
81
+
82
+ # 3. ATOMIC WRITE: Write to temp, then rename
83
+ temp_path = "#{@backup_path}.tmp"
84
+ File.write(temp_path, JSON.generate(data))
85
+ File.rename(temp_path, @backup_path)
86
+
87
+ @last_backup = Time.now
97
88
  end
98
89
 
99
- # Get TTL remaining in seconds
100
- def ttl(key)
101
- key_s = key.to_s
102
- return -1 unless @ttls[key_s]
103
-
104
- remaining = @ttls[key_s] - Time.now
105
- remaining > 0 ? remaining.ceil : -1
106
- end
90
+ def restore!
91
+ return unless @backup_path && File.exist?(@backup_path)
107
92
 
108
- # Set TTL for existing key
109
- def expire(key, ttl)
110
- key_s = key.to_s
111
- return false unless @store.key?(key_s)
93
+ begin
94
+ raw = JSON.parse(File.read(@backup_path))
112
95
 
113
- @ttls[key_s] = Time.now + ttl
114
- true
115
- end
96
+ @store.clear
97
+ @ttls.clear
116
98
 
117
- # Redis-like scan for pattern matching
118
- def scan(pattern = "*", count: 10)
119
- auto_cleanup
120
- regex = pattern_to_regex(pattern)
121
- matching_keys = @store.keys.select { |k| k.match?(regex) && !expired?(k) }
122
- matching_keys.first(count)
99
+ raw["store"].each { |k, v| @store[k] = v }
100
+ raw["ttls"].each do |k, v|
101
+ @ttls[k] = Time.at(v)
102
+ end
103
+ rescue => e
104
+ warn "Telegem: Failed to restore backup: #{e.message}"
105
+ end
123
106
  end
124
107
 
125
108
  private
@@ -129,15 +112,19 @@ module Telegem
129
112
  end
130
113
 
131
114
  def auto_cleanup
132
- if Time.now - @last_cleanup > @cleanup_interval
133
- cleanup
115
+ if (Time.now - @last_cleanup) > @cleanup_interval
116
+ now = Time.now
117
+ expired_keys = @ttls.select { |_, expires| now > expires }.keys
118
+ expired_keys.each { |k| delete(k) }
119
+ @last_cleanup = now
134
120
  end
135
121
  end
136
122
 
137
- def pattern_to_regex(pattern)
138
- regex_str = pattern.gsub('*', '.*').gsub('?', '.')
139
- Regexp.new("^#{regex_str}$")
123
+ def auto_backup
124
+ if @backup_path && (Time.now - @last_backup) > @backup_interval
125
+ backup!
126
+ end
140
127
  end
141
128
  end
142
129
  end
143
- end
130
+ end
@@ -0,0 +1,91 @@
1
+ # lib/session/redis_store.rb
2
+ require 'json'
3
+ require 'time'
4
+
5
+ module Telegem
6
+ module Session
7
+ class RedisStore
8
+ def initialize(redis_url: nil, default_ttl: 300, **options)
9
+ @default_ttl = default_ttl
10
+
11
+ # Load redis gem only when needed
12
+ begin
13
+ require 'redis'
14
+ rescue LoadError
15
+ raise "Redis store requires 'redis' gem. Add 'gem \"redis\"' to your Gemfile."
16
+ end
17
+
18
+ @redis = if redis_url
19
+ Redis.new(url: redis_url, **options)
20
+ else
21
+ Redis.new(**options)
22
+ end
23
+ end
24
+
25
+ def set(key, value, ttl: nil)
26
+ key_s = key.to_s
27
+ serialized = JSON.generate(value)
28
+ ttl_sec = ttl || @default_ttl
29
+
30
+ @redis.setex(key_s, ttl_sec, serialized)
31
+ value
32
+ end
33
+
34
+ def get(key)
35
+ key_s = key.to_s
36
+ data = @redis.get(key_s)
37
+ return nil unless data
38
+
39
+ JSON.parse(data)
40
+ rescue JSON::ParserError
41
+ nil
42
+ end
43
+
44
+ def delete(key)
45
+ key_s = key.to_s
46
+ @redis.del(key_s) > 0
47
+ end
48
+
49
+ def increment(key, amount = 1, ttl: nil)
50
+ key_s = key.to_s
51
+ ttl_sec = ttl || @default_ttl
52
+
53
+ # Atomic increment using Redis
54
+ new_val = @redis.incrby(key_s, amount)
55
+
56
+ # Set expiry if this is a new key or if TTL changed
57
+ if ttl_sec
58
+ @redis.expire(key_s, tttl_sec)
59
+ end
60
+
61
+ new_val
62
+ end
63
+
64
+ def clear_all
65
+ # Be careful with this in production
66
+ @redis.flushdb
67
+ end
68
+
69
+ def close
70
+ @redis.close
71
+ end
72
+
73
+ private
74
+
75
+ def with_retry(max_retries = 3, &block)
76
+ retries = 0
77
+ begin
78
+ block.call
79
+ rescue Redis::BaseError => e
80
+ retries += 1
81
+ if retries <= max_retries
82
+ sleep 0.1 * retries
83
+ retry
84
+ else
85
+ raise
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
data/lib/telegem.rb CHANGED
@@ -3,7 +3,7 @@ require 'logger'
3
3
  require 'json'
4
4
 
5
5
  module Telegem
6
- VERSION = "3.3.0".freeze
6
+ VERSION = "3.4.0"
7
7
  end
8
8
 
9
9
  #
@@ -50,7 +50,7 @@ module Telegem
50
50
 
51
51
  def self.webhook(bot, **options)
52
52
  require_relative 'webhook/server'
53
- Webhook::Server.setup(bot, **options)
53
+ Webhook::Server.new(bot, **options)
54
54
  end
55
55
 
56
56
  def self.info
@@ -68,7 +68,7 @@ module Telegem
68
68
  • Fluent keyboard DSL
69
69
  • Cloud-ready webhook server
70
70
 
71
- Website: https://gitlab.com/ruby-telegem/telegem
71
+ Website: https://github.com/slick-lab/telegem
72
72
  INFO
73
73
  end
74
74
  end
@@ -78,4 +78,4 @@ if ENV['TELEGEM_GLOBAL'] == 'true'
78
78
  def Telegem(token, **options)
79
79
  ::Telegem.new(token, **options)
80
80
  end
81
- end
81
+ end
@@ -116,6 +116,9 @@ module Telegem
116
116
 
117
117
  def handle_webhook_request(request)
118
118
  return [405, {}, ["Method Not Allowed"]] unless request.post?
119
+ received = request.headers['X-Telegram-Bot-Api-Secret-Token'] ||
120
+ request.headers['x-telegram-bot-api-secret-token']
121
+ return [403, {}, ["Forbidden"]] unless received == @secret_token
119
122
 
120
123
  begin
121
124
  body = request.body.read
@@ -163,8 +166,7 @@ module Telegem
163
166
 
164
167
  def set_webhook(**options)
165
168
  url = webhook_url
166
- params = { url: url }.merge(options)
167
- @bot.set_webhook(**params)
169
+ @bot.set_webhook(url, **options)
168
170
  @logger.info("Webhook set to: #{url}")
169
171
  url
170
172
  end
@@ -183,4 +185,4 @@ module Telegem
183
185
  end
184
186
  end
185
187
  end
186
- end
188
+ end
metadata CHANGED
@@ -1,28 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telegem
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
- - sick_phantom
7
+ - zendrx
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-05-05 00:00:00.000000000 Z
11
12
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: concurrent-ruby
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: 1.3.6
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: 1.3.6
26
13
  - !ruby/object:Gem::Dependency
27
14
  name: securerandom
28
15
  requirement: !ruby/object:Gem::Requirement
@@ -123,7 +110,7 @@ dependencies:
123
110
  version: '1.50'
124
111
  description: |
125
112
  Telegem is a modern Telegram Bot Framework for Ruby inspired by Telegraf.js.
126
- Built with async-first design using HTTPX, featuring scenes, middleware,
113
+ Built with async-first design using async-http, featuring scenes, middleware,
127
114
  and a clean DSL. Perfect for building scalable Telegram bots.
128
115
  email:
129
116
  - ynwghosted@icloud.com
@@ -132,22 +119,41 @@ executables:
132
119
  extensions: []
133
120
  extra_rdoc_files: []
134
121
  files:
135
- - CHANGELOG
122
+ - ".rubocop.yml"
123
+ - CHANGELOG.md
136
124
  - CODE_OF_CONDUCT.md
137
- - Contributing.md
138
125
  - Gemfile
139
126
  - Gemfile.lock
140
127
  - LICENSE
141
- - Readme.md
128
+ - README.md
142
129
  - Starts_HallofFame.md
143
130
  - assets/.gitkeep
144
131
  - assets/logo.png
145
132
  - bin/.gitkeep
146
133
  - bin/telegem-ssl
134
+ - contributing.md
147
135
  - docs/.gitkeep
136
+ - docs/api.md
137
+ - docs/bot.md
138
+ - docs/changelog.md
139
+ - docs/context.md
140
+ - docs/core_concepts.md
148
141
  - docs/ctx.md
142
+ - docs/deployment.md
143
+ - docs/error_handling.md
144
+ - docs/examples.md
149
145
  - docs/file_extract.md
150
- - examples/.gitkeep
146
+ - docs/getting_started.md
147
+ - docs/handlers.md
148
+ - docs/keyboards.md
149
+ - docs/middleware.md
150
+ - docs/plugins.md
151
+ - docs/scenes.md
152
+ - docs/sessions.md
153
+ - docs/testing.md
154
+ - docs/troubleshooting.md
155
+ - docs/types.md
156
+ - docs/webhooks.md
151
157
  - lib/api/client.rb
152
158
  - lib/api/types.rb
153
159
  - lib/core/bot.rb
@@ -159,28 +165,37 @@ files:
159
165
  - lib/markup/inline.rb
160
166
  - lib/markup/keyboard.rb
161
167
  - lib/plugins/.gitkeep
168
+ - lib/plugins/cc
162
169
  - lib/plugins/file_extract.rb
170
+ - lib/plugins/translate.rb
163
171
  - lib/session/memory_store.rb
164
172
  - lib/session/middleware.rb
173
+ - lib/session/redis.rb
165
174
  - lib/session/scene_middleware.rb
166
175
  - lib/telegem.rb
167
176
  - lib/webhook/.gitkeep
168
177
  - lib/webhook/server.rb
169
- - public/.gitkeep
170
- homepage: https://gitlab.com/ruby-telegem/telegem
178
+ homepage: https://github.com/slick-lab/telegem
171
179
  licenses:
172
180
  - MIT
173
181
  metadata:
174
- homepage_uri: https://gitlab.com/ruby-telegem/telegem/-/blob/main/README.md
175
- source_code_uri: https://gitlab.com/ruby-telegem/telegem
176
- changelog_uri: https://gitlab.com/ruby-telegem/telegem/-/blob/main/CHANGELOG.md
177
- bug_tracker_uri: https://gitlab.com/ruby-telegem/telegem/-/issues
178
- documentation_uri: https://gitlab.com/ruby-telegem/telegem/-/tree/main/docs-src?ref_type=heads
182
+ homepage_uri: https://github.com/slick-lab/telegem/-/blob/main/README.md
183
+ source_code_uri: https://github.com/slick-lab/telegem
184
+ changelog_uri: https://github.com/slick-lab/telegem/-/blob/main/CHANGELOG.md
185
+ bug_tracker_uri: https://github.com/slick-lab/telegem/-/issues
186
+ documentation_uri: https://github.com/slick-lab/telegem/tree/main/docs
179
187
  rubygems_mfa_required: 'false'
180
- post_install_message: "Thanks for installing Telegem 3.3.0!\n\n\U0001F4DA Documentation:
181
- https://gitlab.com/ruby-telegem/telegem\n\n\U0001F510 For SSL Webhooks:\nRun: telegem-ssl
182
- your-domain.com\nThis sets up Let's Encrypt certificates automatically.\n\n\U0001F916
183
- Happy bot building!\n"
188
+ post_install_message: |
189
+ Thanks for installing Telegem 3.4.0!
190
+
191
+ Documentation: https://github.com/slick-lab/telegem
192
+
193
+ For SSL Webhooks:
194
+ Run: telegem-ssl your-domain.com
195
+ This sets up Let's Encrypt certificates automatically.
196
+
197
+ join the official telegram group: https://t.me/r_telegem
198
+ Happy bot building!
184
199
  rdoc_options: []
185
200
  require_paths:
186
201
  - lib
@@ -188,14 +203,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
188
203
  requirements:
189
204
  - - ">="
190
205
  - !ruby/object:Gem::Version
191
- version: 3.1.0
206
+ version: 3.4.0
192
207
  required_rubygems_version: !ruby/object:Gem::Requirement
193
208
  requirements:
194
209
  - - ">="
195
210
  - !ruby/object:Gem::Version
196
211
  version: '0'
197
212
  requirements: []
198
- rubygems_version: 4.0.6
213
+ rubygems_version: 3.5.22
214
+ signing_key:
199
215
  specification_version: 4
200
216
  summary: Modern, fast Telegram Bot Framework for Ruby
201
217
  test_files: []