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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3f0aebc0b0bcebdc9caad7172d05d3474fef6b0064023f280401a8736f19fbb6
4
+ data.tar.gz: dda796643faa8e779b12a9e4ce1714edc4625563848dfef30af3e1e93acd4276
5
+ SHA512:
6
+ metadata.gz: db62700957058f56b4ad13ea5b4f5d2e7e8b2508316e31365c11bbebf646e6ac7b053f499bf0cdf2f7a096a4c95a69e701b416e2b1eab5bf453f2ab42a55bd47
7
+ data.tar.gz: 4965daf82bc576a79ac1a05cd86ff925b9daebf4e22ce904a61814af39ae2a91c245a58da436d8e746185ded5b9947d2a74a609fa8058a510c763b95f306ed32
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in mchat.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
11
+
12
+ # gem "rubocop", "~> 1.21"
13
+
14
+ gem "rainbow"
15
+
16
+ gem "httparty"
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mchat (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ httparty (0.20.0)
10
+ mime-types (~> 3.0)
11
+ multi_xml (>= 0.5.2)
12
+ mime-types (3.4.1)
13
+ mime-types-data (~> 3.2015)
14
+ mime-types-data (3.2022.0105)
15
+ minitest (5.16.2)
16
+ multi_xml (0.6.0)
17
+ rainbow (3.1.1)
18
+ rake (13.0.6)
19
+
20
+ PLATFORMS
21
+ arm64-darwin-21
22
+ ruby
23
+ x86_64-darwin
24
+ x86_64-linux
25
+
26
+ DEPENDENCIES
27
+ httparty
28
+ mchat!
29
+ minitest (~> 5.0)
30
+ rainbow
31
+ rake (~> 13.0)
32
+
33
+ BUNDLED WITH
34
+ 2.3.3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Mark24Code
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # Mchat
2
+
3
+ Mchat is IRC like chat client.
4
+
5
+ ![preview](./assets/preview.png)
6
+
7
+ This is Mchat client repo, server repo:
8
+
9
+ * [mchat_server](https://github.com/Mark24Code/mchat_server)
10
+
11
+ ## Installation
12
+
13
+ install it yourself as:
14
+
15
+ $ gem install mchat
16
+
17
+ ## Usage
18
+
19
+ ```ruby
20
+ # enter mchat
21
+ mchat
22
+
23
+ # /h for help
24
+ /h
25
+ ```
26
+
27
+
28
+
29
+ ----
30
+
31
+ # Features
32
+
33
+ ### repl
34
+
35
+ * [x] repl主流程
36
+ * [x] 命令模块化
37
+ * [x] help 命令
38
+ * [x] channel 命令
39
+ * [x] join 命令
40
+ * [x] name 命令
41
+ * [x] message 命令
42
+ * [x] leave 命令
43
+ * [x] quit 命令
44
+ * [x] clear 命令
45
+ * [x] default mode
46
+ * [x] boss mode
47
+ * [x] 存储 Pstore 实现取代文件
48
+ ### timeline
49
+
50
+ * [x] timeline 独立
51
+ * [x] 支持简单命令
52
+ * [x] hook_quit
53
+
54
+ ### union
55
+
56
+ * [x] 联合打开screen window
57
+ * [x] 联合关闭
58
+
59
+
60
+ ### TODO features
61
+
62
+ * [ ] 密码登录用户,超级管理员
63
+ * [ ] 密码登录channel
64
+ * [x] 创建频道
65
+ * [x] 配置化
66
+ ~~* [ ] temp~~
67
+ * [ ] 日志
68
+ ~~* [ ] 优化代码,现在太分散~~
69
+ ~~* [ ] 是否要实现一个 tail 包装命令~~
70
+ * [x] 打包下载
71
+ * [ ] README update HOWTO
72
+ * [x] 指令插件化
73
+ * [ ] set 命令
74
+ * [ ] 打字机效果(需要单独的渲染,不可以文件输出)
75
+ * [ ] eval 增加连续组合命令
76
+ * [ ] 可以封装在docker里
77
+ * [ ] 合理化初始化参数
78
+ * [ ] config 三个 path 和dir 部分处理
79
+ * [x] cli 关闭通知timeline关闭
80
+ * [ ] 加密消息
81
+
82
+
83
+ # mchat_experiment
84
+
85
+ Private Repo
86
+
87
+ [mchat_experiment](https://github.com/Mark24Code/mchat_experiment)
88
+
89
+ ## 实验脚本
90
+
91
+ * module 的模块化写法
92
+ * 进程间 PStore 通信
93
+ * 模块化
94
+ * 面向对象实例
95
+
96
+ * Tempfile 的使用
97
+ * MessageQueue 线程队列的使用
98
+ * 使用 screen & screenrc 创造双屏应用
99
+
100
+ #### TUI
101
+
102
+ 模拟一个前端基于命令的浏览器
103
+
104
+ * eventloop 模拟
105
+ * vnode 虚拟dom的模拟:jsx 构建cli界面
106
+ * hooks 模拟
107
+ * lambda_vnode 纯函数式的写法模拟jsx组件
108
+ * repl 时间循环模拟
109
+ * 基于Curses 封的TUI
110
+ * 基于消息队列封装的client
111
+
112
+ ## License
113
+
114
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
115
+
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ # require "rubocop/rake_task"
13
+
14
+ # RuboCop::RakeTask.new
15
+
16
+ Rake::TestTask.new(:preview) do |t|
17
+ system("gem build mchat.gemspec && gem install ./mchat-#{Mchat::VERSION}.gem")
18
+ end
19
+
20
+
21
+ task default: %i[test]
Binary file
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "mchat"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/mchat ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ require 'pathname'
3
+ require_relative "../lib/mchat/comps/font"
4
+ class String
5
+ include Mchat::Style
6
+ end
7
+ require_relative "../lib/mchat/comps/user_config"
8
+ class MchatApp
9
+ include Mchat::UserConfig
10
+
11
+ def initialize
12
+ first_time_use
13
+ @screenrc_dir = Pathname.new(Dir.home).join('.mchat')
14
+ @screenrc = Pathname.new(Dir.home).join('.mchat').join('screenrc')
15
+ end
16
+
17
+ def get_screenrc
18
+ if !screenrc_exist?
19
+ create_screenrc
20
+ end
21
+ end
22
+ def screenrc_exist?
23
+ File.exist? @screenrc
24
+ end
25
+
26
+ def create_screenrc
27
+ require 'fileutils'
28
+ FileUtils.mkdir_p(@screenrc_dir) unless File.exist?(@screenrc_dir)
29
+ File.open(@screenrc, 'w') do |f|
30
+ init_config = %Q(
31
+ split -v
32
+
33
+ screen -t timeline 'mchat_timeline'
34
+ focus
35
+ screen -t repl 'mchat_repl'
36
+ )
37
+ f << init_config
38
+ end
39
+ end
40
+
41
+ def run
42
+ get_screenrc
43
+
44
+ # 使用Screen
45
+
46
+ # 左边文件读取记录,实现读取的信息流
47
+ # 右边是命令行,实现输入的控制流
48
+ # 这样组合来完成应用的交互形态
49
+
50
+ # https://www.gnu.org/software/screen/manual/screen.html#Split
51
+
52
+ # screenrc 里面写的就是 文档里面的命令
53
+ # split 是默认上下 split -v 是左右
54
+ # focus 是永远关注在下一个(cycle来工作的) 对应按键 C-a Tab
55
+
56
+ # -c 读取配置
57
+ system("screen -c #{@screenrc.to_s}")
58
+ end
59
+ end
60
+
61
+
62
+ MchatApp.new.run
data/exe/mchat_repl ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/mchat"
3
+
4
+ repl = Mchat::Repl.new
5
+
6
+ trap("INT") { repl.quit_command_run }
7
+
8
+ repl.run
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/mchat/timeline"
3
+
4
+ tl = Mchat::Timeline.new
5
+
6
+ trap("INT") { tl.hook_close }
7
+
8
+ tl.run
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ puts "[mchat] clean config ..."
4
+ system("rm -rf .mchat")
5
+ puts "[mchat] uninstall mchat ..."
6
+ system("gem uninstall mchat")
7
+
8
+ puts "[mchat] all files have uninstalled. Bye :D"
data/lib/mchat/api.rb ADDED
@@ -0,0 +1,131 @@
1
+ require "json"
2
+ require "httparty"
3
+
4
+ module SafeProtect
5
+ refine String do
6
+ def safe
7
+ json_parse self.strip.dump[1 .. -2]
8
+ end
9
+
10
+ def escape
11
+ json_parse self.dump[1 .. -2]
12
+ end
13
+
14
+ def unescape
15
+ "\"#{self}\"".undump
16
+ end
17
+ end
18
+ end
19
+
20
+ using SafeProtect
21
+
22
+ module Mchat
23
+
24
+ module StatusCode
25
+ # 2000 success
26
+ Success = 2000
27
+
28
+ # 5000 server error
29
+ # 50xx server machine
30
+ ServerError = 5000
31
+ # 51xx logic error
32
+
33
+ # 52xx database auth
34
+ UserHaveExist = 5201 #用户已存在
35
+ UserNotExist = 5202
36
+ # 53xx database data
37
+ InvalidParams = 5301
38
+ RecordHaveExist = 5302
39
+ RecordNotExist = 5303
40
+ end
41
+
42
+ class Request
43
+ include HTTParty
44
+
45
+ def initialize(server)
46
+ self.class.base_uri server
47
+ @headers = {"Accept": "application/json"}
48
+ end
49
+
50
+ def json_parse(resp)
51
+ JSON.parse(resp.body)
52
+ end
53
+
54
+ def server_home
55
+ json_parse self.class.get("/")
56
+ end
57
+
58
+ def ping_server
59
+ json_parse self.class.get("/ping")
60
+ end
61
+
62
+ def get_server_timestamp
63
+ json_parse self.class.get("/timestamp")
64
+ end
65
+
66
+ def conn_server_startup
67
+ json_parse self.class.get("/startup")
68
+ end
69
+
70
+ def get_channels
71
+ # 获得 channels列表
72
+ json_parse self.class.get("/channels")
73
+ end
74
+
75
+ def get_channel(channel_name)
76
+ # 获得 channels 详情
77
+ json_parse self.class.get("/channels/#{channel_name}")
78
+ end
79
+
80
+ def create_channel(channel_name)
81
+ # 创建用户频道
82
+ json_parse self.class.post("/channels/#{channel_name}")
83
+ end
84
+
85
+ def delete_channel(channel_name)
86
+ json_parse self.class.delete("/channels/#{channel_name}")
87
+ end
88
+
89
+ def join_channel(channel_name, user_name)
90
+ json_parse self.class.post(
91
+ "/channels/#{channel_name}/join",
92
+ body: { user_name: user_name }.to_json,
93
+ headers: @headers
94
+ )
95
+ end
96
+
97
+ def leave_channel(channel_name, user_name)
98
+ json_parse self.class.post(
99
+ "/channels/#{channel_name}/leave",
100
+ body: { user_name: user_name }.to_json,
101
+ headers: @headers
102
+ )
103
+ end
104
+
105
+ def ping_channel(channel_name, user_name)
106
+ json_parse self.class.post(
107
+ "/channels/#{channel_name}/ping",
108
+ body: { user_name: user_name }.to_json,
109
+ headers: @headers
110
+ )
111
+ end
112
+
113
+ def create_channel_message(channel_name, user_name, content)
114
+ json_parse self.class.post(
115
+ "/channels/#{channel_name}/messages",
116
+ body: {
117
+ user_name: user_name,
118
+ content: content
119
+ }.to_json,
120
+ headers: @headers
121
+ )
122
+ end
123
+
124
+ def fetch_channel_message(channel_name)
125
+ json_parse self.class.get(
126
+ "/channels/#{channel_name}/messages",
127
+ headers: @headers
128
+ )
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,29 @@
1
+ module Mchat
2
+ module Command
3
+ # Class ########################3
4
+ CommandComps = {}
5
+ CommandConditions = []
6
+
7
+ def self.mount_command(name, mod)
8
+ CommandComps[name] = mod
9
+ end
10
+
11
+ def self.load_command(name)
12
+ h = CommandComps
13
+ unless cmd = h[name]
14
+ require_relative "./commands/#{name}"
15
+ raise RodaError, "command #{name} did not mount itself correctly in Mchat::Command" unless cmd = h[name]
16
+ end
17
+ cmd
18
+ end
19
+
20
+ def self.install(cmd, *args, &block)
21
+ cmd = Mchat::Command.load_command(cmd) if cmd.is_a?(Symbol)
22
+ raise MchatError, "Invalid cmd type: #{cmd.class.inspect}" unless cmd.is_a?(Module)
23
+
24
+ include(cmd::InstanceMethods) if defined?(cmd::InstanceMethods)
25
+ extend(cmd::ClassMethods) if defined?(cmd::ClassMethods)
26
+ cmd.configure(self, *args, &block) if cmd.respond_to?(:configure)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ module Mchat
2
+ module Command
3
+ # Command Clear
4
+ module BossMode
5
+ def self.configure(repl)
6
+ CommandConditions.push({
7
+ name: 'bossmode',
8
+ description: "b[ossmode]\tclean mchat screen & print fake logs",
9
+ help_condition: ['bossmode'],
10
+ help_doc: :bossmode_help_doc,
11
+ command_condition: ['/bossmode', '/b'],
12
+ command_run: :bossmode_command_run
13
+ })
14
+ end
15
+ module InstanceMethods
16
+ def bossmode_help_doc
17
+ _puts %Q(
18
+ #{"Help: BossMode".style.bold}
19
+
20
+ command: /bossmode
21
+ explain: clear chat screen and print fake logs. use agan recover messages.
22
+
23
+ )
24
+ end
25
+
26
+ def bossmode_command_run(repl = nil)
27
+ timeline_bossmode
28
+ end
29
+ end
30
+ end
31
+
32
+ mount_command :bossmode, BossMode
33
+ end
34
+ end
@@ -0,0 +1,87 @@
1
+ module Mchat
2
+ module Command
3
+ module Channel
4
+ def self.configure(repl)
5
+ CommandConditions.push({
6
+ name: 'channel',
7
+ description: "ch[annel]\tfind channel list & get channel info",
8
+ help_condition: ['channel','ch'],
9
+ help_doc: :channel_help_doc,
10
+ command_condition: ['/channel', /\/channel (.*)/, '/ch', /\/ch (.*)/],
11
+ command_run: :channel_command_run
12
+ })
13
+ end
14
+ module InstanceMethods
15
+ def channel_help_doc
16
+ _puts %Q(
17
+ #{"Help: Channel".style.bold}
18
+
19
+ command: /channel <channel_name>
20
+ explain: login the channel
21
+
22
+ )
23
+ end
24
+
25
+ def channel_command_run(channel_name = nil)
26
+ if !channel_name
27
+ # 返回全部节点
28
+ resp = _api.get_channels
29
+ all_channels = resp.fetch("data")
30
+
31
+ # cli
32
+ content = "Mchat Channels:\n".style.primary
33
+ if all_channels.length > 0
34
+ all_channels.each do |c|
35
+ content << "* #{c}\n"
36
+ end
37
+ else
38
+ # TODO create channel
39
+ content << "Opps."
40
+ end
41
+
42
+ content << ""
43
+ content << "type `/join <channel_name>` to join the channel.\n"
44
+ _puts content
45
+
46
+ # printer
47
+ _mchat_action("fetch all channels")
48
+ # _puts2 content
49
+ else
50
+ # 指定节点
51
+ resp = _api.get_channel(channel_name)
52
+ code = resp.fetch("code")
53
+
54
+ if code == StatusCode::RecordNotExist
55
+ content = "#{"Mchat Channel:".style.primary} #{channel_name}\n"
56
+ content << "channel not exist. use follow create new channel.\n"
57
+ content << ">> /channel_new <channel name>"
58
+ _puts content
59
+ return
60
+ else
61
+ # 存在这个channel
62
+ data = resp.fetch("data")
63
+
64
+ online_users = data["online_users"]
65
+
66
+ # cli
67
+ content = "#{"Mchat Channel:".style.primary} #{channel_name}\n"
68
+ content << "#{"online users:".style.jade}\n"
69
+ online_users.each do |c|
70
+ c = c.split(":").last # name
71
+ content << "* #{c.style.jade}\n"
72
+ end
73
+ content << ""
74
+ content << "total: #{online_users.length}.\n"
75
+ _puts content
76
+
77
+ # printer
78
+ _mchat_action("channel #{channel_name} info:")
79
+ # _puts2 content
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ mount_command :channel, Channel
86
+ end
87
+ end
@@ -0,0 +1,61 @@
1
+ module Mchat
2
+ module Command
3
+ module ChannelNew
4
+ def self.configure(repl)
5
+ CommandConditions.push({
6
+ name: 'channel_new',
7
+ description: "channel_new\tcreate new channel",
8
+ help_condition: ['channel_new'],
9
+ help_doc: :channel_new_help_doc,
10
+ command_condition: ['/channel_new', /\/channel_new (.*)/],
11
+ command_run: :channel_new_command_run
12
+ })
13
+ end
14
+ module InstanceMethods
15
+ def channel_new_help_doc
16
+ _puts %Q(
17
+ #{"Help: Channel New".style.bold}
18
+
19
+ command: /channel_new <new_channel_name>
20
+ explain: create a new channel
21
+
22
+ )
23
+ end
24
+
25
+ def channel_new_command_run(channel_name = nil)
26
+ if !channel_name
27
+ content << "/channel_new <new_channel_name>\n".style.warn
28
+ content << "new_channel_name should not be nil\n".style.warn
29
+ _puts content
30
+ else
31
+ # 指定节点
32
+ resp = _api.create_channel(channel_name)
33
+ code = resp.fetch("code")
34
+
35
+ if code == StatusCode::RecordHaveExist
36
+ content = "#{"Mchat Channel:".style.primary} #{channel_name}\n"
37
+ content << "channel <#{channel_name}> have exist. use follow create new channel.\n"
38
+ content << "type `/channel_new <new_channel_name>`"
39
+ content << "you can also find all channels:"
40
+ content << "type `/channel`"
41
+ _puts content
42
+ return
43
+ elsif code == StatusCode::Success
44
+ # 存在这个channel
45
+ data = resp.fetch("data")
46
+
47
+ online_users = data["online_users"]
48
+
49
+ # cli
50
+ _mchat_action("you create channel: #{channel_name}")
51
+ _puts "create channel: #{channel_name} success. try auto join."
52
+
53
+ _dispatch(:join_command_run, channel_name)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ mount_command :channel_new, ChannelNew
60
+ end
61
+ end