telegem 0.2.5 → 1.0.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.
@@ -1,3 +1,4 @@
1
+ # lib/session/middleware.rb - UPDATED
1
2
  module Telegem
2
3
  module Session
3
4
  class Middleware
@@ -9,45 +10,28 @@ module Telegem
9
10
  user_id = get_user_id(ctx)
10
11
  return next_middleware.call(ctx) unless user_id
11
12
 
13
+ # Load session
12
14
  ctx.session = @store.get(user_id) || {}
13
-
14
- begin
15
- result = next_middleware.call(ctx)
16
- # Handle async result
17
- result.is_a?(Async::Task) ? result : Async::Task.new(result)
18
- ensure
19
- @store.set(user_id, ctx.session)
20
- end
21
- end
15
+
16
+ # Run the chain
17
+ result = next_middleware.call(ctx)
18
+
19
+ # Save session (regardless of result)
20
+ @store.set(user_id, ctx.session)
21
+
22
+ # Return whatever the chain returned
23
+ result
24
+ rescue => e
25
+ # Save session even on error
26
+ @store.set(user_id, ctx.session) if user_id && ctx.session
27
+ raise e
28
+ end
29
+
22
30
  private
23
-
31
+
24
32
  def get_user_id(ctx)
25
- return nil unless ctx.from
26
-
27
- ctx.from.id
28
- end
29
- end
30
-
31
- class MemoryStore
32
- def initialize
33
- @store = {}
34
- end
35
-
36
- def get(key)
37
- @store[key]
38
- end
39
-
40
- def set(key, value)
41
- @store[key] = value
42
- end
43
-
44
- def delete(key)
45
- @store.delete(key)
46
- end
47
-
48
- def clear
49
- @store.clear
33
+ ctx.from&.id
50
34
  end
51
35
  end
52
36
  end
53
- end
37
+ end
data/lib/telegem.rb CHANGED
@@ -1,54 +1,9 @@
1
- # lib/telegem.rb
2
- require "async"
3
- require "async/http"
4
- require "mime/types"
5
- require 'logger'
6
- require 'json'
7
-
1
+ # lib/telegem.rb - MAIN ENTRY POINT
8
2
  module Telegem
9
- VERSION = '0.2.5'.freeze
10
-
11
- # Define module structure
12
- module API; end
13
- module Core; end
14
- module Session; end
15
- module Markup; end
16
- module Webhook; end
17
- module Types; end
18
-
19
- # Shortcut for creating a new bot
20
- def self.new(token, **options)
21
- Core::Bot.new(token, **options)
22
- end
23
-
24
- # Configure global settings
25
- def self.configure(&block)
26
- yield(config) if block_given?
27
- config
28
- end
29
-
30
- def self.config
31
- @config ||= Configuration.new
32
- end
33
-
34
- class Configuration
35
- attr_accessor :logger, :default_adapter, :default_concurrency,
36
- :default_session_store, :default_webhook_port
37
-
38
- def initialize
39
- @logger = Logger.new($stdout)
40
- @default_adapter = :async_http
41
- @default_concurrency = 10
42
- @default_session_store = :memory
43
- @default_webhook_port = 3000
44
- end
45
- end
46
-
47
- # Error base class
48
- class Error < StandardError; end
3
+ VERSION = "1.0.0".freeze
49
4
  end
50
5
 
51
- # Load all components
6
+ # Load core components
52
7
  require_relative 'api/client'
53
8
  require_relative 'api/types'
54
9
  require_relative 'core/bot'
@@ -58,11 +13,59 @@ require_relative 'core/scene'
58
13
  require_relative 'session/middleware'
59
14
  require_relative 'session/memory_store'
60
15
  require_relative 'markup/keyboard'
