mchat 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mchat
4
+ VERSION = "0.1.0"
5
+ 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
@@ -0,0 +1,4 @@
1
+ module Mchat
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []