iris_ruby 0.0.1

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: 4a9b7e41ecec4762e6334f03e230bef908e5777d49932ff92e4c5c539812e04a
4
+ data.tar.gz: 735d93cef5dfcf5ba163b5abfc6e904e569e29b20efd867904692078c87d7762
5
+ SHA512:
6
+ metadata.gz: 1a4751059a6817e5a6d3528f1f087ca5c1719c61bb4ba3fd63b05d4eeab90ffb2a7c6e6f91f239e2ab857eff99c41b7cef7d7ea9a2fc6f2a6bee254342dbe273
7
+ data.tar.gz: 26f5522b43b69d3f9d7879f308ec5b035dce88f0dcff1c02ffa73783fe7bb37bbd2b39db2e59a4f700413e019a9b2a4419548aaa1d71770cb5acfe5e21da0903
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 qweruby
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # IrisRb
2
+
3
+ Iris(https://github.com/dolidolih/Iris) 를 기반으로 루비로 포팅한 레포
4
+
5
+ ## 설치
6
+
7
+ ###
8
+ - Ruby ~>3.4.0
9
+
10
+ ### How use
11
+
12
+ ```bash
13
+ gem install bundler
14
+ bundle install
15
+ ```
16
+
17
+ ## 빠른 시작
18
+
19
+ ### 기본 사용법
20
+
21
+ ```ruby
22
+ require_relative 'lib/iris_rb/client'
23
+
24
+ def on_message(chat)
25
+ if msg == ".hi"
26
+ reply("Hello #{chat[:sender][:name]}")
27
+ elsif msg == ".이미지"
28
+ send_image(chat[:room][:id], "./image/example.jpg")
29
+ end
30
+ end
31
+
32
+ def on_newmem(chat)
33
+ reply("#{chat[:sender][:name]}님 환영합니다!")
34
+ end
35
+
36
+ def on_delmem(chat)
37
+ reply("#{chat[:sender][:name]}님 안녕히 가세요!")
38
+ end
39
+
40
+ client = IrisRb::Client.new(
41
+ url: "http://localhost:3000",
42
+ hot_reload: true
43
+ )
44
+
45
+ sleep
46
+ ```
47
+
48
+ ## API 문서
49
+
50
+ ### Client 초기화
51
+
52
+ ```ruby
53
+ client = IrisRb::Client.new(
54
+ url: "http://localhost:3000", # Iris 서버 URL
55
+ hot_reload: true # 개발 모드 (파일 변경 시 자동 재시작)
56
+ )
57
+ ```
58
+
59
+ #### 파라미터
60
+
61
+ - `url` (필수): Iris 서버의 HTTP URL
62
+ - `hot_reload` (선택): 파일 변경 감지 및 자동 재시작 활성화 (기본값: false)
63
+
64
+ ### 이벤트 핸들러
65
+
66
+ #### on_message(chat)
67
+
68
+ 일반 메시지를 받았을 때 호출됩니다.
69
+
70
+ ```ruby
71
+ def on_message(chat)
72
+ # chat 객체 구조:
73
+ # {
74
+ # room: { id: "...", name: "...", type: "..." },
75
+ # sender: { id: "...", name: "..." },
76
+ # message: { id: "...", type: "...", content: "...", attachment: {}, v: {} }
77
+ # }
78
+ end
79
+ ```
80
+
81
+ #### on_newmem(chat)
82
+
83
+ 새로운 멤버가 입장했을 때 호출됩니다.
84
+
85
+ ```ruby
86
+ def on_newmem(chat)
87
+ reply("환영합니다!")
88
+ end
89
+ ```
90
+
91
+ #### on_delmem(chat)
92
+
93
+ 멤버가 퇴장했을 때 호출됩니다.
94
+
95
+ ```ruby
96
+ def on_delmem(chat)
97
+ reply("안녕히 가세요!")
98
+ end
99
+ ```
100
+
101
+ ### 메시지 전송
102
+
103
+ #### reply(message)
104
+
105
+ 현재 방에 텍스트 메시지를 전송합니다.
106
+
107
+ ```ruby
108
+ reply("안녕하세요!")
109
+ ```
110
+
111
+ #### send_image(room_id, image_path_or_base64)
112
+
113
+ 이미지를 전송합니다.
114
+
115
+ ```ruby
116
+ # 파일 경로로 전송
117
+ send_image(chat[:room][:id], "./image/photo.jpg")
118
+
119
+ # Base64 문자열로 전송
120
+ send_image(chat[:room][:id], "iVBORw0KGgoAAAANSUhEUgAA...")
121
+ ```
122
+
123
+ #### send_image_multiple(room_id, image_base64s)
124
+
125
+ 여러 이미지를 한 번에 전송합니다.
126
+
127
+ ```ruby
128
+ client.send_image_multiple(room_id, [base64_image1, base64_image2])
129
+ ```
130
+
131
+ ### 데이터베이스 쿼리
132
+
133
+ #### query(query_str, bind = [])
134
+
135
+ SQL 쿼리를 실행합니다.
136
+
137
+ ```ruby
138
+ # 단순 쿼리
139
+ result = client.query("SELECT * FROM users")
140
+
141
+ # Prepared statement
142
+ result = client.query("SELECT * FROM users WHERE id = ?", [user_id])
143
+ ```
144
+
145
+ ### 헬퍼 함수
146
+
147
+ #### msg
148
+
149
+ 현재 메시지의 내용을 반환합니다 (전역 변수).
150
+
151
+ ```ruby
152
+ def on_message(chat)
153
+ if msg == ".help"
154
+ reply("도움말...")
155
+ end
156
+ end
157
+ ```
158
+
159
+ ## Chat 객체 구조
160
+
161
+ ```ruby
162
+ {
163
+ room: {
164
+ id: "방 ID",
165
+ name: "방 이름",
166
+ type: "방 타입"
167
+ },
168
+ sender: {
169
+ id: "발신자 ID",
170
+ name: "발신자 닉네임"
171
+ },
172
+ message: {
173
+ id: "메시지 ID",
174
+ type: "메시지 타입",
175
+ content: "메시지 내용",
176
+ attachment: {}, # 첨부파일 정보
177
+ v: {} # 추가 메타데이터
178
+ },
179
+ raw: "원본 JSON 문자열"
180
+ }
181
+ ```
182
+
183
+ ## 예제
184
+
185
+ ### 에코 봇
186
+
187
+ ```ruby
188
+ def on_message(chat)
189
+ reply("Echo: #{msg}")
190
+ end
191
+ ```
192
+
193
+ ### 명령어 봇
194
+
195
+ ```ruby
196
+ def on_message(chat)
197
+ case msg
198
+ when ".안녕"
199
+ reply("안녕하세요!")
200
+ when ".시간"
201
+ reply(Time.now.strftime("%Y-%m-%d %H:%M:%S"))
202
+ when /^.계산 (.+)/
203
+ expression = $1
204
+ begin
205
+ result = eval(expression)
206
+ reply("결과: #{result}")
207
+ rescue
208
+ reply("계산 오류")
209
+ end
210
+ end
211
+ end
212
+ ```
213
+
214
+ ### 이미지 전송 봇
215
+
216
+ ```ruby
217
+ def on_message(chat)
218
+ if msg == ".고양이"
219
+ send_image(chat[:room][:id], "./image/cat.jpg")
220
+ end
221
+ end
222
+ ```
223
+
224
+ ## 개발
225
+
226
+
227
+ ### Hot Reload
228
+
229
+ 개발 중에는 `hot_reload: true` 옵션을 사용하여 파일 변경 시 자동으로 재시작할 수 있습니다:
230
+
231
+ ```ruby
232
+ client = IrisRb::Client.new(
233
+ url: "http://localhost:3000",
234
+ hot_reload: true
235
+ )
236
+ ```
237
+
238
+ `.rb` 파일이 변경되면 자동으로 스크립트가 재시작됩니다.
239
+
240
+ ## 버전
241
+
242
+ 현재 버전: **0.0.1**
243
+
244
+ ## 라이선스
245
+
246
+ 이 프로젝트는 개인 프로젝트입니다.
@@ -0,0 +1,302 @@
1
+ require 'websocket-client-simple'
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'json'
5
+ require 'logger'
6
+ require 'listen'
7
+ require 'open-uri'
8
+ require 'base64'
9
+
10
+ module IrisRb
11
+ class Client
12
+ def initialize(url:, hot_reload: false)
13
+ if url =~ %r{^https?://}
14
+ @http_url = url
15
+ @ws_url = url.sub(%r{/$}, "") + "/ws"
16
+ else
17
+ raise ArgumentError, "url must start with http:// or https://"
18
+ end
19
+ @logger = Logger.new($stdout)
20
+ @current_room_id = nil
21
+ connect_websocket
22
+ start_hot_reload if hot_reload
23
+ end
24
+
25
+ def connect_websocket
26
+ @ws = WebSocket::Client::Simple.connect(@ws_url)
27
+ client = self
28
+
29
+ @ws.on :open do
30
+ client.log_info("WebSocket connected")
31
+ end
32
+
33
+ @ws.on :message do |msg|
34
+ client.handle_message(msg.data)
35
+ end
36
+
37
+ @ws.on :error do |e|
38
+ client.log_error("WebSocket error: #{e.message}")
39
+ end
40
+
41
+ @ws.on :close do |e|
42
+ client.log_info("WebSocket disconnected: #{e.inspect}")
43
+ end
44
+ end
45
+
46
+ def handle_message(raw_msg)
47
+ parsed = parse_json(raw_msg)
48
+ return unless parsed
49
+
50
+ json = parsed["json"]
51
+ json = parse_json(json) if json.is_a?(String)
52
+ json ||= {}
53
+
54
+ attachment = json["attachment"]
55
+ attachment = parse_json(attachment) if attachment.is_a?(String)
56
+ attachment ||= {}
57
+
58
+ v = json["v"]
59
+ v = parse_json(v) if v.is_a?(String)
60
+ v ||= {}
61
+
62
+ chat = {
63
+ room: {
64
+ id: json["chat_id"],
65
+ name: parsed["room"]
66
+ },
67
+ sender: {
68
+ id: json["user_id"],
69
+ name: parsed["sender"]
70
+ },
71
+ message: {
72
+ id: json["id"],
73
+ type: json["type"],
74
+ content: json["message"],
75
+ attachment: attachment,
76
+ v: v
77
+ },
78
+ raw: parsed["json"]
79
+ }
80
+
81
+ extended_chat = extend_chat(chat)
82
+
83
+ @current_room_id = extended_chat[:room][:id]
84
+ $current_chat = extended_chat
85
+ $client = self
86
+ $msg = extended_chat[:message][:content].strip
87
+
88
+ Thread.new do
89
+ event = event_type(v)
90
+ case event
91
+ when :message
92
+ on_message(extended_chat) if defined?(on_message)
93
+ when :new_member
94
+ on_newmem(extended_chat) if defined?(on_newmem)
95
+ when :del_member
96
+ on_delmem(extended_chat) if defined?(on_delmem)
97
+ when :unknown
98
+ # do nothing?
99
+ end
100
+ end
101
+ rescue => e
102
+ log_error("Failed to handle message: #{e}")
103
+ end
104
+
105
+ def reply(message)
106
+ if @current_room_id.nil?
107
+ raise ArgumentError, "No current room ID available. Make sure a chat event has been processed first."
108
+ end
109
+
110
+ send_reply(@current_room_id, message, "text")
111
+ end
112
+
113
+ def send_image(room_id, image_path_or_base64)
114
+ if File.exist?(image_path_or_base64)
115
+ image_base64 = image_base64(image_path_or_base64)
116
+ if image_base64.nil?
117
+ log_error("이미지 파일을 읽을 수 없습니다: #{image_path_or_base64}")
118
+ return nil
119
+ end
120
+ send_reply(room_id, image_base64, "image")
121
+ else
122
+ send_reply(room_id, image_path_or_base64, "image")
123
+ end
124
+ end
125
+
126
+ def send_image_multiple(room_id, image_base64s)
127
+ send_reply(room_id, image_base64s, "image_multiple")
128
+ end
129
+
130
+ def query(query_str, bind = [])
131
+ endpoint = URI.join(@http_url, "/query")
132
+ payload = { query: query_str, bind: bind }.to_json
133
+ headers = { "Content-Type" => "application/json" }
134
+
135
+ res = post_request(endpoint, payload, headers)
136
+ if res.is_a?(Net::HTTPSuccess)
137
+ body = JSON.parse(res.body)
138
+ body["data"]
139
+ else
140
+ log_error("Failed to run query: #{res.body}")
141
+ nil
142
+ end
143
+ rescue => e
144
+ log_error("Query error: #{e}")
145
+ nil
146
+ end
147
+
148
+ def log_info(msg)
149
+ @logger ? @logger.info(msg) : puts(msg)
150
+ end
151
+
152
+ def log_error(msg)
153
+ @logger ? @logger.error(msg) : puts(msg)
154
+ end
155
+
156
+ def start_hot_reload
157
+ current_dir = File.expand_path(File.dirname($0))
158
+ listener = Listen.to(current_dir, only: /\.rb$/) do |_modified, _added, _removed|
159
+ exec("ruby #{$0} #{ARGV.join(' ')}")
160
+ end
161
+ listener.start
162
+ end
163
+
164
+ private
165
+
166
+ def send_reply(room_id, data, type)
167
+ endpoint = URI.join(@http_url, "/reply")
168
+ payload = {
169
+ type: type,
170
+ room: room_id,
171
+ data: data
172
+ }.to_json
173
+ headers = { "Content-Type" => "application/json" }
174
+
175
+ res = post_request(endpoint, payload, headers)
176
+ if res.is_a?(Net::HTTPSuccess)
177
+ log_info("Successfully sent #{type}: #{res.code}")
178
+ else
179
+ log_error("Failed to send #{type}: #{res.body}")
180
+ end
181
+ rescue => e
182
+ log_error("Send #{type} error: #{e}")
183
+ end
184
+
185
+ def post_request(uri, payload, headers)
186
+ http = Net::HTTP.new(uri.host, uri.port)
187
+ http.use_ssl = false
188
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
189
+ req.body = payload
190
+ http.request(req)
191
+ end
192
+
193
+ def parse_json(str)
194
+ JSON.parse(str)
195
+ rescue JSON::ParserError
196
+ nil
197
+ end
198
+
199
+ def event_type(v_hash)
200
+ origin = v_hash["origin"]
201
+ case origin
202
+ when "MSG" then :message
203
+ when "NEWMEM" then :new_member
204
+ when "DELMEM" then :del_member
205
+ else :unknown
206
+ end
207
+ end
208
+
209
+ def extend_chat(chat)
210
+ extend_with_room_type(chat)
211
+ extend_with_member_info(chat)
212
+ end
213
+
214
+ def extend_with_room_type(chat)
215
+ query_str = "SELECT type FROM chat_rooms where id = ?"
216
+ room_id = chat[:room][:id]
217
+
218
+ result = query(query_str, [room_id])
219
+ type = result&.first&.fetch("type", "Unknown") || "Unknown"
220
+ chat[:room][:type] = type
221
+ chat
222
+ end
223
+
224
+ def extend_with_member_info(chat)
225
+ user_id = chat[:sender][:id]
226
+
227
+ result = query("SELECT * FROM chat_logs WHERE user_id = ? ORDER BY id ASC LIMIT 1", [user_id])
228
+
229
+ nickname = nil
230
+ if result && result.first
231
+ row = result.first
232
+ message = row["message"]
233
+
234
+ if message
235
+ begin
236
+ feed = JSON.parse(message)
237
+ if feed && feed["feedType"] == 4
238
+ member = feed["members"]&.first
239
+ nickname = member["nickName"] if member
240
+ end
241
+ rescue JSON::ParserError
242
+ end
243
+ end
244
+ end
245
+
246
+ if nickname
247
+ chat[:sender][:name] = nickname
248
+ end
249
+
250
+ chat
251
+ end
252
+
253
+ public
254
+
255
+ def image_base64(image_path)
256
+ begin
257
+ if File.exist?(image_path)
258
+ image_data = File.binread(image_path)
259
+ Base64.strict_encode64(image_data)
260
+ else
261
+ nil
262
+ end
263
+ rescue => e
264
+ nil
265
+ end
266
+ end
267
+
268
+ private
269
+
270
+ def decrypt(enc, data, user_id)
271
+ endpoint = URI.join(@http_url, "/decrypt")
272
+ payload = { enc: enc, b64_ciphertext: data, user_id: user_id }.to_json
273
+ headers = { "Content-Type" => "application/json" }
274
+
275
+ res = post_request(endpoint, payload, headers)
276
+ if res.is_a?(Net::HTTPSuccess)
277
+ body = JSON.parse(res.body)
278
+ body["plain_text"] || data
279
+ else
280
+ log_error("Failed to decrypt: #{res.body}")
281
+ data
282
+ end
283
+ rescue => e
284
+ log_error("Decrypt error: #{e}")
285
+ data
286
+ end
287
+
288
+ public
289
+ end
290
+ end
291
+
292
+ def reply(message)
293
+ $client&.reply(message)
294
+ end
295
+
296
+ def send_image(room_id, file_path)
297
+ $client.send_image(room_id, file_path)
298
+ end
299
+
300
+ def msg
301
+ $msg
302
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IrisRb
4
+ VERSION = "0.0.1"
5
+ end
data/lib/iris_rb.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative "iris_rb/version"
2
+ require_relative "iris_rb/client"
3
+
4
+ module IrisRb
5
+ class Error < StandardError; end
6
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iris_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - qweruby
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A Ruby package that supports the Iris framework
13
+ email:
14
+ - qweruby8@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE
20
+ - README.md
21
+ - lib/iris_rb.rb
22
+ - lib/iris_rb/client.rb
23
+ - lib/iris_rb/version.rb
24
+ homepage: https://github.com/Siruu580/IrisRb
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 3.4.0
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubygems_version: 3.6.9
43
+ specification_version: 4
44
+ summary: Ruby port of Iris
45
+ test_files: []