├── .pdd ├── .gitignore ├── renovate.json ├── .0pdd.yml ├── .github └── workflows │ ├── xcop.yml │ ├── copyrights.yml │ ├── reuse.yml │ ├── typos.yml │ ├── pdd.yml │ ├── yamllint.yml │ ├── markdown-lint.yml │ ├── codecov.yml │ ├── actionlint.yml │ └── rake.yml ├── Gemfile ├── .rultor.yml ├── REUSE.toml ├── .rubocop.yml ├── telepost.gemspec ├── test ├── test__helper.rb └── test_telepost.rb ├── Rakefile ├── LICENSE.txt ├── LICENSES └── MIT.txt ├── README.md ├── lib └── telepost.rb ├── logo.svg └── Gemfile.lock /.pdd: -------------------------------------------------------------------------------- 1 | --source=. 2 | --verbose 3 | --rule min-words:20 4 | --rule min-estimate:15 5 | --rule max-estimate:90 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | .DS_Store 3 | .idea/ 4 | .yardoc/ 5 | *.gem 6 | coverage/ 7 | doc/ 8 | node_modules/ 9 | rdoc/ 10 | vendor/ 11 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.0pdd.yml: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | --- 6 | errors: 7 | - yegor256@gmail.com 8 | 9 | tags: 10 | - pdd 11 | - bug 12 | -------------------------------------------------------------------------------- /.github/workflows/xcop.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: xcop 6 | 'on': 7 | push: 8 | pull_request: 9 | jobs: 10 | xcop: 11 | timeout-minutes: 15 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - uses: actions/checkout@v6 15 | - uses: g4s8/xcop-action@master 16 | -------------------------------------------------------------------------------- /.github/workflows/copyrights.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: copyrights 6 | 'on': 7 | push: 8 | pull_request: 9 | jobs: 10 | copyrights: 11 | timeout-minutes: 15 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - uses: actions/checkout@v6 15 | - uses: yegor256/copyrights-action@0.0.12 16 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: reuse 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | reuse: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v6 19 | - uses: fsfe/reuse-action@v6 20 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: typos 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | typos: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v6 19 | - uses: crate-ci/typos@v1.40.0 20 | -------------------------------------------------------------------------------- /.github/workflows/pdd.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: pdd 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | pdd: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v6 19 | - uses: volodya-lombrozo/pdd-action@master 20 | -------------------------------------------------------------------------------- /.github/workflows/yamllint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: yamllint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | yamllint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v6 19 | - uses: ibiqlik/action-yamllint@v3 20 | -------------------------------------------------------------------------------- /.github/workflows/markdown-lint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: markdown-lint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | paths-ignore: ['paper/**', 'sandbox/**'] 14 | concurrency: 15 | group: markdown-lint-${{ github.ref }} 16 | cancel-in-progress: true 17 | jobs: 18 | markdown-lint: 19 | timeout-minutes: 15 20 | runs-on: ubuntu-24.04 21 | steps: 22 | - uses: actions/checkout@v6 23 | - uses: DavidAnson/markdownlint-cli2-action@v22.0.0 24 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | source 'https://rubygems.org' 7 | gemspec 8 | 9 | gem 'minitest', '~>5.25', require: false 10 | gem 'minitest-reporters', '~>1.7', require: false 11 | gem 'rake', '~>13.2', require: false 12 | gem 'rubocop', '~>1.71', require: false 13 | gem 'rubocop-minitest', '~>0.38', require: false 14 | gem 'rubocop-performance', '~>1.25', require: false 15 | gem 'rubocop-rake', '~>0.7', require: false 16 | gem 'simplecov', '~>0.22', require: false 17 | gem 'simplecov-cobertura', '~>3.0', require: false 18 | gem 'webmock', '~>3.23', require: false 19 | gem 'yard', '~>0.9', require: false 20 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: codecov 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | jobs: 11 | codecov: 12 | timeout-minutes: 15 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@v6 16 | - uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: 3.4.8 19 | bundler-cache: true 20 | - run: bundle config set --global path "$(pwd)/vendor/bundle" 21 | - run: bundle install --no-color 22 | - run: bundle exec rake 23 | - uses: codecov/codecov-action@v5 24 | with: 25 | token: ${{ secrets.CODECOV_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/actionlint.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: actionlint 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | actionlint: 15 | timeout-minutes: 15 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v6 19 | - name: Download actionlint 20 | id: get_actionlint 21 | run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 22 | shell: bash 23 | - name: Check workflow files 24 | run: ${{ steps.get_actionlint.outputs.executable }} -color 25 | shell: bash 26 | -------------------------------------------------------------------------------- /.rultor.yml: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | --- 6 | # yamllint disable rule:line-length 7 | docker: 8 | image: yegor256/ruby 9 | assets: 10 | rubygems.yml: yegor256/home#assets/rubygems.yml 11 | install: |- 12 | pdd -f /dev/null 13 | sudo bundle install --no-color "--gemfile=$(pwd)/Gemfile" 14 | release: 15 | pre: false 16 | script: |- 17 | bundle exec rake 18 | rm -rf *.gem 19 | sed -i "s/0\.0\.0/${tag}/g" telepost.gemspec 20 | git add telepost.gemspec 21 | git commit -m "Version set to ${tag}" 22 | gem build telepost.gemspec 23 | chmod 0600 ../rubygems.yml 24 | gem push *.gem --config-file ../rubygems.yml 25 | merge: 26 | script: |- 27 | bundle exec rake 28 | -------------------------------------------------------------------------------- /.github/workflows/rake.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | --- 4 | # yamllint disable rule:line-length 5 | name: rake 6 | 'on': 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | jobs: 14 | test: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-24.04, macos-15, windows-2022] 18 | ruby: [3.3] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v6 22 | - uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: ${{ matrix.ruby }} 25 | bundler-cache: true 26 | - run: bundle config set --global path "$(pwd)/vendor/bundle" 27 | - run: bundle install --no-color 28 | - run: bundle exec rake 29 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko 2 | # SPDX-License-Identifier: MIT 3 | 4 | version = 1 5 | [[annotations]] 6 | path = [ 7 | ".DS_Store", 8 | ".gitattributes", 9 | ".gitignore", 10 | ".pdd", 11 | "**.json", 12 | "**.md", 13 | "**.png", 14 | "**.svg", 15 | "**.txt", 16 | "**/.DS_Store", 17 | "**/.gitignore", 18 | "**/.pdd", 19 | "**/*.csv", 20 | "**/*.jpg", 21 | "**/*.json", 22 | "**/*.md", 23 | "**/*.pdf", 24 | "**/*.png", 25 | "**/*.svg", 26 | "**/*.txt", 27 | "**/*.vm", 28 | "**/CNAME", 29 | "**/Gemfile.lock", 30 | "Gemfile.lock", 31 | "README.md", 32 | "renovate.json", 33 | ] 34 | precedence = "override" 35 | SPDX-FileCopyrightText = "Copyright (c) 2025 Yegor Bugayenko" 36 | SPDX-License-Identifier = "MIT" 37 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | --- 6 | AllCops: 7 | Exclude: 8 | - 'bin/**/*' 9 | - 'assets/**/*' 10 | - 'vendor/**/*' 11 | - 'j/**/*' 12 | DisplayCopNames: true 13 | TargetRubyVersion: 3.2 14 | NewCops: enable 15 | SuggestExtensions: false 16 | plugins: 17 | - rubocop-rake 18 | - rubocop-minitest 19 | - rubocop-performance 20 | Minitest/EmptyLineBeforeAssertionMethods: 21 | Enabled: false 22 | Layout/EmptyLineAfterGuardClause: 23 | Enabled: false 24 | Layout/MultilineMethodCallIndentation: 25 | Enabled: false 26 | Metrics/AbcSize: 27 | Max: 50 28 | Metrics/MethodLength: 29 | Max: 30 30 | Metrics/CyclomaticComplexity: 31 | Max: 10 32 | Metrics/PerceivedComplexity: 33 | Max: 10 34 | Metrics/ParameterLists: 35 | Max: 10 36 | Layout/ParameterAlignment: 37 | Enabled: false 38 | require: [] 39 | -------------------------------------------------------------------------------- /telepost.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | require 'English' 7 | 8 | Gem::Specification.new do |s| 9 | s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version= 10 | s.required_ruby_version = '>=3.2' 11 | s.name = 'telepost' 12 | s.version = '0.0.0' 13 | s.license = 'MIT' 14 | s.summary = 'Simple Telegram posting Ruby gem' 15 | s.description = 'Simple Telegram posting Ruby gem' 16 | s.authors = ['Yegor Bugayenko'] 17 | s.email = 'yegor256@gmail.com' 18 | s.homepage = 'https://github.com/yegor256/telepost' 19 | s.files = `git ls-files | grep -v -E '^(test/|\\.|renovate)'`.split($RS) 20 | s.rdoc_options = ['--charset=UTF-8'] 21 | s.extra_rdoc_files = ['README.md'] 22 | s.add_dependency 'telegram-bot-ruby', '~> 1.0' 23 | s.metadata['rubygems_mfa_required'] = 'true' 24 | end 25 | -------------------------------------------------------------------------------- /test/test__helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | $stdout.sync = true 7 | 8 | require 'simplecov' 9 | require 'simplecov-cobertura' 10 | unless SimpleCov.running || ENV['PICKS'] 11 | SimpleCov.command_name('test') 12 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( 13 | [ 14 | SimpleCov::Formatter::HTMLFormatter, 15 | SimpleCov::Formatter::CoberturaFormatter 16 | ] 17 | ) 18 | SimpleCov.minimum_coverage 85 19 | SimpleCov.minimum_coverage_by_file 85 20 | SimpleCov.start do 21 | add_filter 'test/' 22 | add_filter 'vendor/' 23 | add_filter 'target/' 24 | track_files 'lib/**/*.rb' 25 | track_files '*.rb' 26 | end 27 | end 28 | 29 | require 'minitest/autorun' 30 | require 'minitest/reporters' 31 | Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new] 32 | Minitest.load :minitest_reporter 33 | 34 | require 'webmock/minitest' 35 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | require 'rubygems' 7 | require 'rake' 8 | require 'rake/clean' 9 | 10 | CLEAN = FileList['coverage'] 11 | 12 | def name 13 | @name ||= File.basename(Dir['*.gemspec'].first, '.*') 14 | end 15 | 16 | def version 17 | Gem::Specification.load(Dir['*.gemspec'].first).version 18 | end 19 | 20 | task default: %i[clean test yard rubocop] 21 | 22 | require 'rake/testtask' 23 | desc 'Run all unit tests' 24 | Rake::TestTask.new(:test) do |test| 25 | test.libs << 'lib' << 'test' 26 | test.pattern = 'test/**/test_*.rb' 27 | test.verbose = false 28 | end 29 | 30 | require 'rubocop/rake_task' 31 | desc 'Run RuboCop on all directories' 32 | RuboCop::RakeTask.new(:rubocop) do |task| 33 | task.fail_on_error = true 34 | end 35 | 36 | require 'yard' 37 | desc 'Build Yard documentation' 38 | YARD::Rake::YardocTask.new do |t| 39 | t.files = ['lib/**/*.rb'] 40 | t.options = ['--fail-on-warning'] 41 | end 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-2025 Yegor Bugayenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-2025 Yegor Bugayenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/test_telepost.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | require 'yaml' 7 | require_relative 'test__helper' 8 | require_relative '../lib/telepost' 9 | 10 | # Telepost test. 11 | # Author:: Yegor Bugayenko (yegor256@gmail.com) 12 | # Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko 13 | # License:: MIT 14 | class TelepostTest < Minitest::Test 15 | def test_fake_posting 16 | tp = Telepost::Fake.new 17 | tp.run 18 | tp.post(123, 'This is', 'a simple', 'message') 19 | assert_equal(1, tp.sent.count) 20 | end 21 | 22 | def test_fake_spam 23 | tp = Telepost::Fake.new 24 | tp.spam('how are you all?') 25 | end 26 | 27 | def test_sends_single_message 28 | WebMock.disable_net_connect! 29 | stub_request(:post, 'https://api.telegram.org/botfoo/sendMessage').to_return(body: '{}') 30 | tp = Telepost.new('foo') 31 | tp.post(42, 'hello!') 32 | end 33 | 34 | def test_sends_simple_spam 35 | WebMock.disable_net_connect! 36 | stub_request(:post, 'https://api.telegram.org/bottoken/sendMessage').to_return(body: '{}') 37 | tp = Telepost.new('token', chats: [42]) 38 | tp.spam('hey!') 39 | end 40 | 41 | def test_listens 42 | WebMock.disable_net_connect! 43 | stub_request(:post, 'https://api.telegram.org/botxx/getUpdates') 44 | .to_return(status: 200, body: '{}') 45 | tp = Telepost.new('xx') 46 | t = 47 | Thread.new do 48 | tp.run do |chat, msg| 49 | # we'll never reach this point 50 | end 51 | end 52 | t.terminate 53 | t.join 54 | end 55 | 56 | def test_real_posting 57 | WebMock.enable_net_connect! 58 | cfg = '/code/home/assets/zerocracy/baza.yml' 59 | skip unless File.exist?(cfg) 60 | yaml = YAML.safe_load_file(cfg) 61 | tp = Telepost.new( 62 | yaml['tg']['token'], 63 | chats: [yaml['tg']['admin_chat'].to_i] 64 | ) 65 | tp.spam('This is just a test message from telepost test') 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby Telegram Client 2 | 3 | [![EO principles respected here](https://www.elegantobjects.org/badge.svg)](https://www.elegantobjects.org) 4 | [![DevOps By Rultor.com](https://www.rultor.com/b/yegor256/telepost)](https://www.rultor.com/p/yegor256/telepost) 5 | [![We recommend RubyMine](https://www.elegantobjects.org/rubymine.svg)](https://www.jetbrains.com/ruby/) 6 | 7 | [![rake](https://github.com/yegor256/telepost/actions/workflows/rake.yml/badge.svg)](https://github.com/yegor256/telepost/actions/workflows/rake.yml) 8 | [![Gem Version](https://badge.fury.io/rb/telepost.svg)](https://badge.fury.io/rb/telepost) 9 | [![Maintainability](https://api.codeclimate.com/v1/badges/21aec58faee3866bdfbb/maintainability)](https://codeclimate.com/github/yegor256/telepost/maintainability) 10 | [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://rubydoc.info/github/yegor256/telepost/master/frames) 11 | [![Hits-of-Code](https://hitsofcode.com/github/yegor256/telepost)](https://hitsofcode.com/view/github/yegor256/telepost) 12 | 13 | Telepost is a simple gateway to Telegram, which can post messages 14 | and respond to primitive requests. 15 | 16 | First, get your token from [@BotFather](https://t.me/BotFather). 17 | 18 | Then, install it: 19 | 20 | ```bash 21 | gem install telepost 22 | ``` 23 | 24 | Then, use it like this: 25 | 26 | ```ruby 27 | require 'telepost' 28 | tp = Telepost.new('..token..') 29 | Thread.start do 30 | tp.run do |chat, msg| 31 | tp.post(chat, 'Thanks for talking to me!') 32 | end 33 | end 34 | tp.post(12345, 'How are you?', 'How are you doing?') 35 | ``` 36 | 37 | All lines you provide to the `post()` method will be concatenated 38 | with a space between them. 39 | 40 | Or you can pre-configure it to talk to certain list of chats. 41 | Your bot has to be an admin of the channel, in order to post there. 42 | Here is how you "spam": 43 | 44 | ```ruby 45 | tp = Telepost.new('..token..', chats: [12345]) 46 | tp.spam('How are you?') 47 | ``` 48 | 49 | That's it. 50 | 51 | ## How to contribute 52 | 53 | Read 54 | [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html). 55 | Make sure your build is green before you contribute 56 | your pull request. You will need to have 57 | [Ruby](https://www.ruby-lang.org/en/) 3.2+ and 58 | [Bundler](https://bundler.io/) installed. Then: 59 | 60 | ```bash 61 | bundle update 62 | bundle exec rake 63 | ``` 64 | 65 | If it's clean and you don't see any error messages, submit your pull request. 66 | -------------------------------------------------------------------------------- /lib/telepost.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko 4 | # SPDX-License-Identifier: MIT 5 | 6 | require 'telegram/bot' 7 | 8 | # Telepost is a simple gateway to Telegram, which can post messages and 9 | # respond to primitive requests: 10 | # 11 | # require 'telepost' 12 | # tp = Telepost.new('... secret token ...') 13 | # tp.run do |chat, msg| 14 | # # Reply to the message via tp.post(msg, chat) 15 | # end 16 | # 17 | # For more information read 18 | # {README}[https://github.com/yegor256/telepost/blob/master/README.md] file. 19 | # 20 | # Author:: Yegor Bugayenko (yegor256@gmail.com) 21 | # Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko 22 | # License:: MIT 23 | class Telepost 24 | # Fake implementation for testing 25 | # 26 | # @note Use this class for testing purposes 27 | class Fake 28 | attr_reader :sent 29 | 30 | def initialize 31 | @sent = [] 32 | end 33 | 34 | def run 35 | # Nothing to do here 36 | end 37 | 38 | def spam(*lines) 39 | post(0, lines) 40 | end 41 | 42 | def post(chat, *lines) 43 | @sent << "#{chat}: #{lines.join(' ')}" 44 | end 45 | end 46 | 47 | # When can't post a message 48 | # 49 | # @note Raised when message posting fails 50 | class CantPost < StandardError; end 51 | 52 | # To make it possible to get the client. 53 | # 54 | # @return [Telegram::Bot::Client] The Telegram bot client 55 | attr_reader :client 56 | 57 | # Makes a new object. To obtain a token you should talk 58 | # to the @BotFather in Telegram. 59 | # 60 | # @param token [String] Telegram bot token 61 | # @param chats [Array] Optional list of chat IDs 62 | def initialize(token, chats: []) 63 | @token = token 64 | @chats = chats 65 | @bot = Telegram::Bot::Client.new(@token) 66 | end 67 | 68 | # You can run a chat bot to listen to the messages coming to it, in 69 | # a separate thread. 70 | # 71 | # @yield [Integer, String] Yields chat ID and message text 72 | # @return [void] 73 | # @raise [RuntimeError] If no block is given 74 | def run 75 | raise 'Block must be given' unless block_given? 76 | @bot.listen do |message| 77 | yield(message.chat.id, message.respond_to?(:text) ? message.text : '') 78 | end 79 | rescue Net::OpenTimeout 80 | retry 81 | end 82 | 83 | # Send the message (lines will be concatenated with a space 84 | # between them) to the chats provided in the constructor 85 | # and encapsulated. 86 | # 87 | # @param lines [Array] Message lines to send 88 | # @return [void] 89 | def spam(*lines) 90 | @chats.each do |chat| 91 | post(chat, *lines) 92 | end 93 | end 94 | 95 | # Post a single message to the designated chat room. The 96 | # chat argument can either be an integer, if you know the 97 | # chat ID, or the name of the channel (your bot has to 98 | # be the admin there). The lines provided will be 99 | # concatenated with a space between them. 100 | # 101 | # @param chat [Integer, String] Chat ID or channel name 102 | # @param lines [Array] Message lines to send 103 | # @return [Telegram::Bot::Types::Message] The sent message object 104 | def post(chat, *lines, parse_mode: 'Markdown') 105 | @bot.api.send_message( 106 | chat_id: chat, 107 | parse_mode:, 108 | disable_web_page_preview: true, 109 | text: lines.join(' ') 110 | ) 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | telepost 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | telepost (0.0.0) 5 | telegram-bot-ruby (~> 1.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | addressable (2.8.7) 11 | public_suffix (>= 2.0.2, < 7.0) 12 | ansi (1.5.0) 13 | ast (2.4.3) 14 | bigdecimal (3.3.1) 15 | builder (3.3.0) 16 | concurrent-ruby (1.3.5) 17 | crack (1.0.1) 18 | bigdecimal 19 | rexml 20 | docile (1.4.1) 21 | dry-core (1.1.0) 22 | concurrent-ruby (~> 1.0) 23 | logger 24 | zeitwerk (~> 2.6) 25 | dry-inflector (1.2.0) 26 | dry-logic (1.6.0) 27 | bigdecimal 28 | concurrent-ruby (~> 1.0) 29 | dry-core (~> 1.1) 30 | zeitwerk (~> 2.6) 31 | dry-struct (1.8.0) 32 | dry-core (~> 1.1) 33 | dry-types (~> 1.8, >= 1.8.2) 34 | ice_nine (~> 0.11) 35 | zeitwerk (~> 2.6) 36 | dry-types (1.8.3) 37 | bigdecimal (~> 3.0) 38 | concurrent-ruby (~> 1.0) 39 | dry-core (~> 1.0) 40 | dry-inflector (~> 1.0) 41 | dry-logic (~> 1.4) 42 | zeitwerk (~> 2.6) 43 | faraday (2.13.2) 44 | faraday-net_http (>= 2.0, < 3.5) 45 | json 46 | logger 47 | faraday-multipart (1.1.1) 48 | multipart-post (~> 2.0) 49 | faraday-net_http (3.4.1) 50 | net-http (>= 0.5.0) 51 | hashdiff (1.2.1) 52 | ice_nine (0.11.2) 53 | json (2.18.0) 54 | language_server-protocol (3.17.0.5) 55 | lint_roller (1.1.0) 56 | logger (1.7.0) 57 | minitest (5.27.0) 58 | minitest-reporters (1.7.1) 59 | ansi 60 | builder 61 | minitest (>= 5.0) 62 | ruby-progressbar 63 | multipart-post (2.4.1) 64 | net-http (0.6.0) 65 | uri 66 | parallel (1.27.0) 67 | parser (3.3.10.0) 68 | ast (~> 2.4.1) 69 | racc 70 | prism (1.6.0) 71 | public_suffix (6.0.2) 72 | racc (1.8.1) 73 | rainbow (3.1.1) 74 | rake (13.3.1) 75 | regexp_parser (2.11.3) 76 | rexml (3.4.4) 77 | rubocop (1.82.0) 78 | json (~> 2.3) 79 | language_server-protocol (~> 3.17.0.2) 80 | lint_roller (~> 1.1.0) 81 | parallel (~> 1.10) 82 | parser (>= 3.3.0.2) 83 | rainbow (>= 2.2.2, < 4.0) 84 | regexp_parser (>= 2.9.3, < 3.0) 85 | rubocop-ast (>= 1.48.0, < 2.0) 86 | ruby-progressbar (~> 1.7) 87 | unicode-display_width (>= 2.4.0, < 4.0) 88 | rubocop-ast (1.48.0) 89 | parser (>= 3.3.7.2) 90 | prism (~> 1.4) 91 | rubocop-minitest (0.38.2) 92 | lint_roller (~> 1.1) 93 | rubocop (>= 1.75.0, < 2.0) 94 | rubocop-ast (>= 1.38.0, < 2.0) 95 | rubocop-performance (1.26.1) 96 | lint_roller (~> 1.1) 97 | rubocop (>= 1.75.0, < 2.0) 98 | rubocop-ast (>= 1.47.1, < 2.0) 99 | rubocop-rake (0.7.1) 100 | lint_roller (~> 1.1) 101 | rubocop (>= 1.72.1) 102 | ruby-progressbar (1.13.0) 103 | simplecov (0.22.0) 104 | docile (~> 1.1) 105 | simplecov-html (~> 0.11) 106 | simplecov_json_formatter (~> 0.1) 107 | simplecov-cobertura (3.1.0) 108 | rexml 109 | simplecov (~> 0.19) 110 | simplecov-html (0.13.2) 111 | simplecov_json_formatter (0.1.4) 112 | telegram-bot-ruby (1.0.0) 113 | dry-struct (~> 1.6) 114 | faraday (~> 2.0) 115 | faraday-multipart (~> 1.0) 116 | zeitwerk (~> 2.6) 117 | unicode-display_width (3.2.0) 118 | unicode-emoji (~> 4.1) 119 | unicode-emoji (4.1.0) 120 | uri (1.0.3) 121 | webmock (3.26.1) 122 | addressable (>= 2.8.0) 123 | crack (>= 0.3.2) 124 | hashdiff (>= 0.4.0, < 2.0.0) 125 | yard (0.9.38) 126 | zeitwerk (2.7.3) 127 | 128 | PLATFORMS 129 | arm64-darwin-22 130 | arm64-darwin-23 131 | ruby 132 | x64-mingw-ucrt 133 | x86_64-linux 134 | 135 | DEPENDENCIES 136 | minitest (~> 5.25) 137 | minitest-reporters (~> 1.7) 138 | rake (~> 13.2) 139 | rubocop (~> 1.71) 140 | rubocop-minitest (~> 0.38) 141 | rubocop-performance (~> 1.25) 142 | rubocop-rake (~> 0.7) 143 | simplecov (~> 0.22) 144 | simplecov-cobertura (~> 3.0) 145 | telepost! 146 | webmock (~> 3.23) 147 | yard (~> 0.9) 148 | 149 | BUNDLED WITH 150 | 2.5.16 151 | --------------------------------------------------------------------------------