telegem 2.0.8 → 2.0.9
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/Test-Projects/Movie-tracker-bot/Gemfile +2 -0
- data/Test-Projects/Movie-tracker-bot/bot.rb +62 -0
- data/Test-Projects/Movie-tracker-bot/handlers/.gitkeep +0 -0
- data/Test-Projects/Movie-tracker-bot/handlers/add_1_.rb +160 -0
- data/Test-Projects/Movie-tracker-bot/handlers/add_2_.rb +139 -0
- data/Test-Projects/Movie-tracker-bot/handlers/premium.rb +13 -0
- data/Test-Projects/Movie-tracker-bot/handlers/report.rb +31 -0
- data/Test-Projects/Movie-tracker-bot/handlers/search.rb +150 -0
- data/Test-Projects/Movie-tracker-bot/handlers/sponsor.rb +14 -0
- data/Test-Projects/Movie-tracker-bot/handlers/start.rb +48 -0
- data/Test-Projects/Movie-tracker-bot/handlers/watch.rb +210 -0
- data/Test-Projects/Test-submitted-by-marvel/.gitkeep +0 -0
- data/Test-Projects/Test-submitted-by-marvel/Marvel-bot.md +3 -0
- data/docs-src/.gitkeep +0 -0
- data/docs-src/Bot-registration_.PNG +0 -0
- data/docs-src/bot.md +295 -0
- data/docs-src/context|ctx|.md +531 -0
- data/docs-src/getting-started.md +328 -0
- data/docs-src/keyboard_inline.md +413 -0
- data/docs-src/scene.md +509 -0
- data/docs-src/understanding-ctx.md +581 -0
- data/lib/api/client.rb +3 -0
- data/lib/core/bot.rb +31 -27
- data/lib/telegem.rb +1 -1
- data/lib/webhook/server.rb +1 -1
- metadata +26 -15
- data/docs/Api.md +0 -211
- data/docs/Cookbook(copy_paste).md +0 -644
- data/docs/Getting_started.md +0 -348
- data/docs/How_to_use.md +0 -571
- data/docs/QuickStart.md +0 -258
- data/docs/Understanding_Scene.md +0 -434
- data/docs/Usage.md +0 -717
- data/docs/webhook_setup.md +0 -199
- /data/{docs → Test-Projects/Movie-tracker-bot}/.gitkeep +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
class WatchHandler
|
|
2
|
+
def initialize(bot, db)
|
|
3
|
+
@bot = bot
|
|
4
|
+
@db = db
|
|
5
|
+
setup_watch_commands
|
|
6
|
+
setup_inline_handlers
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def setup_watch_commands
|
|
10
|
+
# Command: /watch - Show all subscribed shows with inline buttons
|
|
11
|
+
@bot.command("watch") do |ctx|
|
|
12
|
+
show_watch_menu(ctx)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Command: /watched <show_id> s<season>e<episode>
|
|
16
|
+
@bot.command("watched") do |ctx|
|
|
17
|
+
handle_watched_command(ctx)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def show_watch_menu(ctx)
|
|
22
|
+
user_id = ctx.from.id
|
|
23
|
+
|
|
24
|
+
# Get user's shows with their current progress
|
|
25
|
+
shows = @db.execute(<<-SQL, [user_id])
|
|
26
|
+
SELECT
|
|
27
|
+
movies.id,
|
|
28
|
+
movies.title,
|
|
29
|
+
watched.season as current_season,
|
|
30
|
+
watched.episode as current_episode
|
|
31
|
+
FROM watched
|
|
32
|
+
JOIN movies ON movies.id = watched.movie_id
|
|
33
|
+
WHERE watched.telegram_id = ?
|
|
34
|
+
ORDER BY movies.title
|
|
35
|
+
SQL
|
|
36
|
+
|
|
37
|
+
if shows.empty?
|
|
38
|
+
ctx.reply("📭 You're not tracking any shows yet!\nUse /search to find shows, then /add <id>")
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Create message with inline keyboard
|
|
43
|
+
message = "📺 *Mark Episodes Watched*\n\n"
|
|
44
|
+
message += "Click a show to mark episodes:\n\n"
|
|
45
|
+
|
|
46
|
+
shows.each do |id, title, season, episode|
|
|
47
|
+
message += "🎬 *#{title}*\n"
|
|
48
|
+
message += " Current: S#{season}E#{episode}\n"
|
|
49
|
+
message += " Next: S#{season}E#{episode + 1}\n\n"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Create inline keyboard with ONE button per show
|
|
53
|
+
inline = Telegem.inline do
|
|
54
|
+
shows.each do |id, title, season, episode|
|
|
55
|
+
# Button shows: "Stranger Things (S1E3)"
|
|
56
|
+
row button "#{title} (S#{season}E#{episode})",
|
|
57
|
+
callback_data: "select_show:#{id}:#{season}:#{episode}"
|
|
58
|
+
end
|
|
59
|
+
# Add a "Mark All Caught Up" button
|
|
60
|
+
row button "✅ Mark All As Caught Up", callback_data: "catchup_all"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
ctx.reply(message, reply_markup: inline, parse_mode: 'Markdown')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def setup_inline_handlers
|
|
67
|
+
@bot.on(:callback_query) do |ctx|
|
|
68
|
+
case ctx.data
|
|
69
|
+
when /^select_show:(\d+):(\d+):(\d+)$/
|
|
70
|
+
handle_show_selection(ctx, $1.to_i, $2.to_i, $3.to_i)
|
|
71
|
+
when /^mark_episode:(\d+):(\d+):(\d+)$/
|
|
72
|
+
handle_mark_episode(ctx, $1.to_i, $2.to_i, $3.to_i)
|
|
73
|
+
when "catchup_all"
|
|
74
|
+
handle_catchup_all(ctx)
|
|
75
|
+
when "back_to_shows"
|
|
76
|
+
show_watch_menu(ctx)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def handle_show_selection(ctx, show_id, current_season, current_episode)
|
|
82
|
+
# Get show title
|
|
83
|
+
show = @db.execute(
|
|
84
|
+
"SELECT title FROM movies WHERE id = ?",
|
|
85
|
+
[show_id]
|
|
86
|
+
).first
|
|
87
|
+
|
|
88
|
+
return unless show
|
|
89
|
+
|
|
90
|
+
show_title = show[0]
|
|
91
|
+
|
|
92
|
+
# Create episode selection buttons
|
|
93
|
+
message = "🎬 *#{show_title}*\n"
|
|
94
|
+
message += "Mark which episode you watched:\n\n"
|
|
95
|
+
message += "Current: S#{current_season}E#{current_episode}\n\n"
|
|
96
|
+
|
|
97
|
+
# Create buttons for next 5 episodes
|
|
98
|
+
inline = Telegem.inline do
|
|
99
|
+
# Show 5 episodes: current+1 to current+5
|
|
100
|
+
(1..5).each do |offset|
|
|
101
|
+
ep_num = current_episode + offset
|
|
102
|
+
row button "✅ S#{current_season}E#{ep_num}",
|
|
103
|
+
callback_data: "mark_episode:#{show_id}:#{current_season}:#{ep_num}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Next season button
|
|
107
|
+
row button "➡️ Next Season (S#{current_season + 1}E1)",
|
|
108
|
+
callback_data: "mark_episode:#{show_id}:#{current_season + 1}:1"
|
|
109
|
+
|
|
110
|
+
# Back button
|
|
111
|
+
row button "🔙 Back to Shows", callback_data: "back_to_shows"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Edit the message with new buttons
|
|
115
|
+
ctx.edit_message_text(message, reply_markup: inline, parse_mode: 'Markdown')
|
|
116
|
+
ctx.answer_callback_query(text: "Select episode for #{show_title}")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def handle_mark_episode(ctx, show_id, season, episode)
|
|
120
|
+
user_id = ctx.from.id
|
|
121
|
+
|
|
122
|
+
# Update watched table
|
|
123
|
+
@db.execute(<<-SQL, [season, episode, user_id, show_id])
|
|
124
|
+
UPDATE watched
|
|
125
|
+
SET season = ?, episode = ?, updated_at = CURRENT_TIMESTAMP
|
|
126
|
+
WHERE telegram_id = ? AND movie_id = ?
|
|
127
|
+
SQL
|
|
128
|
+
|
|
129
|
+
# Get show title for response
|
|
130
|
+
show = @db.execute(
|
|
131
|
+
"SELECT title FROM movies WHERE id = ?",
|
|
132
|
+
[show_id]
|
|
133
|
+
).first
|
|
134
|
+
|
|
135
|
+
show_title = show[0] if show
|
|
136
|
+
|
|
137
|
+
# Show confirmation
|
|
138
|
+
ctx.answer_callback_query(
|
|
139
|
+
text: "✅ Marked #{show_title} S#{season}E#{episode} as watched!"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Update message to show new status
|
|
143
|
+
message = "✅ *Updated!*\n\n"
|
|
144
|
+
message += "🎬 #{show_title}\n"
|
|
145
|
+
message += "Now watching: S#{season}E#{episode}\n"
|
|
146
|
+
message += "Next alert: S#{season}E#{episode + 1}\n\n"
|
|
147
|
+
message += "Click another show or use /watch again."
|
|
148
|
+
|
|
149
|
+
# Keep back button
|
|
150
|
+
inline = Telegem.inline do
|
|
151
|
+
row button "🔙 Back to Shows", callback_data: "back_to_shows"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
ctx.edit_message_text(message, reply_markup: inline, parse_mode: 'Markdown')
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def handle_catchup_all(ctx)
|
|
158
|
+
user_id = ctx.from.id
|
|
159
|
+
|
|
160
|
+
# For each show, set episode to a high number (like 999 to mark as "caught up")
|
|
161
|
+
shows_updated = @db.execute(<<-SQL, [user_id])
|
|
162
|
+
UPDATE watched
|
|
163
|
+
SET episode = 999, updated_at = CURRENT_TIMESTAMP
|
|
164
|
+
WHERE telegram_id = ? AND episode < 999
|
|
165
|
+
SQL
|
|
166
|
+
|
|
167
|
+
ctx.answer_callback_query(
|
|
168
|
+
text: "✅ Marked all shows as caught up!"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Go back to show list
|
|
172
|
+
show_watch_menu(ctx)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def handle_watched_command(ctx)
|
|
176
|
+
# Text command version: /watched 1 s1e3
|
|
177
|
+
args = ctx.message.text.split
|
|
178
|
+
|
|
179
|
+
if args.size != 3
|
|
180
|
+
ctx.reply("Usage: /watched <show_id> <episode>\nExample: /watched 1 s1e3")
|
|
181
|
+
return
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
show_id = args[1].to_i
|
|
185
|
+
episode_str = args[2]
|
|
186
|
+
|
|
187
|
+
match = episode_str.match(/s(\d+)e(\d+)/i)
|
|
188
|
+
unless match
|
|
189
|
+
ctx.reply("❌ Invalid format. Use: s1e3, s2e5, etc.")
|
|
190
|
+
return
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
season = match[1].to_i
|
|
194
|
+
episode = match[2].to_i
|
|
195
|
+
user_id = ctx.from.id
|
|
196
|
+
|
|
197
|
+
# Update database
|
|
198
|
+
@db.execute(<<-SQL, [season, episode, user_id, show_id])
|
|
199
|
+
UPDATE watched
|
|
200
|
+
SET season = ?, episode = ?, updated_at = CURRENT_TIMESTAMP
|
|
201
|
+
WHERE telegram_id = ? AND movie_id = ?
|
|
202
|
+
SQL
|
|
203
|
+
|
|
204
|
+
if @db.changes > 0
|
|
205
|
+
ctx.reply("✅ Updated! You've watched up to S#{season}E#{episode}")
|
|
206
|
+
else
|
|
207
|
+
ctx.reply("❌ You're not tracking that show. Use /add first.")
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
File without changes
|
data/docs-src/.gitkeep
ADDED
|
File without changes
|
|
Binary file
|
data/docs-src/bot.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
|
|
2
|
+
# Bot API Reference
|
|
3
|
+
|
|
4
|
+
The `Telegem::Core::Bot` class is the main controller for your Telegram bot.
|
|
5
|
+
|
|
6
|
+
## Instantiation
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
bot = Telegem.new(token, **options)
|
|
10
|
+
# or
|
|
11
|
+
bot = Telegem::Core::Bot.new(token, **options)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Bot Control Methods
|
|
15
|
+
|
|
16
|
+
start_polling(**options)
|
|
17
|
+
|
|
18
|
+
Starts the bot in long-polling mode (for development/local use).
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
bot.start_polling(
|
|
22
|
+
timeout: 30, # Seconds to wait for updates (default: 30)
|
|
23
|
+
limit: 100, # Max updates per request (default: 100)
|
|
24
|
+
allowed_updates: ['message', 'callback_query'] # Filter updates
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
shutdown()
|
|
29
|
+
|
|
30
|
+
Gracefully stops the bot.
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
bot.shutdown # Stops polling, closes connections, terminates workers
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
running?()
|
|
37
|
+
|
|
38
|
+
Checks if the bot is currently active.
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
if bot.running?
|
|
42
|
+
puts "Bot is online"
|
|
43
|
+
else
|
|
44
|
+
puts "Bot is offline"
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Handler Registration Methods
|
|
49
|
+
|
|
50
|
+
command(name, **options, &block)
|
|
51
|
+
|
|
52
|
+
Registers a command handler.
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
bot.command('start') do |ctx|
|
|
56
|
+
ctx.reply "Welcome! Use /help for commands."
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# With arguments
|
|
60
|
+
bot.command('greet') do |ctx|
|
|
61
|
+
name = ctx.command_args || 'friend'
|
|
62
|
+
ctx.reply "Hello, #{name}!"
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
hears(pattern, **options, &block)
|
|
67
|
+
|
|
68
|
+
Registers a text pattern handler.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Regex pattern
|
|
72
|
+
bot.hears(/hello|hi|hey/i) do |ctx|
|
|
73
|
+
ctx.reply "Hello there! 👋"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Exact match
|
|
77
|
+
bot.hears('ping') do |ctx|
|
|
78
|
+
ctx.reply 'pong 🏓'
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
on(type, filters = {}, &block)
|
|
83
|
+
|
|
84
|
+
Generic update handler.
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
# Message types
|
|
88
|
+
bot.on(:message, photo: true) do |ctx|
|
|
89
|
+
ctx.reply "Nice photo! 📸"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
bot.on(:message, location: true) do |ctx|
|
|
93
|
+
ctx.reply "Thanks for sharing your location! 📍"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Callback queries (inline button clicks)
|
|
97
|
+
bot.on(:callback_query) do |ctx|
|
|
98
|
+
ctx.answer_callback_query(text: "You clicked #{ctx.data}")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Filter by chat type
|
|
102
|
+
bot.on(:message, chat_type: 'private') do |ctx|
|
|
103
|
+
ctx.reply "This is a private chat"
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Middleware System
|
|
108
|
+
|
|
109
|
+
use(middleware, *args, &block)
|
|
110
|
+
|
|
111
|
+
Adds middleware to the processing chain.
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
# Built-in session middleware (auto-added)
|
|
115
|
+
bot.use(Telegem::Session::Middleware)
|
|
116
|
+
|
|
117
|
+
# Custom middleware
|
|
118
|
+
class LoggerMiddleware
|
|
119
|
+
def call(ctx, next_middleware)
|
|
120
|
+
puts "Processing update from #{ctx.from.id}"
|
|
121
|
+
next_middleware.call(ctx)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
bot.use(LoggerMiddleware)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Scene System
|
|
129
|
+
|
|
130
|
+
scene(id, &block)
|
|
131
|
+
|
|
132
|
+
Defines a scene (multi-step conversation).
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
bot.scene(:survey) do |scene|
|
|
136
|
+
scene.step(:start) do |ctx|
|
|
137
|
+
ctx.reply "What's your name?"
|
|
138
|
+
ctx.session[:step] = :name
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
scene.step(:name) do |ctx|
|
|
142
|
+
name = ctx.message.text
|
|
143
|
+
ctx.reply "Hello #{name}! How old are you?"
|
|
144
|
+
ctx.session[:name] = name
|
|
145
|
+
ctx.session[:step] = :age
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Enter scene from command
|
|
150
|
+
bot.command('survey') do |ctx|
|
|
151
|
+
ctx.enter_scene(:survey)
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Webhook Methods
|
|
156
|
+
|
|
157
|
+
webhook(**options)
|
|
158
|
+
|
|
159
|
+
Returns a webhook server instance.
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
# Quick setup (auto-detects cloud platform)
|
|
163
|
+
server = bot.webhook.run
|
|
164
|
+
|
|
165
|
+
# Manual configuration
|
|
166
|
+
server = bot.webhook(
|
|
167
|
+
port: 3000,
|
|
168
|
+
host: '0.0.0.0',
|
|
169
|
+
secret_token: ENV['SECRET_TOKEN'],
|
|
170
|
+
logger: Logger.new('webhook.log')
|
|
171
|
+
)
|
|
172
|
+
server.run
|
|
173
|
+
server.set_webhook
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
set_webhook(url, **options)
|
|
177
|
+
|
|
178
|
+
Manually sets webhook URL.
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
bot.set_webhook(
|
|
182
|
+
'https://your-app.herokuapp.com/webhook',
|
|
183
|
+
secret_token: 'your-secret',
|
|
184
|
+
drop_pending_updates: true
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
delete_webhook()
|
|
189
|
+
|
|
190
|
+
Removes webhook configuration.
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
bot.delete_webhook # Switch back to polling
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
get_webhook_info()
|
|
197
|
+
|
|
198
|
+
Gets current webhook information.
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
info = bot.get_webhook_info
|
|
202
|
+
puts "Webhook URL: #{info['url']}"
|
|
203
|
+
puts "Has custom certificate: #{info['has_custom_certificate']}"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Error Handling
|
|
207
|
+
|
|
208
|
+
error(&block)
|
|
209
|
+
|
|
210
|
+
Sets global error handler.
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
bot.error do |error, ctx|
|
|
214
|
+
puts "Error: #{error.message}"
|
|
215
|
+
puts "Context: User #{ctx.from&.id}, Chat #{ctx.chat&.id}"
|
|
216
|
+
|
|
217
|
+
# Send error to user
|
|
218
|
+
ctx.reply "Something went wrong! 😅" if ctx&.chat
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Update Processing
|
|
223
|
+
|
|
224
|
+
process(update_data)
|
|
225
|
+
|
|
226
|
+
Manually process an update (useful for testing).
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
test_update = {
|
|
230
|
+
'update_id' => 1,
|
|
231
|
+
'message' => {
|
|
232
|
+
'message_id' => 1,
|
|
233
|
+
'from' => {'id' => 123, 'first_name' => 'Test'},
|
|
234
|
+
'chat' => {'id' => 123},
|
|
235
|
+
'date' => Time.now.to_i,
|
|
236
|
+
'text' => '/start'
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
bot.process(test_update)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Bot Options
|
|
244
|
+
|
|
245
|
+
Available options when creating bot:
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
bot = Telegem.new('TOKEN',
|
|
249
|
+
logger: Logger.new('bot.log'), # Custom logger
|
|
250
|
+
timeout: 60, # API timeout in seconds
|
|
251
|
+
max_threads: 20, # Worker thread pool size
|
|
252
|
+
worker_count: 5, # Update processing workers
|
|
253
|
+
session_store: custom_store # Custom session storage
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Complete Example
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
require 'telegem'
|
|
261
|
+
|
|
262
|
+
bot = Telegem.new(ENV['BOT_TOKEN'])
|
|
263
|
+
|
|
264
|
+
# Error handling
|
|
265
|
+
bot.error do |error, ctx|
|
|
266
|
+
puts "Error: #{error.class}: #{error.message}"
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Commands
|
|
270
|
+
bot.command('start') { |ctx| ctx.reply "Welcome!" }
|
|
271
|
+
bot.command('help') { |ctx| ctx.reply "Available: /start, /help, /menu" }
|
|
272
|
+
|
|
273
|
+
# Interactive menu
|
|
274
|
+
bot.command('menu') do |ctx|
|
|
275
|
+
keyboard = Telegem.keyboard do
|
|
276
|
+
row "Option 1", "Option 2"
|
|
277
|
+
row "Cancel"
|
|
278
|
+
end
|
|
279
|
+
ctx.reply "Choose:", reply_markup: keyboard
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Start based on environment
|
|
283
|
+
if ENV['RACK_ENV'] == 'production'
|
|
284
|
+
bot.webhook.run
|
|
285
|
+
else
|
|
286
|
+
bot.start_polling
|
|
287
|
+
end
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
Next: Context Methods | Keyboard API
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
```
|