Skip to content

Commit 8b864c8

Browse files
committed
fix: Replace outdated tdlib-ruby with minimal ffi wrapper
as tdlib-ruby is conflict with telegram-bot-ruby as they depend on incompatible version of dry-core
1 parent 39cf333 commit 8b864c8

File tree

3 files changed

+110
-98
lines changed

3 files changed

+110
-98
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@ gem "telegram-bot-ruby", "~> 2.4"
6969

7070
gem "jieba_rb", "~> 0.0.5"
7171

72-
gem "tdlib-ruby", "0.9.4"
72+
gem "ffi"

Gemfile.lock

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ GEM
8080
bcrypt_pbkdf (1.1.1-arm64-darwin)
8181
bcrypt_pbkdf (1.1.1-x86_64-darwin)
8282
benchmark (0.4.1)
83-
bigdecimal (3.2.2)
83+
bigdecimal (3.2.3)
8484
bindex (0.8.1)
8585
bootsnap (1.18.6)
8686
msgpack (~> 1.2)
@@ -107,8 +107,6 @@ GEM
107107
reline (>= 0.3.8)
108108
dotenv (3.1.8)
109109
drb (2.2.3)
110-
dry-configurable (0.7.0)
111-
concurrent-ruby (~> 1.0)
112110
dry-core (1.1.0)
113111
concurrent-ruby (~> 1.0)
114112
logger
@@ -144,6 +142,7 @@ GEM
144142
multipart-post (~> 2.0)
145143
faraday-net_http (3.4.1)
146144
net-http (>= 0.5.0)
145+
ffi (1.15.5)
147146
fugit (1.11.2)
148147
et-orbi (~> 1, >= 1.2.11)
149148
raabro (~> 1.4)
@@ -380,8 +379,6 @@ GEM
380379
tailwindcss-ruby (4.1.12-x86_64-darwin)
381380
tailwindcss-ruby (4.1.12-x86_64-linux-gnu)
382381
tailwindcss-ruby (4.1.12-x86_64-linux-musl)
383-
tdlib-ruby (0.9.4)
384-
dry-configurable (~> 0.7)
385382
telegram-bot-ruby (2.4.0)
386383
dry-struct (~> 1.6)
387384
faraday (~> 2.0)
@@ -435,6 +432,7 @@ DEPENDENCIES
435432
brakeman
436433
capybara
437434
debug
435+
ffi
438436
importmap-rails
439437
jbuilder
440438
jieba_rb (~> 0.0.5)
@@ -451,7 +449,6 @@ DEPENDENCIES
451449
sqlite3 (>= 2.1)
452450
stimulus-rails
453451
tailwindcss-rails
454-
tdlib-ruby (= 0.9.4)
455452
telegram-bot-ruby (~> 2.4)
456453
thruster
457454
turbo-rails

lib/tasks/telegram_data_collector.rake

Lines changed: 106 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,95 @@
1-
require "tdlib-ruby"
21

32
namespace :telegram do
4-
desc "Starts the TDLib client to listen for telegram message to automatically collect spam or ham data"
3+
desc "Starts the TDLib client to listen for telegram messages"
54
task listen: :environment do
65
unless Rails.env.development?
76
puts "TDLib client must only run in development env"
87
return
98
end
109

11-
unless ENV.include?("TDLIB_PATH")
12-
help_message = <<~TEXT
13-
TDLIB_PATH environment variable not found, please build https://github.com/tdlib/td and set environment variable
14-
git clone [email protected]:tdlib/td.git
15-
cd td
16-
mkdir build
17-
cd build
18-
cmake -DCMAKE_BUILD_TYPE=Release ..
19-
cmake --build .
20-
export TDLIB_PATH=$(pwd)
10+
tdlib_path = ENV["TDLIB_PATH"]
11+
unless tdlib_path && !tdlib_path.empty?
12+
puts <<~TEXT
13+
TDLIB_PATH environment variable not found, please build https://github.com/tdlib/td and set it:
14+
15+
git clone https://github.com/tdlib/td.git
16+
cd td
17+
mkdir build && cd build
18+
cmake -DCMAKE_BUILD_TYPE=Release ..
19+
cmake --build .
20+
export TDLIB_PATH=$(pwd)
2121
TEXT
22-
puts help_message
23-
return
22+
puts "TDLIB_PATH not set. Skipping Telegram listener."
23+
next
2424
end
2525

