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