message_event.rb

  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