16
+ require_relative 'webhook/server'
17
+
18
+ module Telegem
19
+ # Main entry point: Telegem.new(token)
20
+ def self.new(token, **options)
21
+ Core::Bot.new(token, **options)
22
+ end
23
+
24
+ # Shortcut for creating keyboards
25
+ def self.keyboard(&block)
26
+ Markup.keyboard(&block)
27
+ end
28
+
29
+ def self.inline(&block)
30
+ Markup.inline(&block)
31
+ end
32
+
33
+ # Remove keyboard markup
34
+ def self.remove_keyboard(**options)
35
+ Markup.remove(**options)
36
+ end
37
+
38
+ # Force reply markup
39
+ def self.force_reply(**options)
40
+ Markup.force_reply(**options)
41
+ end
42
+
43
+ # Current version
44
+ def self.version
45
+ VERSION
46
+ end
47
+
48
+ # Framework information
49
+ def self.info
50
+ <<~INFO
51
+ 🤖 Telegem #{VERSION}
52
+ Modern Telegram Bot Framework for Ruby
53
+
54
+ Features:
55
+ • Async HTTPX-based API client
56
+ • Scene system for multi-step conversations
57
+ • Express.js-style middleware
58
+ • Clean Telegraf.js-inspired DSL
59
+ • Webhook and polling support
60
+ • Built-in session management
61
+ • Fluent keyboard DSL
62
+
63
+ Website: https://gitlab.com/ruby-telegem/telegem
64
+ INFO
65
+ end
66
+ end
61
67
 
62
- # Webhook might be in different location
63
- begin
64
- require_relative 'webhook/server'
65
- rescue LoadError
66
- # Try root webhook directory
67
- require_relative '../webhook/server'
68
+ # Also define a top-level shortcut for convenience
69
+ def Telegem(token, **options)
70
+ Telegem.new(token, **options)
68
71
  end
