telegram_bot_engine 0.3.3 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/LICENSE +21 -0
- data/README.md +67 -0
- data/app/controllers/telegram_bot_engine/admin/allowlist_controller.rb +5 -2
- data/app/controllers/telegram_bot_engine/admin/bots_controller.rb +73 -0
- data/app/controllers/telegram_bot_engine/admin/dashboard_controller.rb +6 -0
- data/app/controllers/telegram_bot_engine/admin/events_controller.rb +3 -0
- data/app/controllers/telegram_bot_engine/admin/subscriptions_controller.rb +3 -1
- data/app/jobs/telegram_bot_engine/application_job.rb +10 -0
- data/app/jobs/telegram_bot_engine/delivery_job.rb +15 -8
- data/app/models/telegram_bot_engine/allowed_user.rb +7 -1
- data/app/models/telegram_bot_engine/bot.rb +177 -0
- data/app/models/telegram_bot_engine/event.rb +8 -3
- data/app/models/telegram_bot_engine/subscription.rb +14 -1
- data/app/views/telegram_bot_engine/admin/allowlist/index.html.erb +17 -1
- data/app/views/telegram_bot_engine/admin/bots/edit.html.erb +69 -0
- data/app/views/telegram_bot_engine/admin/bots/index.html.erb +70 -0
- data/app/views/telegram_bot_engine/admin/bots/new.html.erb +53 -0
- data/app/views/telegram_bot_engine/admin/dashboard/show.html.erb +40 -1
- data/app/views/telegram_bot_engine/admin/events/index.html.erb +15 -4
- data/app/views/telegram_bot_engine/admin/layouts/application.html.erb +2 -0
- data/app/views/telegram_bot_engine/admin/subscriptions/index.html.erb +22 -1
- data/config/routes.rb +3 -0
- data/db/migrate/004_create_telegram_bot_engine_bots.rb +21 -0
- data/db/migrate/005_add_bot_to_telegram_bot_engine_subscriptions.rb +38 -0
- data/db/migrate/006_add_bot_to_telegram_bot_engine_allowed_users.rb +32 -0
- data/db/migrate/007_add_bot_to_telegram_bot_engine_events.rb +11 -0
- data/db/migrate/008_add_webhook_id_to_telegram_bot_engine_bots.rb +29 -0
- data/lib/telegram_bot_engine/authorizer.rb +10 -6
- data/lib/telegram_bot_engine/configuration.rb +8 -1
- data/lib/telegram_bot_engine/dispatch.rb +73 -0
- data/lib/telegram_bot_engine/registry.rb +39 -0
- data/lib/telegram_bot_engine/subscriber_commands.rb +17 -7
- data/lib/telegram_bot_engine/version.rb +1 -1
- data/lib/telegram_bot_engine/webhook_registrar.rb +32 -0
- data/lib/telegram_bot_engine.rb +24 -9
- metadata +31 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TelegramBotEngine
|
|
4
|
+
# Resolves and caches a Telegram::Bot::Client per Bot record, keyed by bot id
|
|
5
|
+
# (docs/0001 §2 cap 2, §3.2). This fills telegram-bot's gap: `Telegram.bots` is
|
|
6
|
+
# boot-memoized (`@bots ||=`) and not built for runtime hot-add or token rotation.
|
|
7
|
+
# We resolve a fresh `Telegram::Bot::Client.new(token)` per bot_id rather than
|
|
8
|
+
# leaning on `Telegram.reset_bots`, which is unsafe under concurrency (docs/0001 §9).
|
|
9
|
+
module Registry
|
|
10
|
+
@mutex = Mutex.new
|
|
11
|
+
@clients = {}
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# The client for this bot, built once and cached by bot id.
|
|
15
|
+
def client_for(bot)
|
|
16
|
+
@mutex.synchronize do
|
|
17
|
+
@clients[bot.id] ||= build_client(bot)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Drop a bot's cached client so the next resolve rebuilds it. Called from
|
|
22
|
+
# Bot#after_save / #after_destroy so token rotation can never serve a stale client.
|
|
23
|
+
def invalidate(bot)
|
|
24
|
+
@mutex.synchronize { @clients.delete(bot.id) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Clear the whole cache (test isolation; also safe to call on reload).
|
|
28
|
+
def reset!
|
|
29
|
+
@mutex.synchronize { @clients = {} }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def build_client(bot)
|
|
35
|
+
Telegram::Bot::Client.new(bot.token, bot.slug)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -12,7 +12,7 @@ module TelegramBotEngine
|
|
|
12
12
|
# /start - create subscription if authorized
|
|
13
13
|
def start!(*)
|
|
14
14
|
subscription = TelegramBotEngine::Subscription.find_or_initialize_by(
|
|
15
|
-
chat_id: chat["id"]
|
|
15
|
+
chat_id: chat["id"], bot_id: current_bot&.id
|
|
16
16
|
)
|
|
17
17
|
subscription.assign_attributes(
|
|
18
18
|
user_id: from["id"],
|
|
@@ -24,7 +24,7 @@ module TelegramBotEngine
|
|
|
24
24
|
|
|
25
25
|
TelegramBotEngine::Event.log(
|
|
26
26
|
event_type: "command", action: "start",
|
|
27
|
-
chat_id: chat["id"], username: from["username"]
|
|
27
|
+
chat_id: chat["id"], username: from["username"], bot_id: current_bot&.id
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
welcome = TelegramBotEngine.config.welcome_message % {
|
|
@@ -36,12 +36,12 @@ module TelegramBotEngine
|
|
|
36
36
|
|
|
37
37
|
# /stop - deactivate subscription
|
|
38
38
|
def stop!(*)
|
|
39
|
-
subscription = TelegramBotEngine::Subscription.find_by(chat_id: chat["id"])
|
|
39
|
+
subscription = TelegramBotEngine::Subscription.find_by(chat_id: chat["id"], bot_id: current_bot&.id)
|
|
40
40
|
subscription&.update(active: false)
|
|
41
41
|
|
|
42
42
|
TelegramBotEngine::Event.log(
|
|
43
43
|
event_type: "command", action: "stop",
|
|
44
|
-
chat_id: chat["id"], username: from["username"]
|
|
44
|
+
chat_id: chat["id"], username: from["username"], bot_id: current_bot&.id
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
respond_with :message, text: "You've been unsubscribed. Send /start to resubscribe."
|
|
@@ -51,7 +51,7 @@ module TelegramBotEngine
|
|
|
51
51
|
def help!(*)
|
|
52
52
|
TelegramBotEngine::Event.log(
|
|
53
53
|
event_type: "command", action: "help",
|
|
54
|
-
chat_id: chat["id"], username: from["username"]
|
|
54
|
+
chat_id: chat["id"], username: from["username"], bot_id: current_bot&.id
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
respond_with :message, text: "📋 *Available Commands*\n\n#{available_commands_text}", parse_mode: "Markdown"
|
|
@@ -60,17 +60,27 @@ module TelegramBotEngine
|
|
|
60
60
|
private
|
|
61
61
|
|
|
62
62
|
def authorize_user!
|
|
63
|
-
return if TelegramBotEngine::Authorizer.authorized?(from["username"])
|
|
63
|
+
return if TelegramBotEngine::Authorizer.authorized?(from["username"], bot: current_bot)
|
|
64
64
|
|
|
65
65
|
TelegramBotEngine::Event.log(
|
|
66
66
|
event_type: "auth_failure", action: "unauthorized",
|
|
67
|
-
chat_id: chat["id"], username: from["username"]
|
|
67
|
+
chat_id: chat["id"], username: from["username"], bot_id: current_bot&.id
|
|
68
68
|
)
|
|
69
69
|
|
|
70
70
|
respond_with :message, text: TelegramBotEngine.config.unauthorized_message
|
|
71
71
|
throw :abort
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# The Bot this inbound update arrived for, set by TelegramBotEngine::Dispatch in the
|
|
75
|
+
# request env (docs/0001 §3.7). nil when there is no inbound request context (e.g. the
|
|
76
|
+
# poller, or a host controller without webhook_request) → default/global behavior, unchanged.
|
|
77
|
+
def current_bot
|
|
78
|
+
return unless respond_to?(:webhook_request)
|
|
79
|
+
|
|
80
|
+
request = webhook_request
|
|
81
|
+
request && request.env["telegram_bot_engine.bot"]
|
|
82
|
+
end
|
|
83
|
+
|
|
74
84
|
# Auto-generates command list from public methods ending with !
|
|
75
85
|
def available_commands_text
|
|
76
86
|
commands = self.class.public_instance_methods(false)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TelegramBotEngine
|
|
4
|
+
# Registers/removes a bot's Telegram webhook (docs/0001 §3.6). The webhook URL embeds the
|
|
5
|
+
# bot's per-bot `webhook_secret` path segment, and `secret_token` is set to the same secret
|
|
6
|
+
# so the inbound dispatcher can authenticate Telegram via the X-Telegram-Bot-Api-Secret-Token
|
|
7
|
+
# header. setWebhook is idempotent (overwrites), so re-registering is always safe.
|
|
8
|
+
module WebhookRegistrar
|
|
9
|
+
class << self
|
|
10
|
+
def register(bot, base_url: nil)
|
|
11
|
+
base_url ||= TelegramBotEngine.config.webhook_base_url
|
|
12
|
+
return false if base_url.blank?
|
|
13
|
+
|
|
14
|
+
TelegramBotEngine.client_for(bot).set_webhook(
|
|
15
|
+
url: webhook_url(bot, base_url),
|
|
16
|
+
secret_token: bot.webhook_secret
|
|
17
|
+
)
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def remove(bot)
|
|
22
|
+
TelegramBotEngine.client_for(bot).delete_webhook
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def webhook_url(bot, base_url)
|
|
27
|
+
# The URL path carries the NON-secret webhook_id; the secret stays in secret_token only.
|
|
28
|
+
"#{base_url.to_s.chomp('/')}#{TelegramBotEngine.config.webhook_mount_path}/#{bot.webhook_id}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/telegram_bot_engine.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
require "telegram/bot"
|
|
4
4
|
require "telegram_bot_engine/version"
|
|
5
5
|
require "telegram_bot_engine/configuration"
|
|
6
|
+
require "telegram_bot_engine/registry"
|
|
7
|
+
require "telegram_bot_engine/webhook_registrar"
|
|
8
|
+
require "telegram_bot_engine/dispatch"
|
|
6
9
|
require "telegram_bot_engine/authorizer"
|
|
7
10
|
require "telegram_bot_engine/subscriber_commands"
|
|
8
11
|
require "telegram_bot_engine/engine"
|
|
@@ -21,11 +24,20 @@ module TelegramBotEngine
|
|
|
21
24
|
@config = Configuration.new
|
|
22
25
|
end
|
|
23
26
|
|
|
24
|
-
#
|
|
25
|
-
|
|
27
|
+
# The Telegram::Bot::Client for a Bot record, resolved from the DB and cached by
|
|
28
|
+
# bot id (token-rotation safe). See TelegramBotEngine::Registry / docs/0001 §3.2.
|
|
29
|
+
def client_for(bot)
|
|
30
|
+
Registry.client_for(bot)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Broadcast to all active subscribers via background jobs, delivered through `bot`.
|
|
34
|
+
# `bot:` omitted ⇒ the default bot ⇒ today's behavior, unchanged (docs/0001 §3.3).
|
|
35
|
+
def broadcast(text, bot: nil, **options)
|
|
36
|
+
bot ||= TelegramBotEngine::Bot.default
|
|
26
37
|
subscriber_count = 0
|
|
27
|
-
TelegramBotEngine::Subscription.active.find_each do |subscription|
|
|
38
|
+
TelegramBotEngine::Subscription.active.for_bot(bot).find_each do |subscription|
|
|
28
39
|
TelegramBotEngine::DeliveryJob.perform_later(
|
|
40
|
+
bot.id,
|
|
29
41
|
subscription.chat_id,
|
|
30
42
|
text,
|
|
31
43
|
options
|
|
@@ -35,18 +47,21 @@ module TelegramBotEngine
|
|
|
35
47
|
|
|
36
48
|
TelegramBotEngine::Event.log(
|
|
37
49
|
event_type: "delivery", action: "broadcast",
|
|
38
|
-
|
|
50
|
+
bot_id: bot.id,
|
|
51
|
+
details: { bot: bot.slug, subscriber_count: subscriber_count, text_preview: text.to_s[0, 100] }
|
|
39
52
|
)
|
|
40
53
|
end
|
|
41
54
|
|
|
42
|
-
# Send to a specific chat via background job
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
# Send to a specific chat via background job, delivered through `bot`.
|
|
56
|
+
# `bot:` omitted ⇒ the default bot ⇒ today's behavior, unchanged (docs/0001 §3.3).
|
|
57
|
+
def notify(chat_id:, text:, bot: nil, **options)
|
|
58
|
+
bot ||= TelegramBotEngine::Bot.default
|
|
59
|
+
TelegramBotEngine::DeliveryJob.perform_later(bot.id, chat_id, text, options)
|
|
45
60
|
|
|
46
61
|
TelegramBotEngine::Event.log(
|
|
47
62
|
event_type: "delivery", action: "notify",
|
|
48
|
-
chat_id: chat_id,
|
|
49
|
-
details: { text_preview: text.to_s[0, 100] }
|
|
63
|
+
chat_id: chat_id, bot_id: bot.id,
|
|
64
|
+
details: { bot: bot.slug, text_preview: text.to_s[0, 100] }
|
|
50
65
|
)
|
|
51
66
|
end
|
|
52
67
|
end
|
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: telegram_bot_engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Tomáš Landovský
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
@@ -16,6 +16,9 @@ dependencies:
|
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
18
|
version: '7.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9.0'
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -23,6 +26,9 @@ dependencies:
|
|
|
23
26
|
- - ">="
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
28
|
version: '7.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9.0'
|
|
26
32
|
- !ruby/object:Gem::Dependency
|
|
27
33
|
name: telegram-bot
|
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -39,21 +45,31 @@ dependencies:
|
|
|
39
45
|
version: '0.16'
|
|
40
46
|
description: A mountable Rails engine that adds subscriber persistence, authorization,
|
|
41
47
|
broadcasting, and an admin UI on top of the telegram-bot gem.
|
|
48
|
+
email:
|
|
49
|
+
- landovsky@gmail.com
|
|
42
50
|
executables: []
|
|
43
51
|
extensions: []
|
|
44
52
|
extra_rdoc_files: []
|
|
45
53
|
files:
|
|
54
|
+
- CHANGELOG.md
|
|
55
|
+
- LICENSE
|
|
46
56
|
- README.md
|
|
47
57
|
- app/controllers/telegram_bot_engine/admin/allowlist_controller.rb
|
|
48
58
|
- app/controllers/telegram_bot_engine/admin/base_controller.rb
|
|
59
|
+
- app/controllers/telegram_bot_engine/admin/bots_controller.rb
|
|
49
60
|
- app/controllers/telegram_bot_engine/admin/dashboard_controller.rb
|
|
50
61
|
- app/controllers/telegram_bot_engine/admin/events_controller.rb
|
|
51
62
|
- app/controllers/telegram_bot_engine/admin/subscriptions_controller.rb
|
|
63
|
+
- app/jobs/telegram_bot_engine/application_job.rb
|
|
52
64
|
- app/jobs/telegram_bot_engine/delivery_job.rb
|
|
53
65
|
- app/models/telegram_bot_engine/allowed_user.rb
|
|
66
|
+
- app/models/telegram_bot_engine/bot.rb
|
|
54
67
|
- app/models/telegram_bot_engine/event.rb
|
|
55
68
|
- app/models/telegram_bot_engine/subscription.rb
|
|
56
69
|
- app/views/telegram_bot_engine/admin/allowlist/index.html.erb
|
|
70
|
+
- app/views/telegram_bot_engine/admin/bots/edit.html.erb
|
|
71
|
+
- app/views/telegram_bot_engine/admin/bots/index.html.erb
|
|
72
|
+
- app/views/telegram_bot_engine/admin/bots/new.html.erb
|
|
57
73
|
- app/views/telegram_bot_engine/admin/dashboard/show.html.erb
|
|
58
74
|
- app/views/telegram_bot_engine/admin/events/index.html.erb
|
|
59
75
|
- app/views/telegram_bot_engine/admin/layouts/application.html.erb
|
|
@@ -62,17 +78,29 @@ files:
|
|
|
62
78
|
- db/migrate/001_create_telegram_bot_engine_subscriptions.rb
|
|
63
79
|
- db/migrate/002_create_telegram_bot_engine_allowed_users.rb
|
|
64
80
|
- db/migrate/003_create_telegram_bot_engine_events.rb
|
|
81
|
+
- db/migrate/004_create_telegram_bot_engine_bots.rb
|
|
82
|
+
- db/migrate/005_add_bot_to_telegram_bot_engine_subscriptions.rb
|
|
83
|
+
- db/migrate/006_add_bot_to_telegram_bot_engine_allowed_users.rb
|
|
84
|
+
- db/migrate/007_add_bot_to_telegram_bot_engine_events.rb
|
|
85
|
+
- db/migrate/008_add_webhook_id_to_telegram_bot_engine_bots.rb
|
|
65
86
|
- lib/tasks/telegram_bot_engine.rake
|
|
66
87
|
- lib/telegram_bot_engine.rb
|
|
67
88
|
- lib/telegram_bot_engine/authorizer.rb
|
|
68
89
|
- lib/telegram_bot_engine/configuration.rb
|
|
90
|
+
- lib/telegram_bot_engine/dispatch.rb
|
|
69
91
|
- lib/telegram_bot_engine/engine.rb
|
|
92
|
+
- lib/telegram_bot_engine/registry.rb
|
|
70
93
|
- lib/telegram_bot_engine/subscriber_commands.rb
|
|
71
94
|
- lib/telegram_bot_engine/version.rb
|
|
95
|
+
- lib/telegram_bot_engine/webhook_registrar.rb
|
|
72
96
|
homepage: https://github.com/landovsky/telegram-bot-channels-gem
|
|
73
97
|
licenses:
|
|
74
98
|
- MIT
|
|
75
|
-
metadata:
|
|
99
|
+
metadata:
|
|
100
|
+
source_code_uri: https://github.com/landovsky/telegram-bot-channels-gem
|
|
101
|
+
changelog_uri: https://github.com/landovsky/telegram-bot-channels-gem/blob/main/CHANGELOG.md
|
|
102
|
+
bug_tracker_uri: https://github.com/landovsky/telegram-bot-channels-gem/issues
|
|
103
|
+
rubygems_mfa_required: 'true'
|
|
76
104
|
rdoc_options: []
|
|
77
105
|
require_paths:
|
|
78
106
|
- lib
|