mchat 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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