File without changes
@@ -0,0 +1,193 @@
1
+ # lib/webhook/server.rb - HTTPX VERSION
2
+ require 'json'
3
+ require 'webrick'
4
+
5
+ module Telegem
6
+ module Webhook
7
+ class Server
8
+ attr_reader :bot, :port, :host, :logger, :server, :running
9
+
10
+ def initialize(bot, port: 3000, host: '0.0.0.0', logger: nil)
11
+ @bot = bot
12
+ @port = port
13
+ @host = host
14
+ @logger = logger || Logger.new($stdout)
15
+ @server = nil
16
+ @running = false
17
+ end
18
+
19
+ def run
20
+ return if @running
21
+
22
+ @logger.info "🚀 Starting Telegem webhook server on #{@host}:#{@port}"
23
+ @logger.info "📝 Set your Telegram webhook to: #{webhook_url}"
24
+
25
+ @running = true
26
+
27
+ @server_thread = Thread.new do
28
+ begin
29
+ # Create WEBrick server
30
+ @server = WEBrick::HTTPServer.new(
31
+ Port: @port,
32
+ BindAddress: @host,
33
+ Logger: @logger,
34
+ AccessLog: []
35
+ )
36
+
37
+ # Mount the webhook endpoint
38
+ @server.mount_proc("/webhook/#{@bot.token}") do |req, res|
39
+ handle_webhook_request(req, res)
40
+ end
41
+
42
+ # Mount health check
43
+ @server.mount_proc('/health') do |req, res|
44
+ res.status = 200
45
+ res.content_type = 'text/plain'
46
+ res.body = 'OK'
47
+ end
48
+
49
+ # Mount root
50
+ @server.mount_proc('/') do |req, res|
51
+ res.status = 200
52
+ res.content_type = 'text/html'
53
+ res.body = <<~HTML
54
+ <html>
55
+ <head><title>Telegem Webhook Server</title></head>
56
+ <body>
57
+ <h1>Telegem Webhook Server</h1>
58
+ <p>Webhook URL: <code>#{webhook_url}</code></p>
59
+ <p>Status: <span style="color: green;">Running</span></p>
60
+ <p><a href="/health">Health Check</a></p>
61
+ </body>
62
+ </html>
63
+ HTML
64
+ end
65
+
66
+ # Handle shutdown signals
67
+ ['INT', 'TERM'].each do |signal|
68
+ Signal.trap(signal) { shutdown }
69
+ end
70
+
71
+ # Start the server
72
+ @server.start
73
+
74
+ rescue => e
75
+ @logger.error "❌ Webhook server error: #{e.class}: #{e.message}"
76
+ @logger.error e.backtrace.join("\n") if e.backtrace
77
+ raise
78
+ ensure
79
+ @running = false
80
+ end
81
+ end
82
+
83
+ self
84
+ end
85
+
86
+ def stop
87
+ return unless @running
88
+
89
+ @logger.info "🛑 Stopping webhook server..."
90
+ @running = false
91
+
92
+ # Stop WEBrick server
93
+ @server&.shutdown
94
+
95
+ # Wait for server thread
96
+ @server_thread&.join(5)
97
+
98
+ @logger.info "✅ Webhook server stopped"
99
+ end
100
+
101
+ alias_method :shutdown, :stop
102
+
103
+ def running?
104
+ @running
105
+ end
106
+
107
+ def webhook_url
108
+ base_url = ENV['WEBHOOK_URL'] || "http://#{@host}:#{@port}"
109
+ "#{base_url}/webhook/#{@bot.token}"
110
+ end
111
+
112
+ private
113
+
114
+ def handle_webhook_request(req, res)
115
+ # Only accept POST requests
116
+ unless req.request_method == 'POST'
117
+ res.status = 405
118
+ res.content_type = 'text/plain'
119
+ res.body = 'Method Not Allowed'
120
+ return
121
+ end
122
+
123
+ begin
124
+ # Parse the update
125
+ body = req.body.read
126
+ update_data = JSON.parse(body)
127
+
128
+ # Process the update in a separate thread to keep response fast
129
+ Thread.new do
130
+ begin
131
+ @bot.process(update_data)
132
+ rescue => e
133
+ @logger.error "Error processing update: #{e.message}"
134
+ end
135
+ end
136
+
137
+ # Immediate response to Telegram
138
+ res.status = 200
139
+ res.content_type = 'text/plain'
140
+ res.body = 'OK'
141
+
142
+ rescue JSON::ParserError => e
143
+ @logger.error "Invalid JSON in webhook request: #{e.message}"
144
+ res.status = 400
145
+ res.content_type = 'text/plain'
146
+ res.body = 'Bad Request'
147
+ rescue => e
148
+ @logger.error "Error handling webhook: #{e.class}: #{e.message}"
149
+ res.status = 500
150
+ res.content_type = 'text/plain'
151
+ res.body = 'Internal Server Error'
152
+ end
153
+ end
154
+ end
155
+
156
+ # Middleware for Rack apps (Rails, Sinatra, etc.)
157
+ class Middleware
158
+ def initialize(app, bot)
159
+ @app = app
160
+ @bot = bot
161
+ end
162
+
163
+ def call(env)
164
+ req = Rack::Request.new(env)
165
+
166
+ # Check if this is a webhook request
167
+ if req.post? && req.path == "/webhook/#{@bot.token}"
168
+ handle_webhook(req)
169
+ else
170
+ @app.call(env)
171
+ end
172
+ end
173
+
174
+ private
175
+
176
+ def handle_webhook(req)
177
+ begin
178
+ update_data = JSON.parse(req.body.read)
179
+
180
+ # Process async in background
181
+ Thread.new { @bot.process(update_data) }
182
+
183
+ [200, { 'Content-Type' => 'text/plain' }, ['OK']]
184
+ rescue JSON::ParserError
185
+ [400, { 'Content-Type' => 'text/plain' }, ['Bad Request']]
186
+ rescue => e
187
+ @bot.logger.error("Webhook error: #{e.message}") if @bot.logger
188
+ [500, { 'Content-Type' => 'text/plain' }, ['Internal Server Error']]
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
metadata CHANGED
@@ -1,91 +1,93 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telegem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Phantom
8
- bindir: bin
7
+ - Your Name
8
+ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: async
13
+ name: httpx
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '2.0'
18
+ version: 0.24.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '2.0'
25
+ version: 0.24.0
26
26
  - !ruby/object:Gem::Dependency
27
- name: async-http
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '0.60'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '0.60'
40
- - !ruby/object:Gem::Dependency
41
- name: mime-types
27
+ name: rake
42
28
  requirement: !ruby/object:Gem::Requirement
43
29
  requirements:
44
30
  - - "~>"
45
31
  - !ruby/object:Gem::Version
46
- version: '3.4'
47
- type: :runtime
32
+ version: '13.0'
33
+ type: :development
48
34
  prerelease: false
