1# frozen_string_literal: true
2
3require "json"
4require "time"
5
6module MessageEvent
7 module Emittable
8 def emit(redis, stream: "messages")
9 args = to_redis_fields.flat_map { |k, v| [k, v] }
10 redis.xadd(stream, "*", *args).catch do |e|
11 puts "WARN Failed to emit message event: #{e.message}"
12 end
13 end
14 end
15
16 def self.strip_prefix(tel)
17 tel.to_s.sub(/\Asms:/, "").strip
18 end
19
20 module ValidTel
21 def self.===(value)
22 match?(value)
23 end
24
25 def self.match?(value)
26 value.to_s.match?(/\A\+?\d+\Z/)
27 end
28 end
29
30 module NanpaTel
31 def self.===(value)
32 match?(value)
33 end
34
35 def self.match?(value)
36 value.to_s.match?(/\A\+1\d{10}\Z/)
37 end
38 end
39
40 class Base
41 include Emittable
42
43 @validators = [
44 lambda {
45 unless @event.is_a?(String)
46 raise ArgumentError, "event must be a String"
47 end
48 }
49 ]
50
51 def self.validators
52 parent = superclass.respond_to?(:validators) ? superclass.validators : []
53 parent + (@validators || [])
54 end
55
56 def initialize(event:, timestamp: nil)
57 timestamp = Time.iso8601(timestamp) if timestamp.is_a?(String)
58 @event = event
59 @timestamp = timestamp
60 self.class.validators.each { |validator| instance_exec(&validator) }
61 end
62
63 attr_reader :event, :timestamp
64
65 def to_redis_fields
66 fields = {
67 "event" => @event,
68 "source" => "endstream"
69 }
70 if @timestamp
71 fields["timestamp"] =
72 @timestamp.is_a?(Time) ? @timestamp.iso8601 : @timestamp
73 end
74 fields
75 end
76 end
77
78 class Message < Base
79 @validators = [
80 lambda {
81 unless ValidTel.match?(@owner)
82 raise(
83 ArgumentError,
84 "owner must be a number, optionally prefixed with +"
85 )
86 end
87 },
88 lambda {
89 raise ArgumentError, "to must be an array" unless @to.is_a?(Array)
90 },
91 lambda {
92 unless @to.all? { |t| ValidTel.match?(t) }
93 raise(
94 ArgumentError,
95 "to must be a number, optionally prefixed with +"
96 )
97 end
98 },
99 lambda {
100 unless @body.nil? || @body.is_a?(String)
101 raise ArgumentError, "body must be a String or nil"
102 end
103 },
104 lambda {
105 unless @media_urls.is_a?(Array)
106 raise ArgumentError, "media_urls must be an Array"
107 end
108 }
109 ]
110
111 def initialize(to:, from:, body:, owner:, media_urls: [], **kwargs)
112 owner = MessageEvent.strip_prefix(owner)
113 from = MessageEvent.strip_prefix(from)
114 to = to.map { |t| MessageEvent.strip_prefix(t) }
115
116 @from = from
117 @to = to
118 @body = body
119 @media_urls = media_urls
120 @owner = owner
121 super(**kwargs)
122 end
123
124 attr_reader :from, :to, :body, :media_urls
125
126 def to_redis_fields
127 fields = super.merge(
128 "owner" => @owner,
129 "from" => @from,
130 "to" => JSON.dump(@to)
131 )
132 fields["body"] = @body unless @body.nil?
133 fields["media_urls"] = JSON.dump(@media_urls)
134 fields
135 end
136 end
137
138 class In < Message
139 @validators = [
140 lambda {
141 unless @endstream_id.is_a?(String)
142 raise ArgumentError, "endstream_id must be a String"
143 end
144 }
145 ]
146
147 def initialize(endstream_id:, **kwargs)
148 @endstream_id = endstream_id
149 super(event: "in", **kwargs)
150 end
151
152 attr_reader :owner, :endstream_id
153
154 def to_redis_fields
155 super.merge("endstream_id" => @endstream_id)
156 end
157 end
158
159 class Failed < Base
160 @validators = [
161 lambda {
162 unless @endstream_id.is_a?(String)
163 raise ArgumentError, "endstream_id must be a String"
164 end
165 },
166 lambda {
167 unless @error_code.is_a?(String)
168 raise ArgumentError, "error_code must be a String"
169 end
170 },
171 lambda {
172 unless @error_description.is_a?(String)
173 raise ArgumentError, "error_description must be a String"
174 end
175 }
176 ]
177
178 def initialize(endstream_id:, error_code:, error_description:, **kwargs)
179 @endstream_id = endstream_id
180 @error_code = error_code
181 @error_description = error_description
182 super(event: "failed", **kwargs)
183 end
184
185 attr_reader :endstream_id, :error_code, :error_description
186
187 def to_redis_fields
188 super.merge(
189 "endstream_id" => @endstream_id,
190 "error_code" => @error_code,
191 "error_description" => @error_description
192 )
193 end
194 end
195
196 class Out < Message
197 @validators = [
198 lambda {
199 unless @stanza_id.is_a?(String)
200 raise ArgumentError, "stanza_id must be a String"
201 end
202 }
203 ]
204
205 def initialize(stanza_id:, **kwargs)
206 @stanza_id = stanza_id
207 super(event: "out", **kwargs)
208 end
209
210 attr_reader :stanza_id
211
212 def to_redis_fields
213 super.merge("stanza_id" => @stanza_id)
214 end
215 end
216end