trovobot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/lib/trovobot.rb +136 -0
  4. data/trovobot.gemspec +18 -0
  5. metadata +88 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6b7663f68f63115e61353d82886cf07854ed3d9fd595397342d19574f0ea8738
4
+ data.tar.gz: 78308c69ccfc5806298182b18506aa1eebf5ab86ea86424c9bda23915687b943
5
+ SHA512:
6
+ metadata.gz: 33b9d983a123386370e417fc4f2b6dcee48020ac9fdaa3266d7d202a1ab58180280e3ff54294df139e5293fafa130c86b28fa2cfc128c3dbcabffa4908bb2502
7
+ data.tar.gz: de52c29a50ffb70d18211c651c89b981a180dad962a138c5c44b5b6c57cd84a767ae9ff3e19fd8b6c70d2ac315576b7421388a119fc6ab92ebfb1d2e2b62323f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Victor Maslov
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 in all
13
+ 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 NONINFRINGEMENT. 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.
data/lib/trovobot.rb ADDED
@@ -0,0 +1,136 @@
1
+ module TrovoBot
2
+ require_relative "trovobot/common"
3
+
4
+ require "async"
5
+ require "async/semaphore"
6
+ require "nethttputils"
7
+ require "pp"
8
+
9
+ SEMAPHORE_TIME = Async::Semaphore.new
10
+ private_constant :SEMAPHORE_TIME
11
+ SEMAPHORE_NETHTTP = Async::Semaphore.new 5
12
+ @prev = Time.now
13
+ def self.access_token
14
+ JSON.load( ( Common::cache_text "tokens.json" do
15
+ NetHTTPUtils.request_data "https://open-api.trovo.live/openplatform/exchangetoken", :POST, :json, header: {
16
+ "Client-ID" => File.read("clientid"),
17
+ }, form: {
18
+ client_secret: File.read("clientsecret"),
19
+ grant_type: "authorization_code",
20
+ code: File.read("auth_code"),
21
+ redirect_uri: "https://trovo.live/",
22
+ }
23
+ end ) ).fetch("access_token")
24
+ end
25
+ def self.request mtd, form = {}
26
+ Sync do
27
+ sleep SEMAPHORE_TIME.async{ [@prev + 0.1 - Time.now, 0].max.tap{ @prev = Time.now } }.wait
28
+ JSON.load( SEMAPHORE_NETHTTP.async do
29
+ NetHTTPUtils.request_data "https://open-api.trovo.live/openplatform/#{mtd}", :POST, :json, header: {
30
+ "Client-ID" => File.read("clientid"),
31
+ "Authorization" => "OAuth #{access_token}",
32
+ }, form: form
33
+ rescue NetHTTPUtils::Error => e
34
+ p e.body
35
+ case e.code
36
+ when 401
37
+ fail unless 11714 == JSON.load(e.body).fetch("status")
38
+ Common::cache_text "tokens.json", true do
39
+ NetHTTPUtils.request_data "https://open-api.trovo.live/openplatform/refreshtoken", :POST, :json, header: {
40
+ "Client-ID" => File.read("clientid"),
41
+ }, form: {
42
+ client_secret: File.read("clientsecret"),
43
+ grant_type: "refresh_token",
44
+ refresh_token: JSON.load(File.read"tokens.json").fetch("refresh_token"),
45
+ }
46
+ end
47
+ sleep 1 # TODO: remove?
48
+ retry
49
+ when 400
50
+ fail unless 20000 == JSON.load(e.body).fetch("status")
51
+ pp [mtd, form]
52
+ raise
53
+ end
54
+ end.wait )
55
+ end
56
+ end
57
+ def self.name_to_id name
58
+ request("getusers", {user: [name || fail]})["users"].map{ |_| _["channel_id"] }[0].to_i
59
+ end
60
+
61
+ class << self
62
+ attr_accessor :queue
63
+ end
64
+ self.queue = Queue.new
65
+ Thread.new do
66
+ loop do
67
+ content, channel_id = queue.pop
68
+ fail unless channel_id # omitting (for sending to own channel) isn't implemented yet
69
+ pp TrovoBot::request "chat/send", {content: content, channel_id: channel_id}
70
+ sleep 1
71
+ end
72
+ end.abort_on_exception = true
73
+
74
+ def self.start
75
+ puts "admin -- #{ARGV[0]}"
76
+ puts "channel -- #{ARGV[1]}"
77
+ require "async/websocket/client"
78
+ require "async/http/endpoint"
79
+ Async do |task|
80
+ puts "debug: Async"
81
+ channel_id = name_to_id ARGV[1]
82
+ chat_token = JSON.load( SEMAPHORE_NETHTTP.async do
83
+ NetHTTPUtils.request_data "https://open-api.trovo.live/openplatform/chat/channel-token/#{channel_id}", header: {
84
+ "Accept" => "application/json",
85
+ "Client-ID" => File.read("clientid"),
86
+ }
87
+ end.wait )["token"]
88
+ loop do
89
+ puts "debug: loop"
90
+ Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse "wss://open-chat.trovo.live/chat", alpn_protocols: Async::HTTP::Protocol::HTTP11.names) do |connection|
91
+ puts "debug: connection"
92
+ ping_task = task.async do
93
+ loop do
94
+ sleep 30
95
+ connection.write( {type: "PING", nonce: ""} )
96
+ connection.flush
97
+ end
98
+ end
99
+ connection.write( {type: "AUTH", nonce: "", data: {token: chat_token}} )
100
+ # TODO: wrap the file in a semaphore
101
+ require "fileutils"
102
+ FileUtils.touch "processed.jsonl" unless File.exist? "processed.jsonl"
103
+ to_skip = File.read("processed.jsonl").split("\n")
104
+ # trovo may send the same message but with slightly different attributes, such as avatar, so we store only ids
105
+ while msg = connection.read
106
+ # outdated?
107
+ # for efficiency if I start processing then I immediately stop skipping,
108
+ # so I don't process until it's type="CHAT" with new id
109
+ # for now we only yield and track CHATs
110
+ # next if msg.fetch(:type) == "PONG"
111
+ next unless msg[:type] == "CHAT"
112
+ new_msgs = msg[:data].fetch(:chats, []).reject{ |_| (to_skip || []).include? _.fetch :message_id }
113
+ next if new_msgs.empty?
114
+ to_skip = nil
115
+ puts "< #{Time.now} #{Base64.strict_encode64 msg.to_s}"
116
+ puts msg.pretty_inspect.gsub(/^/, "< ")
117
+ new_msgs.each do |msg|
118
+ yield msg, channel_id
119
+ rescue
120
+ puts $!.full_message
121
+ TrovoBot::queue.push ["error at #{ARGV[1]}: #{$!}, #{$!.backtrace.first}", name_to_id(ARGV[0])]
122
+ else
123
+ File.open("processed.jsonl", "a"){ |_| _.puts msg.fetch :message_id }
124
+ end
125
+ end
126
+ ensure
127
+ ping_task&.stop
128
+ end
129
+ rescue Async::WebSocket::ProtocolError, OpenSSL::SSL::SSLError
130
+ p $!
131
+ sleep 5
132
+ retry
133
+ end
134
+ end # we wanted to rescue Async::* errors to retry this but it appeared to also throw other kinds of exceptions so we just loop
135
+ end
136
+ end
data/trovobot.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "trovobot"
3
+ spec.version = "0.0.1"
4
+ spec.summary = "Trovo Live API library"
5
+
6
+ spec.author = "Victor Maslov aka Nakilon"
7
+ spec.email = "nakilon@gmail.com"
8
+ spec.license = "MIT"
9
+ spec.metadata = {"source_code_uri" => "https://github.com/nakilon/trovobot"}
10
+
11
+ spec.required_ruby_version = ">=3"
12
+
13
+ spec.add_dependency "nethttputils", "~>0.4.3.2"
14
+ spec.add_dependency "async-http"
15
+ spec.add_dependency "async-websocket"
16
+
17
+ spec.files = %w{ LICENSE trovobot.gemspec lib/trovobot.rb }
18
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trovobot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Victor Maslov aka Nakilon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-02-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nethttputils
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.4.3.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.4.3.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: async-http
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: async-websocket
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description:
56
+ email: nakilon@gmail.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - LICENSE
62
+ - lib/trovobot.rb
63
+ - trovobot.gemspec
64
+ homepage:
65
+ licenses:
66
+ - MIT
67
+ metadata:
68
+ source_code_uri: https://github.com/nakilon/trovobot
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '3'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.2.22
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Trovo Live API library
88
+ test_files: []