26-
TD.configure do |config|
27-
config.lib_path = ENV.fetch("TDLIB_PATH")
26+
require "ffi"
27+
require "json"
28+
29+
# Minimal TDLib FFI wrapper, tdlib-ruby is conflict with
30+
# telegram-bot-ruby as they depends on dry-core
31+
module TDJson
32+
extend FFI::Library
33+
lib_name = "tdjson"
34+
if FFI::Platform.windows?
35+
ffi_lib File.join(ENV.fetch("TDLIB_PATH"), "#{lib_name}.dll")
36+
elsif FFI::Platform.mac?
37+
ffi_lib File.join(ENV.fetch("TDLIB_PATH"), "lib#{lib_name}.dylib")
38+
else
39+
ffi_lib File.join(ENV.fetch("TDLIB_PATH"), "lib#{lib_name}.so")
40+
end
41+
42+
attach_function :td_json_client_create, [], :pointer
43+
attach_function :td_json_client_send, [ :pointer, :string ], :void
44+
attach_function :td_json_client_receive, [ :pointer, :double ], :string
45+
attach_function :td_json_client_execute, [ :pointer, :string ], :string
46+
attach_function :td_json_client_destroy, [ :pointer ], :void
2847
end
2948

30-
TD::Api.set_log_verbosity_level(2)
31-
client = TD::Client.new
49+
class TDClient
50+
def initialize
51+
@client = TDJson.td_json_client_create
52+
end
3253

33-
begin
34-
state = nil
35-
mutex = Mutex.new
36-
cond = ConditionVariable.new
54+
def send(query)
55+
TDJson.td_json_client_send(@client, JSON.dump(query))
56+
end
3757

38-
# Authorization state handler
39-
client.on("updateAuthorizationState") do |update|
40-
new_state = update.dig("authorization_state", "@type")
41-
puts "Authorization state: #{new_state}"
58+
def receive(timeout = 1.0)
59+
raw = TDJson.td_json_client_receive(@client, timeout)
60+
raw && JSON.parse(raw)
61+
end
4262

43-
mutex.synchronize do
44-
state = new_state
45-
cond.signal
63+
def execute(query)
64+
raw = TDJson.td_json_client_execute(@client, JSON.dump(query))
65+
raw && JSON.parse(raw)
4666
end
47-
end
4867

49-
# Message Handlers
50-
client.on("updateNewMessage") do |update|
51-
puts "\nNEW MESSAGE RECEIVED"
52-
message = update["message"]
53-
chat_id = message["chat_id"]
54-
content = message["content"]
55-
56-
if content["@type"] == "messageText"
57-
message_content = content["text"]["text"]
58-
process_message(message_content)
59-
puts "Chat ID: #{chat_id} | Text: #{message_content}\n"
60-
else
61-
puts "Chat ID: #{chat_id} | Type: #{content['@type']}\n"
68+
def close
69+
TDJson.td_json_client_destroy(@client)
6270
end
63-
puts "----------------------\n"
6471
end
6572

66-
client.on("updateMessageContent") do |update|
67-
puts "MESSAGE EDITED\n"
68-
chat_id = update["chat_id"]
69-
new_content = update["new_content"]
73+
client = TDClient.new
7074

71-
if new_content["@type"] == "messageText"
72-
message_content = new_content["text"]["text"]
73-
process_message(message_content)
74-
puts "Chat ID: #{chat_id} | New Text: #{}\n"
75-
else
76-
puts "Chat ID: #{chat_id} | New Type: #{new_content['@type']}\n"
77-
end
78-
puts "----------------------\n"
79-
end
75+
# Set log level
76+
client.send({ "@type" => "setLogVerbosityLevel", "new_verbosity_level" => 2 })
8077

