class Sms
Constants
- IRC_RESET
- MAX_MESSAGE_LENGTH
- STYLE_TITLE
- STYLE_WIN
- TEXTBELT_FREE_KEY
- TEXTBELT_URL
Public Class Methods
Source
# File plugins/sms.rb, line 84 def self.api_post(url, payload, post: nil) body = if post post.call(url, payload) else uri = URI.parse(url) Net::HTTP.post_form(uri, payload).body end JSON.parse(body) rescue JSON::ParserError { "success" => false, "error" => "Invalid API response" } end
Source
# File plugins/sms.rb, line 27 def self.command_pattern /sms(?:\s+(.+))?$/i end
Source
# File plugins/sms.rb, line 150 def self.dispatch(args, post: nil) if args.to_s.strip.empty? return { reply: format_usage_reply } end parsed = parse_command(args) return { reply: format_error_reply(parsed[:error]) } if parsed[:error] result = send_message(phone: parsed[:phone], message: parsed[:message], post: post) if result[:success] return { reply: format_success_reply( phone: parsed[:phone], quota_remaining: result[:quota_remaining], text_id: result[:text_id] ) } end { reply: format_error_reply(result[:error]) } end
Source
# File plugins/sms.rb, line 132 def self.format_channel_required_reply format_error_reply("Use !sms in a channel.") end
Source
# File plugins/sms.rb, line 128 def self.format_denial_reply format_error_reply("Voice (+v) or op required to send SMS.") end
Source
# File plugins/sms.rb, line 121 def self.format_error_reply(message) [ IrcFormat.tag("SMS"), message.to_s ].join("\n") end
Source
# File plugins/sms.rb, line 136 def self.format_success_reply(phone:, quota_remaining: nil, text_id: nil) meta = [] meta << "quota #{quota_remaining}" unless quota_remaining.nil? meta << "id #{text_id}" unless text_id.nil? footer = meta.empty? ? "Textbelt" : meta.join(" ยท ") [ IrcFormat.tag("SMS"), IrcFormat.menu_heading(phone.to_s, style: STYLE_WIN), "Message sent to carrier.", IrcFormat.report_footer(footer, "delivery not guaranteed") ].join("\n") end
Source
# File plugins/sms.rb, line 113 def self.format_usage_reply [ IrcFormat.report_header(tag: "SMS", title: "send a text", style: STYLE_TITLE), usage_message, IrcFormat.report_footer("Textbelt free tier ยท AU E.164") ].join("\n") end
Source
# File plugins/sms.rb, line 37 def self.normalize_phone(raw) text = raw.to_s.strip return nil if text.empty? digits = text.gsub(/\D/, "") return nil if digits.empty? if text.start_with?("+") || digits.start_with?("61") local = digits.sub(/\A61/, "") return "+61#{local}" if local.match?(/\A[2-578]\d{8}\z/) end if digits.match?(/\A0[2-578]\d{8}\z/) return "+61#{digits[1..]}" end if digits.match?(/\A[2-578]\d{8}\z/) return "+61#{digits}" end nil end
Source
# File plugins/sms.rb, line 96 def self.parse_api_response(data) success = data["success"] == true return { success: true, quota_remaining: data["quotaRemaining"], text_id: data["textId"] } if success error = data["error"] || data["message"] || "SMS send failed" { success: false, error: error.to_s } end
Source
# File plugins/sms.rb, line 64 def self.parse_command(text) stripped = text.to_s.strip return { error: usage_message } if stripped.empty? phone_raw, message = stripped.split(/\s+/, 2) return { error: usage_message } if phone_raw.nil? || phone_raw.empty? message = message.to_s.strip return { error: "Message cannot be empty." } if message.empty? phone = normalize_phone(phone_raw) return { error: "Invalid Australian phone number." } unless phone && valid_au_phone?(phone) if message.length > MAX_MESSAGE_LENGTH return { error: "Message too long (#{MAX_MESSAGE_LENGTH} chars max)." } end { phone: phone, message: message } end
Source
# File plugins/sms.rb, line 104 def self.send_message(phone:, message:, key: TEXTBELT_FREE_KEY, post: nil) payload = { phone: phone, message: message, key: key } parse_api_response(api_post(TEXTBELT_URL, payload, post: post)) end
Source
# File plugins/sms.rb, line 33 def self.usage_message "Usage: !sms <number> <message> โ e.g. !sms 0477399268 hello" end
Source
# File plugins/sms.rb, line 60 def self.valid_au_phone?(phone) phone.to_s.match?(/\A\+61[2-578]\d{8}\z/) end
Public Instance Methods
Source
# File plugins/sms.rb, line 172 def execute(m, args = nil) unless m.channel themed_flood_safe_reply(m, self.class.format_channel_required_reply) return end unless ChannelTrust.trusted_nick?( nick: m.user.nick, channel: m.channel, user: m.user, network: bot.config.server ) themed_flood_safe_reply(m, self.class.format_denial_reply) return end result = self.class.dispatch(args) themed_flood_safe_reply(m, result[:reply]) end