49
35
  version_requirements: !ruby/object:Gem::Requirement
50
36
  requirements:
51
37
  - - "~>"
52
38
  - !ruby/object:Gem::Version
53
- version: '3.4'
39
+ version: '13.0'
54
40
  - !ruby/object:Gem::Dependency
55
- name: rake
41
+ name: rspec
56
42
  requirement: !ruby/object:Gem::Requirement
57
43
  requirements:
58
44
  - - "~>"
59
45
  - !ruby/object:Gem::Version
60
- version: '13.0'
46
+ version: '3.0'
61
47
  type: :development
62
48
  prerelease: false
63
49
  version_requirements: !ruby/object:Gem::Requirement
64
50
  requirements:
65
51
  - - "~>"
66
52
  - !ruby/object:Gem::Version
67
- version: '13.0'
53
+ version: '3.0'
68
54
  - !ruby/object:Gem::Dependency
69
- name: rspec
55
+ name: pry
70
56
  requirement: !ruby/object:Gem::Requirement
71
57
  requirements:
72
58
  - - "~>"
73
59
  - !ruby/object:Gem::Version
74
- version: '3.0'
60
+ version: 0.14.0
75
61
  type: :development
76
62
  prerelease: false
77
63
  version_requirements: !ruby/object:Gem::Requirement
78
64
  requirements:
79
65
  - - "~>"
80
66
  - !ruby/object:Gem::Version
81
- version: '3.0'
82
- description: A Telegraf-inspired Telegram Bot framework with async I/O
67
+ version: 0.14.0
68
+ description: Blazing-fast Telegram Bot framework with true async/await patterns, inspired
69
+ by Telegraf.js
83
70
  email:
84
- - ynghosted@icloud.com
71
+ - your-email@example.com
85
72
  executables: []
86
73
  extensions: []
87
74
  extra_rdoc_files: []
88
75
  files:
76
+ - ".replit"
77
+ - Contributing.md
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - LICENSE
81
+ - Readme.md
82
+ - Test-Projects/.gitkeep
83
+ - Test-Projects/bot_test1.rb
84
+ - Test-Projects/pizza_test_bot_guide.md
85
+ - docs/.gitkeep
86
+ - docs/Api.md
87
+ - docs/Cookbook.md
88
+ - docs/How_to_use.md
89
+ - docs/QuickStart.md
90
+ - docs/Usage.md
89
91
  - lib/api/client.rb
90
92
  - lib/api/types.rb
91
93
  - lib/core/bot.rb
@@ -96,8 +98,8 @@ files:
96
98
  - lib/session/memory_store.rb
97
99
  - lib/session/middleware.rb
98
100
  - lib/telegem.rb
99
- - telegem.gemspec
100
- - webhook/server.rb
101
+ - lib/webhook/.gitkeep
102
+ - lib/webhook/server.rb
101
103
  homepage: https://gitlab.com/ruby-telegem/telegem
102
104
  licenses:
103
105
  - MIT
@@ -105,6 +107,7 @@ metadata:
105
107
  homepage_uri: https://gitlab.com/ruby-telegem/telegem
106
108
  source_code_uri: https://gitlab.com/ruby-telegem/telegem
107
109
  changelog_uri: https://gitlab.com/ruby-telegem/telegem/-/blob/main/CHANGELOG.md
110
+ documentation_uri: https://gitlab.com/ruby-telegem/telegem/-/tree/main/docs
108
111
  rdoc_options: []
109
112
  require_paths:
110
113
  - lib
@@ -112,7 +115,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
112
115
  requirements:
113
116
  - - ">="
114
117
  - !ruby/object:Gem::Version
115
- version: '3.0'
118
+ version: 2.7.0
116
119
  required_rubygems_version: !ruby/object:Gem::Requirement
117
120
  requirements:
118
121
  - - ">="
