noticed-telegram 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 818bb29f28c5721aa2464aa330b78da51bb99015da403fc14d0688b73d2878f2
4
+ data.tar.gz: 5f290a6330badbe90b0747bc36f1b04bd1b6cfedd416ddec38983eb064bc018c
5
+ SHA512:
6
+ metadata.gz: '084477fff5171b9a82b34d020dacba55745955646240aac56518f7168f256a293000b1b3aeaf8cdcdb71a23981d417d2c4675160fa940d275b93fde93ca5a8aa'
7
+ data.tar.gz: c10396e7c40435ffde0770cef47ad4b94c5327ef01a22c865f9d5ca304d8a8f6ac5a07fc4cb41691a3a015816169672cbeb6f2933356dddd04711540f81d4d65
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.1.0] - 2026-11-14
6
+
7
+ - `deliver_by :telegram` and `bulk_deliver_by :telegram` for Noticed 2.x and 3.x.
8
+ - Supports forum topics (`message_thread_id`), formatting, link previews,
9
+ silent sends, inline keyboards, and any other `sendMessage` field via `json`.
10
+ - A send counts only when Telegram returns 2xx and `"ok": true`; otherwise
11
+ `Noticed::ResponseUnsuccessful` is raised.
12
+ - The bot token is kept out of logs and error messages.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2026 Yaroslav Shmarov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # noticed-telegram
2
+
3
+ Telegram delivery methods for [Noticed](https://github.com/excid3/noticed).
4
+
5
+ - `bulk_deliver_by :telegram` sends **one message per event**, to a group, a forum topic, or a channel. Use it for team alerts like "new signup" or "payment failed".
6
+ - `deliver_by :telegram` sends **one message per recipient**, to each user's private chat with your bot.
7
+
8
+ Both call the Bot API's [`sendMessage`](https://core.telegram.org/bots/api#sendmessage). The bot token is part of Telegram's URL, so this gem keeps it out of Noticed's logs and out of `Noticed::ResponseUnsuccessful` messages, which usually end up in your error tracker.
9
+
10
+ Works with Noticed 2.x and 3.x on Rails 7.1+.
11
+
12
+ ## Install
13
+
14
+ ```ruby
15
+ # Gemfile
16
+ gem "noticed-telegram"
17
+ ```
18
+
19
+ Nothing else: once the gem is in the bundle, Noticed finds `:telegram`.
20
+
21
+ ## Set up a bot
22
+
23
+ 1. Message [@BotFather](https://t.me/botfather), send `/newbot`, and copy the token.
24
+ 2. Store it in credentials, e.g. `telegram: { bot_token: "..." }`.
25
+ 3. Find the chat ID:
26
+ - **Group or forum:** add the bot to the group, send a message there, then open `https://api.telegram.org/bot<TOKEN>/getUpdates` and read `message.chat.id` (it starts with `-100`). In a forum topic, `message.message_thread_id` is the topic ID.
27
+ - **Channel:** add the bot as an admin and use `@channelname` or the numeric `-100…` ID.
28
+ - **A user:** the user must message your bot first; their `chat.id` then appears in `getUpdates` (or in your webhook). Store it on the user.
29
+
30
+ ## Team alerts: `bulk_deliver_by :telegram`
31
+
32
+ ```ruby
33
+ class NewSignupNotifier < Noticed::Event
34
+ bulk_deliver_by :telegram do |config|
35
+ config.bot_token = Rails.application.credentials.dig(:telegram, :bot_token)
36
+ config.chat_id = "-1001234567890"
37
+ config.message_thread_id = 42 # optional: post into one forum topic
38
+ config.text = -> { "New signup: #{params[:user].email}" }
39
+ end
40
+ end
41
+
42
+ NewSignupNotifier.with(user: user).deliver
43
+ ```
44
+
45
+ Procs run in the context of the event, so `params` and `record` are available.
46
+
47
+ ## Per-user messages: `deliver_by :telegram`
48
+
49
+ ```ruby
50
+ class CommentNotifier < Noticed::Event
51
+ deliver_by :telegram do |config|
52
+ config.bot_token = Rails.application.credentials.dig(:telegram, :bot_token)
53
+ config.chat_id = -> { recipient.telegram_chat_id }
54
+ config.text = -> { "<b>#{params[:comment].author_name}</b> replied to you" }
55
+ config.parse_mode = "HTML"
56
+ config.if = -> { recipient.telegram_chat_id.present? }
57
+ end
58
+ end
59
+ ```
60
+
61
+ Procs run in the context of the notification, so `recipient` is available too.
62
+
63
+ ## Options
64
+
65
+ | Option | Required | |
66
+ |---|---|---|
67
+ | `bot_token` | yes | From @BotFather. |
68
+ | `chat_id` | yes | Numeric ID or `@channelname`. |
69
+ | `text` | | The message. Telegram rejects a send without text, so set this or put `text` in `json`. |
70
+ | `message_thread_id` | | Forum topic to post into. |
71
+ | `parse_mode` | | `"HTML"`, `"MarkdownV2"`, or `"Markdown"`. Escape user input to match. |
72
+ | `link_preview_options` | | e.g. `{ is_disabled: true }`. |
73
+ | `disable_web_page_preview` | | Older form of the above; Telegram still accepts it. |
74
+ | `disable_notification` | | `true` sends silently. |
75
+ | `protect_content` | | `true` blocks forwarding and saving. |
76
+ | `entities`, `reply_markup` | | Passed through as given. |
77
+ | `json` | | A hash merged over everything above, for any other `sendMessage` field. |
78
+ | `raise_if_not_ok` | | Defaults to `true`. Set `false` to ignore failed sends instead of raising. |
79
+
80
+ Every option accepts a value, a proc, or a symbol naming a method on the notifier, as in the rest of Noticed. Noticed's own `if`, `unless`, `wait`, and `queue` options work as usual.
81
+
82
+ ## Failures
83
+
84
+ A send counts as delivered only when Telegram answers with a 2xx status **and** `"ok": true`. Anything else raises `Noticed::ResponseUnsuccessful`, with Telegram's error description in the message and the token shown as `bot[FILTERED]`. Common causes:
85
+
86
+ - `403 Forbidden: bot was blocked by the user`: the user stopped the bot. Clear their stored chat ID.
87
+ - `400 Bad Request: chat not found`: wrong ID, or the user never messaged the bot.
88
+ - `429 Too Many Requests`: Telegram's [rate limits](https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this). Retry with backoff:
89
+
90
+ ```ruby
91
+ # config/initializers/noticed.rb
92
+ ActiveSupport.on_load(:noticed_delivery_methods_telegram) do
93
+ retry_on Noticed::ResponseUnsuccessful, wait: :polynomially_longer, attempts: 5
94
+ end
95
+
96
+ ActiveSupport.on_load(:noticed_bulk_delivery_methods_telegram) do
97
+ retry_on Noticed::ResponseUnsuccessful, wait: :polynomially_longer, attempts: 5
98
+ end
99
+ ```
100
+
101
+ ## Why a separate gem
102
+
103
+ This was [proposed to Noticed](https://github.com/excid3/noticed/pull/568). The maintainer preferred that Telegram support live in its own gem, maintained by someone who uses Telegram.
104
+
105
+ ## License
106
+
107
+ MIT
data/SECURITY.md ADDED
@@ -0,0 +1,8 @@
1
+ # Security policy
2
+
3
+ Security fixes are released for the latest version.
4
+
5
+ Please do not open a public issue for a vulnerability. Use
6
+ [GitHub private vulnerability reporting](https://github.com/yshmarov/noticed-telegram/security/advisories/new)
7
+ with the affected version, a minimal reproduction, and the impact. Never include
8
+ a real bot token.
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noticed
4
+ module BulkDeliveryMethods
5
+ # One message per event, e.g. to a team group chat or a channel.
6
+ class Telegram < BulkDeliveryMethod
7
+ include Noticed::Telegram::Api
8
+ end
9
+ end
10
+ end
11
+
12
+ ActiveSupport.run_load_hooks :noticed_bulk_delivery_methods_telegram, Noticed::BulkDeliveryMethods::Telegram
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noticed
4
+ module DeliveryMethods
5
+ # One message per recipient, e.g. to each user's private chat with your bot.
6
+ class Telegram < DeliveryMethod
7
+ include Noticed::Telegram::Api
8
+ end
9
+ end
10
+ end
11
+
12
+ ActiveSupport.run_load_hooks :noticed_delivery_methods_telegram, Noticed::DeliveryMethods::Telegram
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ module Noticed
7
+ module Telegram
8
+ # sendMessage over the Telegram Bot API, shared by the individual and bulk
9
+ # delivery methods.
10
+ #
11
+ # The request is made here rather than with Noticed's `post_request`
12
+ # because the bot token is part of the URL path, and `post_request` puts
13
+ # the full URL into the ResponseUnsuccessful message (and so into error
14
+ # trackers). Every URL this module logs or raises with has the token
15
+ # replaced by [FILTERED].
16
+ module Api
17
+ API_HOST = 'api.telegram.org'
18
+ REDACTED_URL = "https://#{API_HOST}/bot[FILTERED]/sendMessage".freeze
19
+
20
+ PASSTHROUGH_OPTIONS = %i[
21
+ text
22
+ message_thread_id
23
+ parse_mode
24
+ entities
25
+ link_preview_options
26
+ disable_web_page_preview
27
+ disable_notification
28
+ protect_content
29
+ reply_markup
30
+ ].freeze
31
+
32
+ def self.included(base)
33
+ base.required_options :bot_token, :chat_id
34
+ end
35
+
36
+ def deliver
37
+ payload = json_payload
38
+ response = send_message(payload)
39
+
40
+ raise ResponseUnsuccessful.new(response, REDACTED_URL, { json: payload }) if raise_if_not_ok? && !ok?(response)
41
+
42
+ response
43
+ end
44
+
45
+ def json_payload
46
+ payload = { chat_id: evaluate_option(:chat_id) }
47
+ PASSTHROUGH_OPTIONS.each do |option|
48
+ payload[option] = evaluate_option(option) if config.key?(option)
49
+ end
50
+
51
+ return payload unless config.key?(:json)
52
+
53
+ custom = evaluate_option(:json)
54
+ custom.is_a?(Hash) ? payload.merge(custom.symbolize_keys) : custom
55
+ end
56
+
57
+ private
58
+
59
+ def send_message(payload)
60
+ logger.debug("POST #{REDACTED_URL}")
61
+
62
+ response = Net::HTTP.start(API_HOST, 443, use_ssl: true, open_timeout: 5, read_timeout: 15) do |http|
63
+ http.post("/bot#{evaluate_option(:bot_token)}/sendMessage", payload.to_json,
64
+ 'Content-Type' => 'application/json')
65
+ end
66
+
67
+ logger.debug("Response: #{response.code}")
68
+ response
69
+ end
70
+
71
+ # Telegram answers {"ok": true, ...} on success and {"ok": false, ...}
72
+ # with a 4xx status on failure, so a send counts only when both agree.
73
+ def ok?(response)
74
+ response.is_a?(Net::HTTPSuccess) && JSON.parse(response.body.to_s)['ok'] == true
75
+ rescue JSON::ParserError
76
+ false
77
+ end
78
+
79
+ def raise_if_not_ok?
80
+ config.key?(:raise_if_not_ok) ? evaluate_option(:raise_if_not_ok) : true
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Noticed
4
+ module Telegram
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'noticed'
4
+ require 'noticed/telegram/version'
5
+
6
+ # Noticed resolves `deliver_by :telegram` to Noticed::DeliveryMethods::Telegram
7
+ # at delivery time. The classes are autoloaded rather than required because
8
+ # their superclasses inherit from Noticed::ApplicationJob, which only exists
9
+ # once the host app's autoloaders are running.
10
+ module Noticed
11
+ module Telegram
12
+ autoload :Api, 'noticed/telegram/api'
13
+ end
14
+
15
+ module DeliveryMethods
16
+ autoload :Telegram, 'noticed/delivery_methods/telegram'
17
+ end
18
+
19
+ module BulkDeliveryMethods
20
+ autoload :Telegram, 'noticed/bulk_delivery_methods/telegram'
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: noticed-telegram
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yaroslav Shmarov
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: noticed
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '4'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '2.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '4'
32
+ description: |
33
+ Send Noticed notifications through the Telegram Bot API: one message per
34
+ recipient with `deliver_by :telegram`, or one message to a group, forum
35
+ topic, or channel with `bulk_deliver_by :telegram`. The bot token is kept
36
+ out of logs and error messages.
37
+ email:
38
+ - yaroslav.shmarov@clickfunnels.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - CHANGELOG.md
44
+ - MIT-LICENSE
45
+ - README.md
46
+ - SECURITY.md
47
+ - lib/noticed/bulk_delivery_methods/telegram.rb
48
+ - lib/noticed/delivery_methods/telegram.rb
49
+ - lib/noticed/telegram.rb
50
+ - lib/noticed/telegram/api.rb
51
+ - lib/noticed/telegram/version.rb
52
+ homepage: https://github.com/yshmarov/noticed-telegram
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ homepage_uri: https://github.com/yshmarov/noticed-telegram
57
+ source_code_uri: https://github.com/yshmarov/noticed-telegram
58
+ changelog_uri: https://github.com/yshmarov/noticed-telegram/blob/main/CHANGELOG.md
59
+ bug_tracker_uri: https://github.com/yshmarov/noticed-telegram/issues
60
+ rubygems_mfa_required: 'true'
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.2'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 4.0.20
76
+ specification_version: 4
77
+ summary: 'Telegram delivery methods for Noticed: deliver_by :telegram and bulk_deliver_by
78
+ :telegram.'
79
+ test_files: []