From 8f79a79b4e10a5e47520beabc01b63249806e772 Mon Sep 17 00:00:00 2001 From: Marrub Date: Sun, 24 Feb 2019 01:43:50 -0500 Subject: [PATCH] fix conflation of server and bot --- .yardopts | 2 +- source/backends/discord.rb | 187 +++++++++++++++++++++++++++++++++++++ source/backends/irc.rb | 143 ++++++++++++++++++++++++++++ source/main.rb | 29 +++--- source/module.rb | 6 +- source/modules/base.rb | 2 +- source/modules/relay.rb | 2 +- source/robots.rb | 37 ++++++++ source/server.rb | 25 +---- source/servers/discord.rb | 147 ----------------------------- source/servers/irc.rb | 124 ------------------------ 11 files changed, 388 insertions(+), 316 deletions(-) create mode 100644 source/backends/discord.rb create mode 100644 source/backends/irc.rb create mode 100644 source/robots.rb delete mode 100644 source/servers/discord.rb delete mode 100644 source/servers/irc.rb diff --git a/.yardopts b/.yardopts index 7fbee05..42f0c05 100644 --- a/.yardopts +++ b/.yardopts @@ -1 +1 @@ ---protected --private --hide-void-return source/*.rb source/servers/*.rb +--protected --private --hide-void-return source/*.rb source/backends/*.rb diff --git a/source/backends/discord.rb b/source/backends/discord.rb new file mode 100644 index 0000000..3fa02ae --- /dev/null +++ b/source/backends/discord.rb @@ -0,0 +1,187 @@ +# Discord backend. +module Backend_Discord + require 'discordrb' + + # Helper for channel permissions handling. + class ChannelPerms + def initialize() @cprm = {} end + + # @param chan [Bot_Discord::Channel] the channel for this permission + # @return [Boolean] true if the channel is enabled, false otherwise. + def [](chan) @cprm[chan.name] or @cprm[chan.real.id] end + + # Sets a channel's permission on/off. + # @param chan [Bot_Discord::Channel] the channel for this permission + # @param set [Boolean] if this permission should be set + # @return [void] + def []=(chan, set) @cprm[chan] = set end + end + + # A Discord user. + class User < Vrobot4::Server::User + attr_reader :real # The Discordrb::User instance. + + # @param user [Discordrb::User] the discord user + # @param ops [Array] list of operator role IDs + # @param hop [Array] list of half-operator role IDs + def initialize user, ops, hop + @real = user + @name = user.name + @id = user.id + @roles = "v" + if user.is_a? Discordrb::Member + if user.owner? + @roles += "Ooh" + elsif ops and ops.any? do |role| user.role? role end + @roles += "oh" + elsif hop and hop.any? do |role| user.role? role end + @roles += "h" + end + end + end + end + + # A Discord channel. + class Channel < Vrobot4::Server::Channel + attr_reader :real # The Discordrb::Channel instance. + + # @param chan [Discordrb::Channel] the discord channel + def initialize chan + @real = chan + @name = ?# + chan.name + @id = chan.id + end + end + + # A Discord server. + class Server < Vrobot4::Server::AudioServer + # The server type name. + def self.type() "Discord" end + + # (see Vrobot4::Server::Server#initialize) + def initialize info, bot, id + super info, bot + + @id = id + @ops = info["admins"] || [] + @hop = info["halfop"] || [] + + if mods = info["modules"] + for mod in mods + load_mod mod + end + end + end + + # (see Vrobot4::Server::AudioServer#voice_quit) + def voice_quit m + @bot.real.voice_destroy m.chan.real.server.id + end + + # (see Vrobot4::Server::AudioServer#voice_play) + def voice_play m, fname + vc = @bot.real.voice m.chan.real + raise RuntimeError, "I'm not in a voice channel" unless vc + vc.play_file fname + end + + # (see Vrobot4::Server::AudioServer#voice_play_io) + def voice_play_io m, io + vc = @bot.real.voice m.chan.real + raise ArgumentError, "Invalid IO stream" unless io + raise RuntimeError, "I'm not in a voice channel" unless vc + Thread.new do + vc.play_io io + end + + # HACK + sleep 1 + oldst = nil + while vc.stream_time != oldst + oldst = vc.stream_time + sleep 5 + end + end + + # (see Vrobot4::Server::Server#flags) + def flags + ?A + end + + protected + + # (see Vrobot4::Server::Server#load_permissions) + def load_permissions pinf + @mprm = {chan: {}, role: {}, glob: {}} + return unless pinf + pinf.each do |pr| + mod = Vrobot4::Module.get_module_type(pr["module"])[:type] + if pr.key? "channel" + @mprm[:chan][mod] = ChannelPerms.new unless @mprm[:chan][mod] + @mprm[:chan][mod][pr["channel"]] = true + elsif pr.key? "roles" + @mprm[:role][mod] = pr["roles"] + elsif pr.key? "enable" + @mprm[:glob][mod] = pr["enable"] + end + end + end + end + + # A bot implementation for Discord using discordrb. + class Bot < Vrobot4::Robots::Bot + # The bot type name. + def self.type() "Discord" end + Vrobot4::Robots.add_bot_type self + + attr_reader :real + + def initialize info + super + @servers = {} + + @real = Discordrb::Bot.new \ + token: info["apikey"], + client_id: info["client"] + + @real.message do |evt| + if evt.server + serv = server_by_id evt.server.id + m = Vrobot4::Server::Message.new \ + msg: evt.message.content, + user: User.new(evt.user, @ops, @hop), + chan: Channel.new(evt.channel), + serv: serv, + reply: -> (text) {evt.respond text}, + reply_b: -> (text) {evt.respond "```\n#{text}```"} + + serv.handle_text_cmd m + end + end + end + + # (see Vrobot4::Robots::Bot#connect) + def connect + @real.run + end + + protected + + # Adds a server to the hash. + # @param id [Integer] id of the server + # @return [Discordrb::Server] + def add_server id + Vrobot4.log :DEBUG, "initializing server #{id}" + @servers[id] = Server.new @info[id] || {}, self, id + end + + # Finds a server in the hash, potentially adding it. + # @param id [Integer] id of the server + # @return [Discordrb::Server] + def server_by_id id + @servers[id] || add_server(id) + end + end +end + +## EOF diff --git a/source/backends/irc.rb b/source/backends/irc.rb new file mode 100644 index 0000000..3551015 --- /dev/null +++ b/source/backends/irc.rb @@ -0,0 +1,143 @@ +# IRC backend. +module Backend_IRC + require 'cinch' + + # Helper for channel permissions. + class ChannelPerms + def initialize() @cprm = {} end + + # @param chan [Backend_IRC::Channel] the channel for this permission + # @return [Boolean] true if the channel is enabled, false otherwise. + def [](chan) @cprm[chan.name] end + + # Sets a channel's permission on/off. + # @param name [String] the channel for this permission + # @param set [Boolean] if this permission should be set + # @return [void] + def []=(name, set) @cprm[name] = set end + end + + # An IRC user. + class User < Vrobot4::Server::User + attr_reader :real # The Cinch::User instance. + + # @param user [Cinch::User] the irc user + # @param chan [Cinch::Channel] the irc channel this object was made in + def initialize user, chan + @real = user + @name = user.nick + @id = Vrobot4.hash_str user.nick.downcase + if user.oper? then @roles = "Oohv" + elsif chan.opped? user then @roles = "ohv" + elsif chan.half_opped? user then @roles = "hv" + elsif chan.voiced? user then @roles = "v" + else @roles = "" end + end + end + + # An IRC channel. + class Channel < Vrobot4::Server::Channel + attr_reader :real # The Cinch::Channel instance. + + # @param chan [Cinch::Channel] the irc channel + def initialize chan + @real = chan + @name = chan.name.downcase + @id = Vrobot4.hash_str chan.name.downcase + end + end + + # An IRC server. + class Server < Vrobot4::Server::Server + # The server type name. + def self.type() "IRC" end + + # (see Vrobot4::Server::Server#initialize) + def initialize info, bot + super + @id = Vrobot4.hash_str info["server"].downcase + + for mod in info["modules"] do + load_mod mod + end + end + + # (see Vrobot4::Server::Server#flags) + def flags + ?L + end + + protected + + # (see Vrobot4::Server::Server#load_permissions) + def load_permissions pinf + @mprm = {chan: {}, role: {}, glob: {}} + return unless pinf + pinf.each do |pr| + mod = Vrobot4::Module.get_module_type(pr["module"])[:type] + if pr.key? "channel" + @mprm[:chan][mod] = ChannelPerms.new unless @mprm[:chan][mod] + @mprm[:chan][mod][pr["channel"].downcase] = true + elsif pr.key? "roles" + @mprm[:role][mod] = pr["roles"] + elsif pr.key? "enable" + @mprm[:glob][mod] = pr["enable"] + end + end + end + end + + # A bot implementation for IRC using cinch. + class Bot < Vrobot4::Robots::Bot + # The bot type name. + def self.type() "IRC" end + Vrobot4::Robots.add_bot_type self + + attr_accessor :serv # @return [Vrobot4::Server::Server] the server + + # (see Vrobot4::Robots::Bot#initialize) + def initialize info + super + this = self + + @bot = Cinch::Bot.new do + configure do |cfg| + cfg.server = info["server"] + cfg.nick = info["nick"] || "vrobot4" + cfg.port = info["port"] if info.key? "port" + cfg.password = info["pass"] if info.key? "pass" + cfg.modes = info["modes"] if info.key? "modes" + cfg.channels = info["channels"] if info.key? "channels" + cfg.realname = "vrobot4" + cfg.user = "vrobot4" + cfg.message_split_start = "… " + cfg.message_split_end = " …" + end + + on :message do |evt| + return unless evt.channel + + m = Vrobot4::Server::Message.new \ + msg: evt.message, + user: User.new(evt.user, evt.channel), + chan: Channel.new(evt.channel), + serv: this.serv, + reply: -> (text) {evt.reply text}, + reply_b: -> (text) {evt.reply text} + + this.serv.handle_text_cmd m + end + end + + @bot.loggers.level = :warn + @serv = Server.new info, self + end + + # (see Vrobot4::Robots::Bot#connect) + def connect + @bot.start + end + end +end + +## EOF diff --git a/source/main.rb b/source/main.rb index c085b18..a02cacd 100644 --- a/source/main.rb +++ b/source/main.rb @@ -1,36 +1,31 @@ require './common.rb' require './module.rb' require './server.rb' +require './robots.rb' require 'yaml' module Vrobot4 private - # Loads a bot from its configuration +botinfo+. - # @param botinfo [Hash] arbitrary bot information + # Loads a bot from its configuration +info+. + # @param info [Hash] arbitrary bot information # ["type"] The type by name of the bot. - # @return [Vrobot4::Server] the loaded bot - def self.load_bot botinfo - type = botinfo["type"] - serv = Server::get_server_type(type).new botinfo - - for mod in botinfo["modules"] do - serv.load_mod mod - end - - serv + # @return [Vrobot4::Robots::Bot] the loaded bot + def self.load_bot info + type = info["type"] + Robots::get_bot_type(type).new info end # Runs all bots in +bots+ in separate threads. - # @param bots [Array] all bots to run + # @param bots [Array] all bots to run # @return [void] def self.run_bots bots threads = [] - bots.each do |server| + bots.each do |bot| threads << Thread.new do - server.connect + bot.connect end end @@ -65,8 +60,8 @@ module Vrobot4 begin bots = [] - cfg["bots"].each do |botinfo| - bots << load_bot(botinfo) + cfg["bots"].each do |info| + bots << load_bot(info) end log :DEBUGV, "bots: #{bots.to_s}" diff --git a/source/module.rb b/source/module.rb index 30e8c31..20c6100 100644 --- a/source/module.rb +++ b/source/module.rb @@ -134,11 +134,7 @@ module Vrobot4::Module check_arg argv[argn], argn, opt[i] end else - if opt.length - check_arg argv, 0, opt[0] unless argv.empty? - else - check_arg argv, 0, req[0] - end + check_arg argv, 0, req[0] end argv end diff --git a/source/modules/base.rb b/source/modules/base.rb index 95a00e5..a2dc72f 100644 --- a/source/modules/base.rb +++ b/source/modules/base.rb @@ -59,7 +59,7 @@ class Mod_Base < Vrobot4::Module::Module def c_dbg m, argv check_args argv, "N" - Vrobot4.debug = argv.to_i + Vrobot4.set_debug argv.to_i end def c_info m, argv diff --git a/source/modules/relay.rb b/source/modules/relay.rb index 34e58dd..7c9a854 100644 --- a/source/modules/relay.rb +++ b/source/modules/relay.rb @@ -1,6 +1,6 @@ class Mod_Relay < Vrobot4::Module::Module def self.type() "Relay" end - Vrobot4::Module.add_module_type self, server: "Discord" + Vrobot4::Module.add_module_type self def initialize info super diff --git a/source/robots.rb b/source/robots.rb new file mode 100644 index 0000000..c4dd34e --- /dev/null +++ b/source/robots.rb @@ -0,0 +1,37 @@ +# Module for vrobot4 bot interfaces. +module Vrobot4::Robots + # Generic bot interface. + class Bot + # @param info [Hash] arbitrary extra information for this bot + def initialize info + @info = info + end + + # Connect to all servers. + # @abstract + # @return [void] + def connect + raise NotImplementedError, "Bot#connect not implemented" + end + end + + # Adds a bot type to the global list. + # @param type [Class] bot type to add + # @return [void] + def self.add_bot_type type + @@bot_types[type.type] = type + Vrobot4.log :INFO, "added bot type: #{type.type}" + end + + # Gets a bot type by name from the global list. + # @param name [String] name of the bot type to find + # @return [Class] the bot type + def self.get_bot_type name + @@bot_types[name] + end + + # The set of all bot types. + @@bot_types = {} +end + +## EOF diff --git a/source/server.rb b/source/server.rb index 5b381ab..7154b6d 100644 --- a/source/server.rb +++ b/source/server.rb @@ -57,9 +57,12 @@ module Vrobot4::Server class Server attr_reader :mprm # @return [Hash] module permissions for this server attr_reader :id # @return [Integer] unique identifier for the server + attr_reader :bot # @return [Vrobot4::Robots::Bot] the bot we belong to # @param info [Hash] arbitrary extra information for this server - def initialize info + # @param bot [Vrobot4::Robots::Bot] the bot we belong to + def initialize info, bot + @bot = bot @info = info @modules = [Mod_Base.new(nil)] load_permissions info["permissions"] @@ -72,7 +75,7 @@ module Vrobot4::Server mt = Vrobot4::Module.get_module_type(mod) if mt[:server] and mt[:server] != self.class.type or mt[:servflags] and mt[:servflags] !~ flags then - raise ArgumentError, "Module " + mod + " not valid for this server" + raise ArgumentError, "Module #{mod} is not valid for this server" end @modules << mt[:type].new(@info[mod]) end @@ -188,24 +191,6 @@ module Vrobot4::Server raise NotImplementedError, "AudioServer#voice_play_io not implemented" end end - - # Adds a server type to the global list. - # @param type [Class] server type to add - # @return [void] - def self.add_server_type type - @@server_types[type.type] = type - Vrobot4.log :INFO, "added server type: #{type.type}" - end - - # Gets a server type by name from the global list. - # @param name [String] name of the server type to find - # @return [Vrobot4::Server::Server] the server type - def self.get_server_type name - @@server_types[name] - end - - # The set of all server types. - @@server_types = {} end ## EOF diff --git a/source/servers/discord.rb b/source/servers/discord.rb deleted file mode 100644 index 483d697..0000000 --- a/source/servers/discord.rb +++ /dev/null @@ -1,147 +0,0 @@ -require 'discordrb' - -# A server implementation for Discord using discordrb. -class Sv_Discord < Vrobot4::Server::AudioServer - # The server type name. - def self.type() "Discord" end - Vrobot4::Server.add_server_type self - - attr_reader :bot # The Discordrb::Bot instance. - - # (see Vrobot4::Server::Server#initialize) - def initialize info - super - - @id = info["client"] - - @ops = info["admins"] || [] - @hop = info["halfop"] || [] - - @bot = Discordrb::Bot.new \ - token: info["apikey"], - client_id: info["client"] - - @bot.message do |evt| - m = Vrobot4::Server::Message.new \ - msg: evt.message.content, - user: User.new(evt.user, @ops, @hop), - chan: Channel.new(evt.channel), - serv: self, - reply: -> (text) {evt.respond text}, - reply_b: -> (text) {evt.respond "```\n#{text}```"} - - handle_text_cmd m - end - end - - # (see Vrobot4::Server::AudioServer#voice_quit) - def voice_quit m - @bot.voice_destroy m.chan.real.server.id - end - - # (see Vrobot4::Server::AudioServer#voice_play) - def voice_play m, fname - vc = @bot.voice m.chan.real - raise RuntimeError, "I'm not in a voice channel" unless vc - vc.play_file fname - end - - # (see Vrobot4::Server::AudioServer#voice_play_io) - def voice_play_io m, io - vc = @bot.voice m.chan.real - raise ArgumentError, "Invalid IO stream" unless io - raise RuntimeError, "I'm not in a voice channel" unless vc - Thread.new do - vc.play_io io - end - # HACK - sleep 1 - oldst = nil - while vc.stream_time != oldst - oldst = vc.stream_time - sleep 5 - end - end - - # (see Vrobot4::Server::Server#connect) - def connect - @bot.run - end - - # (see Vrobot4::Server::Server#flags) - def flags - ?A - end - - protected - - # (see Vrobot4::Server::Server#load_permissions) - def load_permissions pinf - @mprm = {chan: {}, role: {}, glob: {}} - pinf.each do |pr| - mod = Vrobot4::Module.get_module_type(pr["module"])[:type] - if pr.key? "channel" - @mprm[:chan][mod] = ChannelPerms.new unless @mprm[:chan].key? mod - @mprm[:chan][mod][pr["channel"]] = true - elsif pr.key? "roles" - @mprm[:role][mod] = pr["roles"] - elsif pr.key? "enable" - @mprm[:glob][mod] = pr["enable"] - end - end if pinf - end - - # Helper for channel permissions handling. - class ChannelPerms - def initialize() @cprm = {} end - - # @param chan [Sv_Discord::Channel] the channel for this permission - # @return [Boolean] true if the channel is enabled, false otherwise. - def [](chan) @cprm[chan.name] or @cprm[chan.real.id] end - - # Sets a channel's permission on/off. - # @param chan [Sv_Discord::Channel] the channel for this permission - # @param set [Boolean] if this permission should be set - # @return [void] - def []=(chan, set) @cprm[chan] = set end - end - private_constant :ChannelPerms - - # A Discord user. - class User < Vrobot4::Server::User - attr_reader :real # The Discordrb::User instance. - - # @param user [Discordrb::User] the discord user - # @param ops [Array] list of operator role IDs - # @param hop [Array] list of half-operator role IDs - def initialize user, ops, hop - @real = user - @name = user.name - @id = user.id - @roles = "v" - if user.is_a? Discordrb::Member - if user.owner? - @roles += "Ooh" - elsif ops and ops.any? do |role| user.role? role end - @roles += "oh" - elsif hop and hop.any? do |role| user.role? role end - @roles += "h" - end - end - end - end - - # A Discord channel. - class Channel < Vrobot4::Server::Channel - attr_reader :real # The Discordrb::Channel instance. - - # @param chan [Discordrb::Channel] the discord channel - def initialize chan - @real = chan - @name = ?# + chan.name - @id = chan.id - end - end -end - -## EOF diff --git a/source/servers/irc.rb b/source/servers/irc.rb deleted file mode 100644 index e4716e4..0000000 --- a/source/servers/irc.rb +++ /dev/null @@ -1,124 +0,0 @@ -require 'cinch' - -# A server implementation for IRC using cinch. -class Sv_IRC < Vrobot4::Server::Server - # The server type name. - def self.type() "IRC" end - Vrobot4::Server.add_server_type self - - attr_reader :bot # The Cinch::Bot instance. - - # (see Vrobot4::Server::Server#initialize) - def initialize info - super - - this = self - @bot = Cinch::Bot.new do - configure do |cfg| - cfg.server = info["server"] - cfg.nick = info["nick"] || "vrobot4" - cfg.port = info["port"] if info.key? "port" - cfg.password = info["pass"] if info.key? "pass" - cfg.modes = info["modes"] if info.key? "modes" - cfg.channels = info["channels"] if info.key? "channels" - cfg.realname = "vrobot4" - cfg.user = "vrobot4" - cfg.message_split_start = "… " - cfg.message_split_end = " …" - end - - on :message do |evt| - return unless evt.channel - - m = Vrobot4::Server::Message.new \ - msg: evt.message, - user: User.new(evt.user, evt.channel), - chan: Channel.new(evt.channel), - serv: this, - reply: -> (text) {evt.reply text}, - reply_b: -> (text) {evt.reply text} - - this.handle_text_cmd m - end - end - - @bot.loggers.level = :warn - @id = info["server"].downcase.sum - end - - # (see Vrobot4::Server::Server#connect) - def connect - @bot.start - end - - # (see Vrobot4::Server::Server#flags) - def flags - ?L - end - - protected - - # (see Vrobot4::Server::Server#load_permissions) - def load_permissions pinf - @mprm = {chan: {}, role: {}, glob: {}} - pinf.each do |pr| - mod = Vrobot4::Module.get_module_type(pr["module"])[:type] - if pr.key? "channel" - @mprm[:chan][mod] = ChannelPerms.new unless @mprm[:chan].key? mod - @mprm[:chan][mod][pr["channel"].downcase] = true - elsif pr.key? "roles" - @mprm[:role][mod] = pr["roles"] - elsif pr.key? "enable" - @mprm[:glob][mod] = pr["enable"] - end - end if pinf - end - - # Helper for channel permissions. - class ChannelPerms - def initialize() @cprm = {} end - - # @param chan [Sv_IRC::Channel] the channel for this permission - # @return [Boolean] true if the channel is enabled, false otherwise. - def [](chan) @cprm[chan.name] end - - # Sets a channel's permission on/off. - # @param name [String] the channel for this permission - # @param set [Boolean] if this permission should be set - # @return [void] - def []=(name, set) @cprm[name] = set end - end - private_constant :ChannelPerms - - # An IRC user. - class User < Vrobot4::Server::User - attr_reader :real # The Cinch::User instance. - - # @param user [Cinch::User] the irc user - # @param chan [Cinch::Channel] the irc channel this object was made in - def initialize user, chan - @real = user - @name = user.nick - @id = user.nick.downcase.sum - if user.oper? then @roles = "Oohv" - elsif chan.opped? user then @roles = "ohv" - elsif chan.half_opped? user then @roles = "hv" - elsif chan.voiced? user then @roles = "v" - else @roles = "" end - end - end - - # An IRC channel. - class Channel < Vrobot4::Server::Channel - attr_reader :real # The Cinch::Channel instance. - - # @param chan [Cinch::Channel] the irc channel - def initialize chan - @real = chan - @name = chan.name.downcase - @id = chan.name.downcase.sum - end - end -end - -## EOF