class Asterisk
Constants
- ASTERISK_BIN
- DEFAULT_CONFIG
- ENDPOINT_LINE
- IRC_CTRL
- IRC_RESET
- MAX_DIALPLAN_LINES
- OBJECTS_FOUND
- PLUGIN
- RELOAD_COMMANDS
- RELOAD_TARGETS
- SECRET_PATTERN
- STYLE_TITLE
- TRANSPORT_LINE
Public Class Methods
Source
# File plugins/asterisk.rb, line 306 def self.admin_allowed?(network:, channel:, nick:, is_op:, store: RabbotDb) store.user_allowed_at_level?( network: network, channel: channel, nick: nick, is_op: is_op, minimum: RabbotDb::LEVEL_ADMIN ) end
Source
# File plugins/asterisk.rb, line 57 def self.allowed_config_key?(key) key.to_s.match?(/\A[a-z][a-z0-9_]{0,40}\z/) end
Source
# File plugins/asterisk.rb, line 291 def self.build_argv(command) [ASTERISK_BIN, "-rx", command] end
Source
# File plugins/asterisk.rb, line 43 def self.command_pattern /asterisk(?: (.+))?$/i end
Source
# File plugins/asterisk.rb, line 253 def self.config_value(network:, channel:, key:, store: RabbotDb) default = DEFAULT_CONFIG.fetch(key, "") raw = store.get_plugin_value(plugin: PLUGIN, key: key, network: network, channel: channel) raw.nil? ? default : raw.to_s end
Source
# File plugins/asterisk.rb, line 285 def self.default_run lambda do |argv| Open3.capture3("sudo", *argv) end end
Source
# File plugins/asterisk.rb, line 53 def self.denied_message "Only channel ops and registered admins can manage Asterisk." end
Source
# File plugins/asterisk.rb, line 362 def self.dialplan_report(context:, run: default_run) command = context.nil? ? "dialplan show" : "dialplan show #{context}" result = run_cli(command, run: run) return { error: result[:error] } unless result[:success] lines = parse_dialplan_lines(result[:stdout], context: context) { text: format_dialplan_report(context: context, lines: lines) } end
Source
# File plugins/asterisk.rb, line 383 def self.dispatch_command(args, network:, channel:, nick:, is_op: false, run: default_run, store: RabbotDb) parsed = parse_command(args) return parsed if parsed[:error] case parsed[:action] when :status status_report(run: run) when :endpoints endpoints_report(run: run) when :transports transports_report(run: run) when :dialplan dialplan_report(context: parsed[:context], run: run) when :reload unless admin_allowed?(network: network, channel: channel, nick: nick, is_op: is_op, store: store) return { error: denied_message } end reload_report(target: parsed[:target], run: run) when :config_list { text: format_config_report(format_config_list(network: network, channel: channel, store: store)) } when :config_get value = config_value(network: network, channel: channel, key: parsed[:key], store: store) { text: format_config_report("#{parsed[:key]}=#{value}") } when :config_set unless admin_allowed?(network: network, channel: channel, nick: nick, is_op: is_op, store: store) return { error: denied_message } end set_config!(network: network, channel: channel, key: parsed[:key], value: parsed[:value], store: store) { text: format_config_report("#{parsed[:key]}=#{parsed[:value]}") } when :config_unset unless admin_allowed?(network: network, channel: channel, nick: nick, is_op: is_op, store: store) return { error: denied_message } end unset_config!(network: network, channel: channel, key: parsed[:key], store: store) value = config_value(network: network, channel: channel, key: parsed[:key], store: store) { text: format_config_report("#{parsed[:key]}=#{value}") } else { error: usage_message } end end
Source
# File plugins/asterisk.rb, line 336 def self.endpoints_report(run: default_run) result = run_cli("pjsip show endpoints", run: run) return { error: result[:error] } unless result[:success] endpoints = parse_endpoints(result[:stdout]) total = parse_objects_found(result[:stdout]) total = endpoints.length unless total.positive? { text: format_endpoints_report(endpoints: endpoints, total: total) } end
Source
# File plugins/asterisk.rb, line 279 def self.format_config_list(network:, channel:, store: RabbotDb) DEFAULT_CONFIG.keys.map do |key| "#{key}=#{config_value(network: network, channel: channel, key: key, store: store)}" end.join(", ") end
Source
# File plugins/asterisk.rb, line 246 def self.format_config_report(text) header = IrcFormat.report_header(tag: "ASTERISK", title: "config", style: STYLE_TITLE) footer = IrcFormat.report_footer("plugin settings") [header, text, footer].join("\n") end
Source
# File plugins/asterisk.rb, line 228 def self.format_dialplan_report(context:, lines:) label = context.nil? ? "dialplan" : "dialplan #{context}" header = IrcFormat.report_header(tag: "ASTERISK", title: label, style: STYLE_TITLE) body = lines.empty? ? ["No dialplan lines found."] : lines footer = IrcFormat.report_footer("#{body.length} lines") ([header] + body + [footer]).join("\n") end
Source
# File plugins/asterisk.rb, line 206 def self.format_endpoints_report(endpoints:, total:) title = "#{total} endpoints" header = IrcFormat.report_header(tag: "ASTERISK", title: title, style: STYLE_TITLE) body = endpoints.map do |entry| "#{entry[:name]} โ #{entry[:state]} โ #{entry[:channels]} ch" end footer = IrcFormat.report_footer("PJSIP endpoints") ([header] + body + [footer]).join("\n") end
Source
# File plugins/asterisk.rb, line 237 def self.format_reload_report(results:) header = IrcFormat.report_header(tag: "ASTERISK", title: "reload", style: STYLE_TITLE) body = results.map(&:strip).reject(&:empty?) body = ["Reload completed."] if body.empty? footer = IrcFormat.report_footer("Asterisk PBX") ([header] + body + [footer]).join("\n") end
Source
# File plugins/asterisk.rb, line 197 def self.format_status_report(version:, uptime:, channels:, calls:) title = "#{version} ยท #{channels} channels ยท #{calls} calls" header = IrcFormat.report_header(tag: "ASTERISK", title: title, style: STYLE_TITLE) body = "Uptime: #{uptime}" footer = IrcFormat.report_footer("Asterisk PBX", "status") [header, body, footer].join("\n") end
Source
# File plugins/asterisk.rb, line 217 def self.format_transports_report(transports:, total:) title = "#{total} transports" header = IrcFormat.report_header(tag: "ASTERISK", title: title, style: STYLE_TITLE) body = transports.map do |entry| "#{entry[:id]} โ #{entry[:type]} โ #{entry[:bind]}" end footer = IrcFormat.report_footer("PJSIP transports") ([header] + body + [footer]).join("\n") end
Source
# File plugins/asterisk.rb, line 123 def self.parse_active_calls(text) match = text.to_s.match(/(\d+)\s+active calls/) match ? match[1].to_i : 0 end
Source
# File plugins/asterisk.rb, line 118 def self.parse_channels_count(text) match = text.to_s.match(/(\d+)\s+active channels/) match ? match[1].to_i : 0 end
Source
# File plugins/asterisk.rb, line 61 def self.parse_command(args) text = args.to_s.strip return { action: :status } if text.empty? return { action: :status } if text.casecmp?("status") return { action: :endpoints } if text.casecmp?("endpoints") || text.casecmp?("peers") return { action: :transports } if text.casecmp?("transports") if (match = text.match(/\Areload(?:\s+(\S+))?\z/i)) target = (match[1] || "all").downcase return { error: usage_message } unless RELOAD_TARGETS.include?(target) return { action: :reload, target: target } end if (match = text.match(/\Aconfig\s+list\z/i)) return { action: :config_list } end if (match = text.match(/\Aconfig\s+get\s+(\S+)\z/i)) key = match[1] return { error: usage_message } unless allowed_config_key?(key) return { action: :config_get, key: key } end if (match = text.match(/\Aconfig\s+set\s+(\S+)\s+(.+)\z/i)) key = match[1] value = match[2].strip return { error: usage_message } unless allowed_config_key?(key) && !value.empty? return { action: :config_set, key: key, value: value } end if (match = text.match(/\Aconfig\s+unset\s+(\S+)\z/i)) key = match[1] return { error: usage_message } unless allowed_config_key?(key) return { action: :config_unset, key: key } end if (match = text.match(/\Adialplan(?:\s+(\S+))?\z/i)) return { action: :dialplan, context: match[1] } end { error: usage_message } end
Source
# File plugins/asterisk.rb, line 165 def self.parse_dialplan_lines(text, context:) lines = [] in_context = context.nil? text.to_s.each_line do |raw_line| line = raw_line.chomp if (match = line.match(/\A\[ Context '([^']+)'/)) in_context = context.nil? || match[1] == context lines << sanitize_dialplan_line(line) if in_context next end next unless in_context next if line.strip.empty? lines << sanitize_dialplan_line(line) break if lines.length >= MAX_DIALPLAN_LINES end lines end
Source
# File plugins/asterisk.rb, line 128 def self.parse_endpoints(text) text.to_s.each_line.filter_map do |line| next if line.include?("<") match = line.match(ENDPOINT_LINE) next unless match { name: match[1].strip, state: match[2].strip, channels: match[3].to_i } end end
Source
# File plugins/asterisk.rb, line 156 def self.parse_objects_found(text) match = text.to_s.match(OBJECTS_FOUND) match ? match[1].to_i : 0 end
Source
# File plugins/asterisk.rb, line 143 def self.parse_transports(text) text.to_s.each_line.filter_map do |line| match = line.match(TRANSPORT_LINE) next unless match { id: match[1], type: match[2], bind: match[3] } end end
Source
# File plugins/asterisk.rb, line 113 def self.parse_uptime(text) match = text.to_s.match(/System uptime:\s+(.+)/) match ? match[1].strip : "unknown" end
Source
# File plugins/asterisk.rb, line 108 def self.parse_version(text) match = text.to_s.match(/Asterisk\s+(\d+\.\d+\.\d+)/) match ? match[1] : "unknown" end
Source
# File plugins/asterisk.rb, line 187 def self.reload_command_for(target) RELOAD_COMMANDS.fetch(target) end
Source
# File plugins/asterisk.rb, line 191 def self.reload_commands_for(target) return RELOAD_COMMANDS.values if target == "all" [reload_command_for(target)] end
Source
# File plugins/asterisk.rb, line 371 def self.reload_report(target:, run: default_run) outputs = [] reload_commands_for(target).each do |command| result = run_cli(command, run: run) return { error: result[:error] } unless result[:success] outputs << result[:stdout] end { text: format_reload_report(results: outputs) } end
Source
# File plugins/asterisk.rb, line 295 def self.run_cli(command, run: default_run) stdout, stderr, status = run.call(build_argv(command)) if status.success? { success: true, stdout: stdout.to_s, stderr: stderr.to_s } else error = [stderr, stdout].join("\n").strip error = "Asterisk command failed." if error.empty? { success: false, error: error } end end
Source
# File plugins/asterisk.rb, line 161 def self.sanitize_dialplan_line(line) line.to_s.gsub(SECRET_PATTERN) { "#{Regexp.last_match(1)}:***@" } end
Source
# File plugins/asterisk.rb, line 259 def self.set_config!(network:, channel:, key:, value:, store: RabbotDb) store.set_plugin_value( plugin: PLUGIN, key: key, value: value.to_s, network: network, channel: channel ) end
Source
# File plugins/asterisk.rb, line 316 def self.status_report(run: default_run) version_result = run_cli("core show version", run: run) return { error: version_result[:error] } unless version_result[:success] uptime_result = run_cli("core show uptime", run: run) return { error: uptime_result[:error] } unless uptime_result[:success] channels_result = run_cli("core show channels count", run: run) return { error: channels_result[:error] } unless channels_result[:success] { text: format_status_report( version: parse_version(version_result[:stdout]), uptime: parse_uptime(uptime_result[:stdout]), channels: parse_channels_count(channels_result[:stdout]), calls: parse_active_calls(channels_result[:stdout]) ) } end
Source
# File plugins/asterisk.rb, line 349 def self.transports_report(run: default_run) result = run_cli("pjsip show transports", run: run) return { error: result[:error] } unless result[:success] transports = parse_transports(result[:stdout]) total = parse_objects_found(result[:stdout]) total = transports.length unless total.positive? { text: format_transports_report(transports: transports, total: total) } end
Source
# File plugins/asterisk.rb, line 269 def self.unset_config!(network:, channel:, key:, store: RabbotDb) store.set_plugin_value( plugin: PLUGIN, key: key, value: DEFAULT_CONFIG.fetch(key, ""), network: network, channel: channel ) end
Source
# File plugins/asterisk.rb, line 49 def self.usage_message "Usage: !asterisk [status|endpoints|transports|dialplan [context]|reload <dialplan|pjsip|manager|core|all>|config <list|get|set|unset> ...]" end
Public Instance Methods
Source
# File plugins/asterisk.rb, line 427 def execute(m, args_text = nil) return unless BotIdentity.owner_bot?(bot) result = self.class.dispatch_command( args_text, network: bot.config.server, channel: m.channel.name, nick: m.user.nick, is_op: m.user.channel_admin? ) themed_flood_safe_reply(m, result[:error] || result[:text]) end