trovobot 0.0.1

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.
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: []