81-
# Authorization Loop
82-
# https://core.telegram.org/tdlib/getting-started#user-authorization
83-
current_state = nil
84-
mutex.synchronize do
78+
begin
79+
state = nil
8580
loop do
86-
# Wait for state change
87-
cond.wait(mutex) while state == current_state
88-
current_state = state
89-
puts "Processing state: #{current_state}"
81+
update = client.receive(1.0)
82+
next unless update
9083

91-
case current_state
84+
case update["@type"]
85+
when "updateAuthorizationState"
86+
new_state = update["authorization_state"]["@type"]
87+
puts "Authorization state: #{new_state}"
88+
state = new_state
89+
90+
case new_state
9291
when "authorizationStateWaitTdlibParameters"
9392
puts "Setting TDLib parameters..."
94-
mutex.unlock # Release before network call
95-
9693
params = {
9794
"@type" => "setTdlibParameters",
9895
"api_id" => Rails.application.credentials.dig(:tdlib_app_id),
@@ -107,45 +104,63 @@ export TDLIB_PATH=$(pwd)
107104
"device_model" => "Ruby TD Client",
108105
"application_version" => "1.0"
109106
}
110-
client.broadcast_and_receive(params)
111-
mutex.lock # Re-acquire after network call
107+
client.send(params)
112108

113109
when "authorizationStateWaitPhoneNumber"
114-
puts "Please, enter your phone number (e.g., +15551234567):"
115-
mutex.unlock # Release before blocking input
110+
puts "Please enter your phone number (e.g. +15551234567):"
116111
phone = STDIN.gets.strip
117-
118-
params = {
119-
"@type" => "setAuthenticationPhoneNumber",
120-
"phone_number" => phone
121-
}
122-
client.broadcast_and_receive(params)
123-
mutex.lock # Re-acquire
112+
client.send({
113+
"@type" => "setAuthenticationPhoneNumber",
114+
"phone_number" => phone
115+
})
124116

125117
when "authorizationStateWaitCode"
126-
puts "Please, enter code from Telegram/SMS:"
127-
mutex.unlock # Release before blocking input
118+
puts "Please enter the code from Telegram/SMS:"
128119
code = STDIN.gets.strip
129-
130-
params = {
131-
"@type" => "checkAuthenticationCode",
132-
"code" => code
133-
}
134-
client.broadcast_and_receive(params)
135-
mutex.lock # Re-acquire
120+
client.send({
121+
"@type" => "checkAuthenticationCode",
122+
"code" => code
123+
})
136124

137125
when "authorizationStateReady"
138126
puts "Authorization successful! Listening for messages..."
139-
# Wait for potential state changes (disconnections, etc.)
140-
cond.wait(mutex)
141127

142128
when "authorizationStateClosed"
143129
puts "Authorization closed. Exiting."
144130
break
131+
else
132+
puts "Unhandled authorization state: #{new_state}"
133+
end
134+
135+
when "updateNewMessage"
136+
message = update["message"]
137+
chat_id = message["chat_id"]
138+
content = message["content"]
139+
140+
if content["@type"] == "messageText"
141+
message_content = content["text"]["text"]
142+
process_message(message_content)
143+
puts "Chat ID: #{chat_id} | Text: #{message_content}"
144+
else
145+
puts "Chat ID: #{chat_id} | Type: #{content['@type']}"
146+
end
147+
puts "----------------------"
148+
149+
when "updateMessageContent"
150+
chat_id = update["chat_id"]
151+
new_content = update["new_content"]
145152

153+
if new_content["@type"] == "messageText"
154+
message_content = new_content["text"]["text"]
155+
process_message(message_content)
156+
puts "Chat ID: #{chat_id} | New Text: #{message_content}"
146157
else
147-
puts "Unhandled authorization state: #{current_state}"
158+
puts "Chat ID: #{chat_id} | New Type: #{new_content['@type']}"
148159
end
160+
puts "----------------------"
161+
162+
else
163+
# ignore other updates
149164
end
150165
end
151166

0 commit comments

Comments
 (0)