mchat 0.1.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.
- checksums.yaml +7 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +34 -0
- data/LICENSE.txt +21 -0
- data/README.md +115 -0
- data/Rakefile +21 -0
- data/assets/preview.png +0 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/mchat +62 -0
- data/exe/mchat_repl +8 -0
- data/exe/mchat_timeline +8 -0
- data/exe/mchat_uninstall +8 -0
- data/lib/mchat/api.rb +131 -0
- data/lib/mchat/command.rb +29 -0
- data/lib/mchat/commands/bossmode.rb +34 -0
- data/lib/mchat/commands/channel.rb +87 -0
- data/lib/mchat/commands/channel_new.rb +61 -0
- data/lib/mchat/commands/clear.rb +34 -0
- data/lib/mchat/commands/default.rb +39 -0
- data/lib/mchat/commands/guide.rb +33 -0
- data/lib/mchat/commands/help.rb +55 -0
- data/lib/mchat/commands/join.rb +47 -0
- data/lib/mchat/commands/leave.rb +43 -0
- data/lib/mchat/commands/message.rb +53 -0
- data/lib/mchat/commands/name.rb +47 -0
- data/lib/mchat/commands/quit.rb +38 -0
- data/lib/mchat/comps/font.rb +38 -0
- data/lib/mchat/comps/message.rb +18 -0
- data/lib/mchat/comps/timeline_api.rb +17 -0
- data/lib/mchat/comps/user_config.rb +55 -0
- data/lib/mchat/comps/welcome.rb +23 -0
- data/lib/mchat/fake_log.txt +29 -0
- data/lib/mchat/logger.rb +21 -0
- data/lib/mchat/repl.rb +245 -0
- data/lib/mchat/store.rb +94 -0
- data/lib/mchat/timeline.rb +108 -0
- data/lib/mchat/version.rb +5 -0
- data/lib/mchat.rb +12 -0
- data/sig/mchat.rbs +4 -0
- metadata +89 -0
data/lib/mchat/repl.rb
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
require_relative "./version"
|
2
|
+
|
3
|
+
require_relative "./comps/font"
|
4
|
+
# monkey patch!
|
5
|
+
# give String styles
|
6
|
+
class String
|
7
|
+
include Mchat::Style
|
8
|
+
end
|
9
|
+
|
10
|
+
require_relative "./comps/user_config"
|
11
|
+
require_relative "./api"
|
12
|
+
require_relative "./command"
|
13
|
+
require_relative "./comps/timeline_api"
|
14
|
+
require_relative "./comps/welcome"
|
15
|
+
require_relative "./comps/message"
|
16
|
+
|
17
|
+
require_relative "./store"
|
18
|
+
|
19
|
+
module Mchat
|
20
|
+
module ModApi
|
21
|
+
def _current_channel
|
22
|
+
@current_channel
|
23
|
+
end
|
24
|
+
|
25
|
+
def _set_current_channel(channel_name)
|
26
|
+
@current_channel = channel_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def _current_nickname
|
30
|
+
@current_nickname
|
31
|
+
end
|
32
|
+
|
33
|
+
def _set_current_nickname(nickname)
|
34
|
+
@current_nickname = nickname
|
35
|
+
end
|
36
|
+
|
37
|
+
def _chat_screen_print(content)
|
38
|
+
# TODO add log
|
39
|
+
# @printer.display(content)
|
40
|
+
@store.message_writer(content)
|
41
|
+
end
|
42
|
+
|
43
|
+
def _cli_screen_print(content)
|
44
|
+
# TODO add log
|
45
|
+
puts content
|
46
|
+
end
|
47
|
+
|
48
|
+
def _dispatch(name, content = nil)
|
49
|
+
if content
|
50
|
+
__send__(name, content)
|
51
|
+
else
|
52
|
+
__send__(name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
alias _puts _cli_screen_print
|
57
|
+
alias _puts2 _chat_screen_print
|
58
|
+
|
59
|
+
def _mchat_speak(content)
|
60
|
+
_puts2 Message.new({
|
61
|
+
"user_name" => "Mchat",
|
62
|
+
"timestamp" => Time.now.to_i,
|
63
|
+
"content" => content
|
64
|
+
}).display
|
65
|
+
end
|
66
|
+
|
67
|
+
def _mchat_action(content)
|
68
|
+
_puts2 Message.new({
|
69
|
+
"user_name" => "Mchat [action]",
|
70
|
+
"timestamp" => Time.now.to_i,
|
71
|
+
"content" => content
|
72
|
+
}).display
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
module Mchat
|
77
|
+
# Core REPL class
|
78
|
+
class Repl
|
79
|
+
|
80
|
+
include UserConfig
|
81
|
+
include Command
|
82
|
+
include Welcome
|
83
|
+
include TimelineApi
|
84
|
+
|
85
|
+
install_commands = [
|
86
|
+
:help,
|
87
|
+
:guide,
|
88
|
+
:channel,
|
89
|
+
:channel_new,
|
90
|
+
:join,
|
91
|
+
:name,
|
92
|
+
:message,
|
93
|
+
:leave,
|
94
|
+
:clear,
|
95
|
+
:quit,
|
96
|
+
:bossmode,
|
97
|
+
:default
|
98
|
+
]
|
99
|
+
|
100
|
+
install_commands.each do |command|
|
101
|
+
Command.install command
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# Instance ########################3
|
106
|
+
|
107
|
+
def initialize
|
108
|
+
first_time_use
|
109
|
+
|
110
|
+
@config = read_user_config
|
111
|
+
@server = @config.fetch("server") || 'localhost:4567'
|
112
|
+
|
113
|
+
@api = ::Mchat::Request.new(@server)
|
114
|
+
|
115
|
+
@wait_prefix = @config.fetch("wait_prefix") || ">>"
|
116
|
+
@display_welcome = @config.fetch("display_welcome") || true
|
117
|
+
|
118
|
+
|
119
|
+
@channel_message_poll_time = 1 # seconds
|
120
|
+
@channel_message_poll_running = true # global lock
|
121
|
+
|
122
|
+
@channel_heartbeat_running = true # global lock
|
123
|
+
@channel_heartbeat_time = 2 # global lock
|
124
|
+
|
125
|
+
@clear_repl_everytime = @config.fetch("clear_repl_everytime") || false # global lock
|
126
|
+
|
127
|
+
@current_channel = nil
|
128
|
+
@current_nickname = nil
|
129
|
+
|
130
|
+
@store = Mchat::Store.new({
|
131
|
+
field_name: :messages
|
132
|
+
})
|
133
|
+
end
|
134
|
+
|
135
|
+
def _api
|
136
|
+
@api
|
137
|
+
end
|
138
|
+
|
139
|
+
def fetch_channel_task
|
140
|
+
Thread.new do
|
141
|
+
last_news_time = 0
|
142
|
+
while _current_channel && @channel_message_poll_running
|
143
|
+
resp = _api.fetch_channel_message(_current_channel)
|
144
|
+
data = resp.fetch("data")
|
145
|
+
messages = data["messages"] || []
|
146
|
+
|
147
|
+
news = messages.select { |m| m["timestamp"].to_i > last_news_time.to_i }
|
148
|
+
news.sort! { |a, b| a["timestamp"].to_i <=> b["timestamp"].to_i }
|
149
|
+
|
150
|
+
if news.length.positive?
|
151
|
+
content = ""
|
152
|
+
news.each do |m|
|
153
|
+
content << Message.new(m).display
|
154
|
+
end
|
155
|
+
_puts2 content
|
156
|
+
last_news_time = news.last["timestamp"]
|
157
|
+
end
|
158
|
+
sleep @channel_message_poll_time
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def channel_heartbeat_task
|
164
|
+
Thread.new do
|
165
|
+
while _current_channel && _current_nickname && @channel_heartbeat_running
|
166
|
+
resp = _api.ping_channel(_current_channel, _current_nickname)
|
167
|
+
sleep @channel_heartbeat_time
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def parser(raw)
|
173
|
+
words = raw.strip
|
174
|
+
catch :halt do
|
175
|
+
CommandConditions.each do |command|
|
176
|
+
command_condition = command[:command_condition]
|
177
|
+
command_condition.each do |cc|
|
178
|
+
if cc.match(words)
|
179
|
+
content = $1 ? $1 : nil
|
180
|
+
_dispatch(command[:command_run], content)
|
181
|
+
throw :halt
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def user_hint_prefix
|
189
|
+
printf "#{_current_channel ? '['+_current_channel+']' : '' }#{_current_nickname ? '@'+_current_nickname : '' }#{@wait_prefix}"
|
190
|
+
end
|
191
|
+
|
192
|
+
include Mchat::ModApi
|
193
|
+
|
194
|
+
def tick_work
|
195
|
+
begin
|
196
|
+
user_hint_prefix
|
197
|
+
user_type_in = gets
|
198
|
+
|
199
|
+
@clear_repl_everytime && system('clear')
|
200
|
+
parser(user_type_in)
|
201
|
+
sleep 0.1
|
202
|
+
rescue => exception
|
203
|
+
p exception
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def init_help_message
|
208
|
+
_puts "Mchat #{Mchat::VERSION}"
|
209
|
+
_puts "/h[elp] for help".style.primary
|
210
|
+
_puts ""
|
211
|
+
end
|
212
|
+
|
213
|
+
def conn_server
|
214
|
+
resp = _api.conn_server_startup
|
215
|
+
startup_msg = resp.fetch("data")
|
216
|
+
return Message.new(startup_msg).display
|
217
|
+
end
|
218
|
+
|
219
|
+
def before_loop_setup
|
220
|
+
# cli
|
221
|
+
_puts welcome(@display_welcome)
|
222
|
+
|
223
|
+
init_help_message
|
224
|
+
# chat printer
|
225
|
+
_puts2 welcome(@display_welcome)
|
226
|
+
# _puts2 conn_server
|
227
|
+
end
|
228
|
+
|
229
|
+
def run
|
230
|
+
before_loop_setup
|
231
|
+
loop do
|
232
|
+
tick_work
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
if __FILE__ == $0
|
240
|
+
repl = Mchat::Repl.new
|
241
|
+
|
242
|
+
trap("INT") { repl.quit_command_run }
|
243
|
+
|
244
|
+
repl.run
|
245
|
+
end
|
data/lib/mchat/store.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'pstore'
|
3
|
+
|
4
|
+
module Mchat
|
5
|
+
class Store
|
6
|
+
|
7
|
+
attr_accessor :store_messages_reader_run
|
8
|
+
def initialize(opt={})
|
9
|
+
# TODO use path to read dir
|
10
|
+
@store_dir = Pathname.new(Dir.home).join('.mchat')
|
11
|
+
@store_path = opt[:store_path] || Pathname.new(Dir.home).join('.mchat').join('mchatdb')
|
12
|
+
@store_sync_time = opt[:store_sync_time] || 1
|
13
|
+
@store_async_flag = opt[:store_async_flag] || false
|
14
|
+
@field_name = opt[:field_name]
|
15
|
+
@field_history_name = "#{@field_name.to_s}_history".to_sym
|
16
|
+
|
17
|
+
@store_messages_reader_run = true
|
18
|
+
@store = nil
|
19
|
+
end
|
20
|
+
def store_exist?
|
21
|
+
File.exist? @store_path
|
22
|
+
end
|
23
|
+
def create_store
|
24
|
+
require 'fileutils'
|
25
|
+
FileUtils.mkdir_p(@store_dir) unless File.exist?(@store_dir)
|
26
|
+
PStore.new(@store_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_store
|
30
|
+
if !store_exist?
|
31
|
+
create_store
|
32
|
+
end
|
33
|
+
|
34
|
+
@store = ::PStore.new(@store_path)
|
35
|
+
|
36
|
+
@store.transaction do
|
37
|
+
@store[@field_history_name] ||= Array.new
|
38
|
+
@store[@field_name] ||= Array.new
|
39
|
+
end
|
40
|
+
|
41
|
+
@store
|
42
|
+
end
|
43
|
+
|
44
|
+
def messages_history(count)
|
45
|
+
last_messages = @store[@field_history_name].last(count)
|
46
|
+
return last_messages
|
47
|
+
end
|
48
|
+
|
49
|
+
def message_writer(content)
|
50
|
+
get_store
|
51
|
+
@store.transaction do
|
52
|
+
if content.is_a? Array
|
53
|
+
@store[@field_name] += content
|
54
|
+
else
|
55
|
+
@store[@field_name] << content
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def message_loop_reader
|
61
|
+
get_store
|
62
|
+
thx = Thread.new {
|
63
|
+
while @store_messages_reader_run do
|
64
|
+
@store.transaction do
|
65
|
+
messages = @store[@field_name]
|
66
|
+
messages.each do |m|
|
67
|
+
end
|
68
|
+
# puts m
|
69
|
+
if block_given?
|
70
|
+
yield(messages)
|
71
|
+
end
|
72
|
+
@store[@field_history_name] += messages
|
73
|
+
@store[@field_name] = []
|
74
|
+
end
|
75
|
+
sleep @store_sync_time
|
76
|
+
end
|
77
|
+
}
|
78
|
+
|
79
|
+
if(!@store_async_flag)
|
80
|
+
thx.join
|
81
|
+
end
|
82
|
+
|
83
|
+
return thx
|
84
|
+
end
|
85
|
+
|
86
|
+
def hook_quit
|
87
|
+
@store.transaction do
|
88
|
+
messages = @store[@field_name]
|
89
|
+
@store[@field_history_name] += messages
|
90
|
+
@store[@field_name] = []
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'rainbow'
|
3
|
+
require_relative './store'
|
4
|
+
|
5
|
+
module Mchat
|
6
|
+
class TimelineCommand
|
7
|
+
attr_accessor :name, :data, :used
|
8
|
+
def initialize(name, data = nil)
|
9
|
+
# name for log
|
10
|
+
@name = name
|
11
|
+
@data = data
|
12
|
+
@used = false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Timeline
|
17
|
+
def initialize
|
18
|
+
@reader = Mchat::Store.new({
|
19
|
+
field_name: :messages
|
20
|
+
})
|
21
|
+
|
22
|
+
@boss_mode = false
|
23
|
+
end
|
24
|
+
|
25
|
+
# 约定和cli交互用api_xx方法
|
26
|
+
def api_clear
|
27
|
+
system('clear')
|
28
|
+
end
|
29
|
+
|
30
|
+
def api_bossmode
|
31
|
+
# toggle api
|
32
|
+
@boss_mode = !@boss_mode
|
33
|
+
|
34
|
+
thx = nil
|
35
|
+
if @boss_mode
|
36
|
+
system('clear')
|
37
|
+
boss_will_see_fake_logs
|
38
|
+
else
|
39
|
+
sleep 2
|
40
|
+
system('clear')
|
41
|
+
puts "======= Recover last 100 ==========="
|
42
|
+
@reader.messages_history(100).each do |m|
|
43
|
+
if m.is_a? String || Rainbow::Presenter
|
44
|
+
puts m
|
45
|
+
end
|
46
|
+
end
|
47
|
+
puts "======= Recover last 100 ==========="
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def api_close_window
|
52
|
+
@reader.store_messages_reader_run = false
|
53
|
+
end
|
54
|
+
|
55
|
+
def hook_close
|
56
|
+
@reader.hook_quit
|
57
|
+
system('screen -X quit')
|
58
|
+
end
|
59
|
+
|
60
|
+
def dispatch(m)
|
61
|
+
cmd_name = m.name
|
62
|
+
data = m.data || nil
|
63
|
+
|
64
|
+
api_name = "api_#{cmd_name}".to_sym
|
65
|
+
if data
|
66
|
+
__send__(api_name, data)
|
67
|
+
else
|
68
|
+
__send__(api_name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def boss_will_see_fake_logs
|
73
|
+
fake_logs = File.open(Pathname.new(File.join(__dir__, './fake_log.txt'))).readlines
|
74
|
+
thx = Thread.new {
|
75
|
+
while @boss_mode
|
76
|
+
fake_logs.each do |line|
|
77
|
+
puts line
|
78
|
+
sleep 0.1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def run
|
85
|
+
@reader.message_loop_reader { |messages|
|
86
|
+
messages.each do |m|
|
87
|
+
if m.is_a? String || Rainbow::Presenter
|
88
|
+
if !@boss_mode
|
89
|
+
puts m
|
90
|
+
end
|
91
|
+
elsif m.is_a? Mchat::TimelineCommand
|
92
|
+
dispatch(m)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
}
|
96
|
+
hook_close
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
if __FILE__ == $0
|
103
|
+
tl = Mchat::Timeline.new
|
104
|
+
|
105
|
+
trap("INT") { tl.hook_close }
|
106
|
+
|
107
|
+
tl.run
|
108
|
+
end
|
data/lib/mchat.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "mchat/version"
|
4
|
+
|
5
|
+
module Mchat
|
6
|
+
class Error < StandardError; end
|
7
|
+
class MchatError < StandardError; end
|
8
|
+
# Your code goes here...
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative "./mchat/repl"
|
12
|
+
require_relative "./mchat/timeline"
|
data/sig/mchat.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mchat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark24
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-08-15 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: IRC like chat client power by ruby
|
14
|
+
email:
|
15
|
+
- mark.zhangyoung@qq.com
|
16
|
+
executables:
|
17
|
+
- mchat
|
18
|
+
- mchat_repl
|
19
|
+
- mchat_timeline
|
20
|
+
- mchat_uninstall
|
21
|
+
extensions: []
|
22
|
+
extra_rdoc_files: []
|
23
|
+
files:
|
24
|
+
- Gemfile
|
25
|
+
- Gemfile.lock
|
26
|
+
- LICENSE.txt
|
27
|
+
- README.md
|
28
|
+
- Rakefile
|
29
|
+
- assets/preview.png
|
30
|
+
- bin/console
|
31
|
+
- bin/setup
|
32
|
+
- exe/mchat
|
33
|
+
- exe/mchat_repl
|
34
|
+
- exe/mchat_timeline
|
35
|
+
- exe/mchat_uninstall
|
36
|
+
- lib/mchat.rb
|
37
|
+
- lib/mchat/api.rb
|
38
|
+
- lib/mchat/command.rb
|
39
|
+
- lib/mchat/commands/bossmode.rb
|
40
|
+
- lib/mchat/commands/channel.rb
|
41
|
+
- lib/mchat/commands/channel_new.rb
|
42
|
+
- lib/mchat/commands/clear.rb
|
43
|
+
- lib/mchat/commands/default.rb
|
44
|
+
- lib/mchat/commands/guide.rb
|
45
|
+
- lib/mchat/commands/help.rb
|
46
|
+
- lib/mchat/commands/join.rb
|
47
|
+
- lib/mchat/commands/leave.rb
|
48
|
+
- lib/mchat/commands/message.rb
|
49
|
+
- lib/mchat/commands/name.rb
|
50
|
+
- lib/mchat/commands/quit.rb
|
51
|
+
- lib/mchat/comps/font.rb
|
52
|
+
- lib/mchat/comps/message.rb
|
53
|
+
- lib/mchat/comps/timeline_api.rb
|
54
|
+
- lib/mchat/comps/user_config.rb
|
55
|
+
- lib/mchat/comps/welcome.rb
|
56
|
+
- lib/mchat/fake_log.txt
|
57
|
+
- lib/mchat/logger.rb
|
58
|
+
- lib/mchat/repl.rb
|
59
|
+
- lib/mchat/store.rb
|
60
|
+
- lib/mchat/timeline.rb
|
61
|
+
- lib/mchat/version.rb
|
62
|
+
- sig/mchat.rbs
|
63
|
+
homepage: https://github.com/Mark24Code/mchat
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
metadata:
|
67
|
+
homepage_uri: https://github.com/Mark24Code/mchat
|
68
|
+
source_code_uri: https://github.com/Mark24Code/mchat
|
69
|
+
changelog_uri: https://github.com/Mark24Code/mchat
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 2.6.0
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubygems_version: 3.3.3
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: IRC like chat client
|
89
|
+
test_files: []
|