telegem 3.2.4 → 3.3.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +103 -0
- data/Gemfile +1 -1
- data/Readme.md +2 -6
- data/bin/telegem-init +32 -0
- data/bin/telegem-ssl +7 -0
- data/lib/api/client.rb +1 -10
- data/lib/api/types.rb +321 -47
- data/lib/core/bot.rb +23 -17
- data/lib/core/context.rb +9 -7
- data/lib/core/rate_limit.rb +0 -14
- data/lib/core/scene.rb +1 -1
- data/lib/markup/inline.rb +104 -0
- data/lib/markup/keyboard.rb +77 -298
- data/lib/plugins/file_extract.rb +4 -24
- data/lib/session/memory_store.rb +91 -128
- data/lib/telegem.rb +3 -2
- data/lib/webhook/server.rb +1 -1
- metadata +17 -12
- data/CHANGELOG +0 -95
data/lib/session/memory_store.rb
CHANGED
|
@@ -1,147 +1,107 @@
|
|
|
1
|
-
# lib/session/memory_store.rb
|
|
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
|
-
@
|
|
9
|
-
@
|
|
10
|
-
@
|
|
17
|
+
@default_ttl = default_ttl
|
|
18
|
+
@cleanup_interval = cleanup_interval
|
|
19
|
+
@backup_path = backup_path
|
|
20
|
+
@backup_interval = backup_interval
|
|
21
|
+
|
|
11
22
|
@last_cleanup = Time.now
|
|
23
|
+
@last_backup = Time.now
|
|
24
|
+
|
|
25
|
+
restore! if @backup_path && File.exist?(@backup_path)
|
|
12
26
|
end
|
|
13
27
|
|
|
14
|
-
# Store with optional TTL
|
|
15
28
|
def set(key, value, ttl: nil)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
key_s = key.to_s
|
|
19
|
-
@store[key_s] = value
|
|
20
|
-
@ttls[key_s] = Time.now + (ttl || @default_ttl)
|
|
21
|
-
value
|
|
22
|
-
end
|
|
23
|
-
end
|
|
29
|
+
auto_cleanup
|
|
30
|
+
key_s = key.to_s
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@mutex.synchronize do
|
|
28
|
-
key_s = key.to_s
|
|
29
|
-
return nil unless @store.key?(key_s)
|
|
30
|
-
|
|
31
|
-
# Auto-clean if expired
|
|
32
|
-
if expired?(key_s)
|
|
33
|
-
delete(key_s)
|
|
34
|
-
return nil
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
@store[key_s]
|
|
38
|
-
end
|
|
39
|
-
end
|
|
32
|
+
@store[key_s] = value
|
|
33
|
+
@ttls[key_s] = Time.now + (ttl || @default_ttl)
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@mutex.synchronize do
|
|
44
|
-
key_s = key.to_s
|
|
45
|
-
return false unless @store.key?(key_s)
|
|
46
|
-
!expired?(key_s)
|
|
47
|
-
end
|
|
35
|
+
auto_backup
|
|
36
|
+
value
|
|
48
37
|
end
|
|
49
38
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@
|
|
53
|
-
key_s = key.to_s
|
|
54
|
-
@store.delete(key_s)
|
|
55
|
-
@ttls.delete(key_s)
|
|
56
|
-
true
|
|
57
|
-
end
|
|
58
|
-
end
|
|
39
|
+
def get(key)
|
|
40
|
+
key_s = key.to_s
|
|
41
|
+
return nil unless @store.key?(key_s)
|
|
59
42
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
key_s = key.to_s
|
|
64
|
-
current = get(key_s) || 0
|
|
65
|
-
new_value = current + amount
|
|
66
|
-
set(key_s, new_value, ttl: ttl)
|
|
67
|
-
new_value
|
|
43
|
+
if expired?(key_s)
|
|
44
|
+
delete(key_s)
|
|
45
|
+
return nil
|
|
68
46
|
end
|
|
69
|
-
end
|
|
70
47
|
|
|
71
|
-
|
|
72
|
-
def decrement(key, amount = 1)
|
|
73
|
-
increment(key, -amount)
|
|
48
|
+
@store[key_s]
|
|
74
49
|
end
|
|
75
50
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if now > expires
|
|
82
|
-
@store.delete(key)
|
|
83
|
-
@ttls.delete(key)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
@last_cleanup = now
|
|
87
|
-
end
|
|
51
|
+
def delete(key)
|
|
52
|
+
key_s = key.to_s
|
|
53
|
+
@store.delete(key_s)
|
|
54
|
+
@ttls.delete(key_s)
|
|
55
|
+
true
|
|
88
56
|
end
|
|
89
57
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
65
|
+
end
|
|
66
|
+
|
|
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
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def restore!
|
|
91
|
+
return unless @backup_path && File.exist?(@backup_path)
|
|
92
|
+
|
|
93
|
+
begin
|
|
94
|
+
raw = JSON.parse(File.read(@backup_path))
|
|
95
|
+
|
|
93
96
|
@store.clear
|
|
94
97
|
@ttls.clear
|
|
95
|
-
@last_cleanup = Time.now
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Get all keys (non-expired)
|
|
100
|
-
def keys
|
|
101
|
-
@mutex.synchronize do
|
|
102
|
-
auto_cleanup
|
|
103
|
-
@store.keys.select { |k| !expired?(k) }
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Get size (non-expired entries)
|
|
108
|
-
def size
|
|
109
|
-
keys.size
|
|
110
|
-
end
|
|
111
98
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
@mutex.synchronize do
|
|
119
|
-
key_s = key.to_s
|
|
120
|
-
return -1 unless @ttls[key_s]
|
|
121
|
-
|
|
122
|
-
remaining = @ttls[key_s] - Time.now
|
|
123
|
-
remaining > 0 ? remaining.ceil : -1
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Set TTL for existing key
|
|
128
|
-
def expire(key, ttl)
|
|
129
|
-
@mutex.synchronize do
|
|
130
|
-
key_s = key.to_s
|
|
131
|
-
return false unless @store.key?(key_s)
|
|
132
|
-
|
|
133
|
-
@ttls[key_s] = Time.now + ttl
|
|
134
|
-
true
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# Redis-like scan for pattern matching
|
|
139
|
-
def scan(pattern = "*", count: 10)
|
|
140
|
-
@mutex.synchronize do
|
|
141
|
-
auto_cleanup
|
|
142
|
-
regex = pattern_to_regex(pattern)
|
|
143
|
-
matching_keys = @store.keys.select { |k| k.match?(regex) && !expired?(k) }
|
|
144
|
-
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}"
|
|
145
105
|
end
|
|
146
106
|
end
|
|
147
107
|
|
|
@@ -152,16 +112,19 @@ module Telegem
|
|
|
152
112
|
end
|
|
153
113
|
|
|
154
114
|
def auto_cleanup
|
|
155
|
-
if Time.now - @last_cleanup > @cleanup_interval
|
|
156
|
-
|
|
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
|
|
157
120
|
end
|
|
158
121
|
end
|
|
159
122
|
|
|
160
|
-
def
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
123
|
+
def auto_backup
|
|
124
|
+
if @backup_path && (Time.now - @last_backup) > @backup_interval
|
|
125
|
+
backup!
|
|
126
|
+
end
|
|
164
127
|
end
|
|
165
128
|
end
|
|
166
129
|
end
|
|
167
|
-
end
|
|
130
|
+
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.
|
|
6
|
+
VERSION = "3.3.1".freeze
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
#
|
|
@@ -16,6 +16,7 @@ require_relative 'core/scene'
|
|
|
16
16
|
require_relative 'session/middleware'
|
|
17
17
|
require_relative 'session/memory_store'
|
|
18
18
|
require_relative 'markup/keyboard'
|
|
19
|
+
require_relative 'markup/inline'
|
|
19
20
|
|
|
20
21
|
require_relative 'plugins/file_extract'
|
|
21
22
|
require_relative 'session/scene_middleware'
|
|
@@ -77,4 +78,4 @@ if ENV['TELEGEM_GLOBAL'] == 'true'
|
|
|
77
78
|
def Telegem(token, **options)
|
|
78
79
|
::Telegem.new(token, **options)
|
|
79
80
|
end
|
|
80
|
-
end
|
|
81
|
+
end
|
data/lib/webhook/server.rb
CHANGED
|
@@ -120,7 +120,7 @@ module Telegem
|
|
|
120
120
|
begin
|
|
121
121
|
body = request.body.read
|
|
122
122
|
update_data = JSON.parse(body)
|
|
123
|
-
|
|
123
|
+
process_webhook_update(update_data)
|
|
124
124
|
[200, {}, ["OK"]]
|
|
125
125
|
rescue
|
|
126
126
|
[500, {}, ["Internal Server Error"]]
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: telegem
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- zendrx
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-04-24 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: concurrent-ruby
|
|
@@ -129,10 +130,11 @@ email:
|
|
|
129
130
|
- ynwghosted@icloud.com
|
|
130
131
|
executables:
|
|
131
132
|
- telegem-ssl
|
|
133
|
+
- telegem-init
|
|
132
134
|
extensions: []
|
|
133
135
|
extra_rdoc_files: []
|
|
134
136
|
files:
|
|
135
|
-
- CHANGELOG
|
|
137
|
+
- CHANGELOG.md
|
|
136
138
|
- CODE_OF_CONDUCT.md
|
|
137
139
|
- Contributing.md
|
|
138
140
|
- Gemfile
|
|
@@ -143,6 +145,7 @@ files:
|
|
|
143
145
|
- assets/.gitkeep
|
|
144
146
|
- assets/logo.png
|
|
145
147
|
- bin/.gitkeep
|
|
148
|
+
- bin/telegem-init
|
|
146
149
|
- bin/telegem-ssl
|
|
147
150
|
- docs/.gitkeep
|
|
148
151
|
- docs/ctx.md
|
|
@@ -156,6 +159,7 @@ files:
|
|
|
156
159
|
- lib/core/rate_limit.rb
|
|
157
160
|
- lib/core/scene.rb
|
|
158
161
|
- lib/markup/.gitkeep
|
|
162
|
+
- lib/markup/inline.rb
|
|
159
163
|
- lib/markup/keyboard.rb
|
|
160
164
|
- lib/plugins/.gitkeep
|
|
161
165
|
- lib/plugins/file_extract.rb
|
|
@@ -166,18 +170,18 @@ files:
|
|
|
166
170
|
- lib/webhook/.gitkeep
|
|
167
171
|
- lib/webhook/server.rb
|
|
168
172
|
- public/.gitkeep
|
|
169
|
-
homepage: https://
|
|
173
|
+
homepage: https://github.com/slick-lab/telegem
|
|
170
174
|
licenses:
|
|
171
175
|
- MIT
|
|
172
176
|
metadata:
|
|
173
|
-
homepage_uri: https://
|
|
174
|
-
source_code_uri: https://
|
|
175
|
-
changelog_uri: https://
|
|
176
|
-
bug_tracker_uri: https://
|
|
177
|
+
homepage_uri: https://github.com/slick-lab/telegem/-/blob/main/README.md
|
|
178
|
+
source_code_uri: https://github.com/slick-lab/telegem
|
|
179
|
+
changelog_uri: https://github.com/slick-lab/telegem/-/blob/main/CHANGELOG.md
|
|
180
|
+
bug_tracker_uri: https://github.com/slick-lab/telegem/-/issues
|
|
177
181
|
documentation_uri: https://gitlab.com/ruby-telegem/telegem/-/tree/main/docs-src?ref_type=heads
|
|
178
182
|
rubygems_mfa_required: 'false'
|
|
179
|
-
post_install_message: "Thanks for installing Telegem 3.
|
|
180
|
-
https://
|
|
183
|
+
post_install_message: "Thanks for installing Telegem 3.3.1!\n\n\U0001F4DA Documentation:
|
|
184
|
+
https://github.com/slick-lab/telegem\n\n\U0001F510 For SSL Webhooks:\nRun: telegem-ssl
|
|
181
185
|
your-domain.com\nThis sets up Let's Encrypt certificates automatically.\n\n\U0001F916
|
|
182
186
|
Happy bot building!\n"
|
|
183
187
|
rdoc_options: []
|
|
@@ -194,7 +198,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
194
198
|
- !ruby/object:Gem::Version
|
|
195
199
|
version: '0'
|
|
196
200
|
requirements: []
|
|
197
|
-
rubygems_version: 3.
|
|
201
|
+
rubygems_version: 3.5.22
|
|
202
|
+
signing_key:
|
|
198
203
|
specification_version: 4
|
|
199
204
|
summary: Modern, fast Telegram Bot Framework for Ruby
|
|
200
205
|
test_files: []
|
data/CHANGELOG
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
Telegem Changelog
|
|
2
|
-
|
|
3
|
-
v3.1.1 (current)
|
|
4
|
-
|
|
5
|
-
🚀 New Features
|
|
6
|
-
|
|
7
|
-
· FileExtractor Plugin: New plugin for extracting content from various file types (PDF, JSON, HTML, TXT)
|
|
8
|
-
· Async File Download: Added download method to API client for downloading Telegram files
|
|
9
|
-
· Context File Helpers: Added download_file, download_photo, download_document methods to Context
|
|
10
|
-
· Extended File Support: Plugin supports PDF text extraction, JSON parsing, HTML/raw text processing
|
|
11
|
-
· Async/Sync Dual Mode: All file operations available in both sync (download) and async (download!) modes
|
|
12
|
-
|
|
13
|
-
v3.1.0
|
|
14
|
-
|
|
15
|
-
· BREAKING: Rewrote polling system to prevent duplicate messages
|
|
16
|
-
· Fixed thread deadlock in async polling loop
|
|
17
|
-
· Added scene_middleware.rb for scene-based conversations
|
|
18
|
-
· Improved MemoryStore with TTL and thread safety
|
|
19
|
-
· Enhanced keyboard markup builder with web_app support
|
|
20
|
-
· Added message reaction and chat boost update types
|
|
21
|
-
· Fixed callback query handling for inline keyboards
|
|
22
|
-
|
|
23
|
-
v3.0.0
|
|
24
|
-
|
|
25
|
-
· BREAKING: Complete async rewrite with async gem
|
|
26
|
-
· New HTTP client using HTTPX with proper async/await pattern
|
|
27
|
-
· Added scene system for multi-step conversations
|
|
28
|
-
· Middleware composer system for plugin architecture
|
|
29
|
-
· Type system with dynamic accessors for Telegram objects
|
|
30
|
-
· Session management with memory store
|
|
31
|
-
· Rate limiting middleware
|
|
32
|
-
· File upload support via multipart forms
|
|
33
|
-
|
|
34
|
-
v2.0.0
|
|
35
|
-
|
|
36
|
-
· BREAKING: Ruby 3.0+ requirement
|
|
37
|
-
· Added webhook support with Rack middleware
|
|
38
|
-
· Inline query and callback query handlers
|
|
39
|
-
· Location, contact, and poll answer handlers
|
|
40
|
-
· Keyboard markup helpers (Telegem::Markup)
|
|
41
|
-
· Improved error handling with custom error classes
|
|
42
|
-
· Logging integration with configurable loggers
|
|
43
|
-
|
|
44
|
-
v1.5.0
|
|
45
|
-
|
|
46
|
-
· Added command argument parsing (ctx.command_args)
|
|
47
|
-
· Message entity parsing (mentions, hashtags, bot commands)
|
|
48
|
-
· Chat member update handlers
|
|
49
|
-
· Pre-checkout and shipping query support
|
|
50
|
-
· File download helper methods
|
|
51
|
-
· Context helper methods for common API calls
|
|
52
|
-
|
|
53
|
-
v1.0.0
|
|
54
|
-
|
|
55
|
-
· Stable API release
|
|
56
|
-
· Message handlers with text pattern matching
|
|
57
|
-
· Command handlers with regex support
|
|
58
|
-
· Basic context object with chat/message accessors
|
|
59
|
-
· Simple API client with error handling
|
|
60
|
-
· Polling and webhook modes
|
|
61
|
-
· Configuration options for timeout and limits
|
|
62
|
-
|
|
63
|
-
v0.5.0
|
|
64
|
-
|
|
65
|
-
· Added callback query support
|
|
66
|
-
· Inline keyboard builder
|
|
67
|
-
· Message editing and deletion helpers
|
|
68
|
-
· Media sending methods (photo, document, audio, video)
|
|
69
|
-
· Chat action methods (typing, upload indicators)
|
|
70
|
-
|
|
71
|
-
v0.3.0
|
|
72
|
-
|
|
73
|
-
· Middleware system with bot.use
|
|
74
|
-
· Session management foundation
|
|
75
|
-
· Basic rate limiting
|
|
76
|
-
· Command filtering by chat type
|
|
77
|
-
· Improved logging with debug levels
|
|
78
|
-
|
|
79
|
-
v0.2.0
|
|
80
|
-
|
|
81
|
-
· Basic polling implementation
|
|
82
|
-
· Message type detection (text, photo, document)
|
|
83
|
-
· Command parsing with arguments
|
|
84
|
-
· Simple reply methods
|
|
85
|
-
· Error handling for API calls
|
|
86
|
-
|
|
87
|
-
v0.1.0 (Initial Release)
|
|
88
|
-
|
|
89
|
-
· Basic Telegram Bot API wrapper
|
|
90
|
-
· Send/receive messages
|
|
91
|
-
· Simple command handling
|
|
92
|
-
· Minimal dependencies (just httparty)
|
|
93
|
-
· Support for basic message types
|
|
94
|
-
|
|
95
|
-
---
|