data/telegem.gemspec DELETED
@@ -1,43 +0,0 @@
1
- # telegem.gemspec
2
- Gem::Specification.new do |spec|
3
- spec.name = "telegem"
4
-
5
- # Read version from lib/telegem.rb
6
- version_file = File.read('lib/telegem.rb').match(/VERSION\s*=\s*['"]([^'"]+)['"]/)
7
- spec.version = version_file ? version_file[1] : "0.2.5"
8
-
9
- spec.authors = ["Phantom"]
10
- spec.email = ["ynghosted@icloud.com"]
11
-
12
- spec.summary = "Modern, async Telegram Bot API for Ruby"
13
- spec.description = "A Telegraf-inspired Telegram Bot framework with async I/O"
14
- spec.homepage = "https://gitlab.com/ruby-telegem/telegem"
15
- spec.license = "MIT"
16
- spec.required_ruby_version = ">= 3.0"
17
-
18
- # Include all necessary files
19
- spec.files = Dir[
20
- "lib/**/*.rb", # All Ruby files in lib/
21
- "webhook/**/*.rb", # Webhook files (if in root)
22
- "README.md",
23
- "LICENSE.txt",
24
- "CHANGELOG.md",
25
- "*.gemspec"
26
- ].select { |f| File.exist?(f) }
27
-
28
- # This tells RubyGems to look in lib/ when requiring
29
- spec.require_paths = ["lib"]
30
-
31
- spec.metadata = {
32
- "homepage_uri" => "https://gitlab.com/ruby-telegem/telegem",
33
- "source_code_uri" => "https://gitlab.com/ruby-telegem/telegem",
34
- "changelog_uri" => "https://gitlab.com/ruby-telegem/telegem/-/blob/main/CHANGELOG.md"
35
- }
36
-
37
- spec.add_dependency "async", "~> 2.0"
38
- spec.add_dependency "async-http", "~> 0.60"
39
- spec.add_dependency "mime-types", "~> 3.4"
40
-
41
- spec.add_development_dependency "rake", "~> 13.0"
42
- spec.add_development_dependency "rspec", "~> 3.0"
43
- end
data/webhook/server.rb DELETED
@@ -1,86 +0,0 @@
1
- module Telegem
2
- module Webhook
3
- class Server
4
- attr_reader :bot, :endpoint, :server, :logger
5
-
6
- def initialize(bot, endpoint: nil, logger: nil)
7
- @bot = bot
8
- @endpoint = endpoint || Async::HTTP::Endpoint.parse("http://0.0.0.0:3000")
9
- @logger = logger || Logger.new($stdout)
10
- @server = nil
11
- @running = false
12
- end
13
-
14
- def run
15
- Async do |task|
16
- @server = Async::HTTP::Server.new(app, @endpoint)
17
- @running = true
18
-
19
- @logger.info "Starting webhook server on #{@endpoint}"
20
- @logger.info "Set your Telegram webhook to: #{webhook_url}"
21
-
22
- @server.run
23
- rescue => e
24
- @logger.error "Webhook server error: #{e.message}"
25
- raise
26
- ensure
27
- @running = false
28
- end
29
- end
30
-
31
- def stop
32
- Async do
33
- @server&.close
34
- @running = false
35
- @logger.info "Webhook server stopped"
36
- end
37
- end
38
-
39
- def running?
40
- @running
41
- end
42
-
43
- def webhook_url
44
- "#{@endpoint.url}/webhook/#{@bot.token}"
45
- end
46
-
47
- def app
48
- proc do |req|
49
- handle_request(req)
50
- end
51
- end
52
-
53
- def process_webhook_request(req)
54
- Async do
55
- body = req.body.read
56
- data = JSON.parse(body)
57
- await @bot.process(data)
58
- [200, {}, ["OK"]]
59
- rescue JSON::ParserError => e
60
- @logger.error "Invalid JSON in webhook request: #{e.message}"
61
- [400, {}, ["Bad Request"]]
62
- rescue => e
63
- @logger.error "Error processing webhook request: #{e.message}"
64
- [500, {}, ["Internal Server Error"]]
65
- end
66
- end
67
-
68
- def handle_request(req)
69
- Async do
70
- case req.path
71
- when "/webhook/#{@bot.token}"
72
- process_webhook_request(req)
73
- when "/health"
74
- [200, {}, ["OK"]]
75
- else
76
- [404, {}, ["Not Found"]]
77
- end
78
- end
79
- end
80
-
81
- def call(req)
82
- handle_request(req)
83
- end
84
- end
85
- end
86
- end