sleeproom 0.1.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.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SleepRoom
4
+ module Record
5
+ module API
6
+ class RoomAPI
7
+ def initialize(room_url_key)
8
+ @url = ROOM_API + "?room_url_key=" + room_url_key
9
+ @json = nil
10
+ get
11
+ end
12
+
13
+ def get(task: Async::Task.current)
14
+ @json = API.get(@url).wait
15
+ end
16
+
17
+ def live?
18
+ @json["is_live"]
19
+ end
20
+
21
+ def broadcast_key
22
+ @json["broadcast_key"].to_s
23
+ end
24
+
25
+ def broadcast_host
26
+ @json["broadcast_host"].to_s
27
+ end
28
+
29
+ def room_id
30
+ @json["room_id"]
31
+ end
32
+
33
+ def room_name
34
+ @json["room_name"]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SleepRoom
4
+ module Record
5
+ module API
6
+ class StreamingAPI
7
+ def initialize(room_id)
8
+ @url = STREAMING_API + "?room_id=" + room_id.to_s + "&ignore_low_stream=1"
9
+ @json = nil
10
+ get
11
+ end
12
+
13
+ def get(task: Async::Task.current)
14
+ @json = API.get(@url).wait
15
+ end
16
+
17
+ def streaming_url
18
+ @json["streaming_url_list"].sort_by{|hash| -hash["quality"]}.first["url"]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+ require "sleeproom/record/write_status"
3
+
4
+ module SleepRoom
5
+ module Record
6
+ class Showroom
7
+ SITE = "showroom"
8
+ def initialize(room:, group: "default", queue:)
9
+ @room = room
10
+ @group = group
11
+ @queue = queue
12
+ @running = false
13
+ @downlaoding = false
14
+ @reconnection = false
15
+ end
16
+
17
+ # @param user [String]
18
+ # @return [Boolean]
19
+ def record(reconnection: false)
20
+ room = @room
21
+ Async do |task|
22
+ api = API::RoomAPI.new(room)
23
+ task.async do |t|
24
+ while api.live?
25
+ if status = SleepRoom.load_config(:status).find{|hash| hash[:room] == room}
26
+ if !status[:pid].nil?
27
+ break if SleepRoom.running?(pid) == false
28
+ else
29
+ break
30
+ end
31
+ else
32
+ break
33
+ end
34
+ t.sleep 60
35
+ end
36
+ end.wait
37
+ @room_id = api.room_id
38
+ @room_name = api.room_name
39
+ @is_live = api.live?
40
+ @broadcast_host = api.broadcast_host
41
+ @broadcast_key = api.broadcast_key
42
+ if @is_live
43
+ start_time = Time.now
44
+ log("Live broadcast.")
45
+ streaming_url = parse_streaming_url
46
+ output = build_output
47
+ pid = SleepRoom::Record.call_minyami(url: streaming_url, output: output)
48
+ downloading(streaming_url, pid, start_time)
49
+ record
50
+ else
51
+ log("Status: Stop.")
52
+ waiting_live(ws: :init)
53
+ Async do |task|
54
+ while true
55
+ if @running == false && @reconnection == false
56
+ start_websocket
57
+ elsif @reconnection == true
58
+ api = API::RoomAPI.new(room)
59
+ @room_id = api.room_id
60
+ @room_name = api.room_name
61
+ @is_live = api.live?
62
+ @broadcast_host = api.broadcast_host
63
+ @broadcast_key = api.broadcast_key
64
+ start_websocket
65
+ @reconnection = false
66
+ end
67
+ task.sleep 10
68
+ end
69
+ end
70
+ end
71
+ rescue => e
72
+ add_error(e)
73
+ SleepRoom.error(e.full_message)
74
+ log("Retry...")
75
+ task.sleep 5
76
+ retry
77
+ end
78
+ end
79
+
80
+ def log(str)
81
+ SleepRoom.info("[#{@room}] #{str}")
82
+ end
83
+
84
+ private
85
+ def start_websocket()
86
+ Async do |task|
87
+ @running = true
88
+ log("Broadcast Key: #{@broadcast_key}")
89
+ waiting_live(ws: :init)
90
+ ws = WebSocket.new(room: @room, broadcast_key: @broadcast_key, url: @broadcast_host)
91
+ @ws = ws
92
+ # ws status
93
+ ws_task = task.async do |sub|
94
+ ws.running = true
95
+ ws.connect(task: sub) do |message|
96
+ case message["t"].to_i
97
+ when 101
98
+ log("Live stop.")
99
+ ws.running = false
100
+ @running = false
101
+ record
102
+ when 104
103
+ log("Live start.")
104
+ start_time = Time.now
105
+ streaming_url = parse_streaming_url
106
+ output = build_output
107
+ pid = SleepRoom::Record.call_minyami(url: streaming_url, output: output)
108
+ downloading(streaming_url, pid, start_time)
109
+ ws.running = false
110
+ @running = false
111
+ @reconnection = true
112
+ else
113
+ # other
114
+ end
115
+ end
116
+ rescue => e
117
+ SleepRoom.error("WS Stop.")
118
+ SleepRoom.error(e.full_message)
119
+ ws.running = false
120
+ @running = false
121
+ add_error(e)
122
+ end
123
+
124
+ Async do |task|
125
+ while @running && @downlaoding == false
126
+ status = ws.status
127
+ if !status[:last_ack].nil? && Time.now.to_i - status[:last_ack].to_i > 65
128
+ ws.running = false
129
+ @running = false
130
+ task.stop
131
+ end
132
+ waiting_live(status)
133
+ task.sleep 30
134
+ end
135
+ end
136
+ task.children.each(&:wait)
137
+ ensure
138
+ ws.running = false
139
+ @running = false
140
+ end
141
+ end
142
+
143
+ def parse_streaming_url(task: Async::Task.current)
144
+ api = API::StreamingAPI.new(@room_id)
145
+ streaming_url_list = api.streaming_url
146
+ end
147
+
148
+ def build_output(task: Async::Task.current)
149
+ room = @room
150
+ group = @group
151
+ tmp_str = configatron.default_save_name
152
+ tmp_str = tmp_str.sub("\%TIME\%", Time.now.strftime("%Y-%m-%d-%H-%M-%S")) if tmp_str.include?("\%TIME\%")
153
+ tmp_str = tmp_str.sub("\%ROOMNAME\%", room) if tmp_str.include?("\%ROOMNAME\%")
154
+ File.join(group, room, "showroom", tmp_str)
155
+ end
156
+
157
+ def downloading(streaming_url, pid, start_time, task: Async::Task.current)
158
+ @downlaoding = true
159
+ @queue.add({
160
+ room: @room,
161
+ start_time: start_time,
162
+ name: @room_name,
163
+ group: @group,
164
+ live: true,
165
+ status: :downloading,
166
+ streaming_url: streaming_url,
167
+ download_pid: pid
168
+ })
169
+ task.async do |t|
170
+ while true
171
+ if !SleepRoom.running?(pid) && !API::RoomAPI.new(@room).live?
172
+ log("Download complete.")
173
+ @downlaoding = false
174
+ @queue.add({
175
+ room: @room,
176
+ name: @room_name,
177
+ group: @group,
178
+ live: API::RoomAPI.new(@room).live?,
179
+ status: :complete,
180
+ })
181
+ break
182
+ end
183
+ t.sleep 60
184
+ end
185
+ end.wait
186
+ end
187
+
188
+ def add_error(error)
189
+ @queue.add({
190
+ room: @room,
191
+ name: @room_name,
192
+ group: @group,
193
+ status: :retry,
194
+ error: error.message
195
+ })
196
+ end
197
+
198
+ def waiting_live(status)
199
+ @queue.add({
200
+ room: @room,
201
+ live: false,
202
+ group: @group,
203
+ name: @room_name,
204
+ status: :waiting,
205
+ ws: status
206
+ })
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+ require "terminal-table"
3
+
4
+ module SleepRoom
5
+ module Record
6
+ class Tasks
7
+ # @return [void]
8
+ def self.start
9
+ Async do |_task|
10
+ count = 0
11
+ write_status = WriteStatus.new
12
+ SleepRoom.reload_config
13
+ if SleepRoom.running?
14
+ SleepRoom.error("PID #{SleepRoom.load_pid} Process is already running.")
15
+ exit
16
+ else
17
+ SleepRoom.write_config_file(:status, [])
18
+ end
19
+ SleepRoom.create_pid(Process.pid)
20
+ lists = SleepRoom.load_config(:record)
21
+ lists.each do |group, list|
22
+ SleepRoom.info("Empty list.") if list.empty?
23
+ list.each do |room|
24
+ record = SleepRoom::Record::Showroom.new(room: room["room"], group: group, queue: write_status)
25
+ record.record
26
+ count += 1
27
+ end
28
+ rescue
29
+ SleepRoom.error("Cannot parse Recording list.")
30
+ end
31
+ write_status.run
32
+ SleepRoom.info("共启动 #{count} 个任务.")
33
+ wait
34
+ rescue => e
35
+ puts e.full_message
36
+ end
37
+ rescue Exception
38
+ SleepRoom.create_pid(nil) unless SleepRoom.running?
39
+ puts "Exit..."
40
+ end
41
+
42
+ # @return [void]
43
+ def self.stop
44
+ SleepRoom.reload_config
45
+ raise "未实现"
46
+ end
47
+
48
+ # @return [void]
49
+ def self.status
50
+ Async do
51
+ SleepRoom.reload_config
52
+ status = SleepRoom.load_status
53
+ pid = SleepRoom.load_config(:pid)
54
+ if !SleepRoom.running?(pid) || status.empty? || pid.nil?
55
+ lists = SleepRoom.load_config(:record)
56
+ SleepRoom.info("No tasks running.")
57
+ lists.each do |group, list|
58
+ next if list.empty?
59
+ rows = []
60
+ title = group
61
+ headings = list[0].keys
62
+ list.each do |hash|
63
+ rows.push(hash.values)
64
+ end
65
+ puts Terminal::Table.new(title: "[Recording list] Group: #{title}",:rows => rows, headings: headings)
66
+ end
67
+ else
68
+ rows = []
69
+ headings = status[0].keys
70
+ status.each do |hash|
71
+ rows.push(
72
+ hash.values.map do |s|
73
+ if s.is_a?(Hash)
74
+ "#{(s[:last_ack].is_a?(Time) ? "[ACK]" + s[:last_ack].strftime("%H:%M:%S").to_s : "nil")}"
75
+ elsif s.is_a?(Time)
76
+ s.strftime("%H:%M:%S")
77
+ else
78
+ s.to_s
79
+ end
80
+ end
81
+ )
82
+ end
83
+ puts Terminal::Table.new(title: "Status [PID #{pid}] (#{status.count})",:rows => rows, headings: headings)
84
+ end
85
+ end
86
+ end
87
+
88
+ def self.add(room, group)
89
+ Async do
90
+ group = "default" if group.empty?
91
+ old_record = SleepRoom.load_config(:record)
92
+ name = API::RoomAPI.new(room).room_name
93
+ input_record = {"room" => room, "name" => name}
94
+ if !old_record[group].nil? && new_record = old_record[group].find{|h| h = input_record if h["room"] == room}
95
+ SleepRoom.error("Room #{room} already exists.")
96
+ else
97
+ old_record[group] = [] if old_record[group].nil?
98
+ old_record[group].push(input_record)
99
+ new_record = old_record
100
+ SleepRoom.write_config_file(:record, new_record)
101
+ SleepRoom.info("Added success.")
102
+ end
103
+ end
104
+ end
105
+
106
+ def self.remove(room)
107
+ old_record = SleepRoom.load_config(:record)
108
+ new_record = old_record.each {|k, v| v.delete_if { |h| h["room"] == room }}
109
+ SleepRoom.write_config_file(:record, new_record)
110
+ SleepRoom.info("Remove success.")
111
+ end
112
+
113
+ private
114
+ def self.wait
115
+ Async do |task|
116
+ while true
117
+ task.sleep 1
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,91 @@
1
+ require "sleeproom/async/websocket"
2
+ require "async/http/endpoint"
3
+ require "async/websocket/client"
4
+ require "json"
5
+
6
+ module SleepRoom
7
+ module Record
8
+ class WebSocket
9
+ def initialize(room:, broadcast_key:, url:)
10
+ @room = room
11
+ @url = "wss://" + url
12
+ @broadcast_key = broadcast_key
13
+ @running = false
14
+ @status = {}
15
+ end
16
+
17
+ def connect(task: Async::Task.current)
18
+ url = @url
19
+ endpoint = Async::HTTP::Endpoint.parse(url)
20
+ Async::WebSocket::Client.connect(endpoint, handler: WebSocketConnection) do |connection|
21
+ @connection = connection
22
+ @running = true
23
+ connection.write("SUB\t#{@broadcast_key}")
24
+ connection.flush
25
+ log("Connect to websocket server.")
26
+ @status[:last_update] = Time.now
27
+
28
+ ping_task = task.async do |sub|
29
+ while @running
30
+ sub.sleep 60
31
+ @status[:last_ping] = Time.now
32
+ connection.write("PING\tshowroom")
33
+ connection.flush
34
+ end
35
+ end
36
+
37
+ status_task = task.async do |sub|
38
+ while true
39
+ sub.sleep 1
40
+ if @running == false
41
+ connection.close
42
+ end
43
+ end
44
+ end
45
+
46
+ while message = connection.read
47
+ @status[:last_update]
48
+ if message == "ACK\tshowroom"
49
+ @status[:last_ack] = Time.now if message == "ACK\tshowroom"
50
+ end
51
+ if message.start_with?("MSG")
52
+ @status[:last_msg] = Time.now
53
+ begin
54
+ yield JSON.parse(message.split("\t")[2])
55
+ rescue => e
56
+ SleepRoom.error(e.message)
57
+ end
58
+ end
59
+ end
60
+ rescue => e
61
+ SleepRoom.error(e.message)
62
+ ensure
63
+ ping_task&.stop
64
+ connection.close
65
+ log("WebSocket closed.")
66
+ end
67
+ end
68
+
69
+ def running?
70
+ @running
71
+ end
72
+
73
+ def running=(bool)
74
+ @running = bool
75
+ end
76
+
77
+ def stop
78
+ @running = false
79
+ @connection.close
80
+ end
81
+
82
+ def status
83
+ @status
84
+ end
85
+
86
+ def log(str)
87
+ SleepRoom.info("[#{@room}] #{str}")
88
+ end
89
+ end
90
+ end
91
+ end