documentation and version bump

master
an 2019-02-23 23:46:40 -05:00
parent 10b23a9d8c
commit 34aeec60dc
7 changed files with 250 additions and 140 deletions

View File

@ -1 +1 @@
--protected source/*.rb source/servers/*.rb --protected --private --hide-void-return source/*.rb source/servers/*.rb

View File

@ -1,23 +1,30 @@
require 'zlib'
# Module for the main vrobot4 program and global info. # Module for the main vrobot4 program and global info.
module Vrobot4 module Vrobot4
# The current program version. # The current program version.
Version = "4.00".freeze Version = "4.01".freeze
def self.debug=(set) @@debug = set end # Sets the current debug level. # Sets the current debug level.
def self.debug ( ) @@debug end # Gets the current debug level. # @param set [Integer] the debug level
# @return [void]
def self.set_debug set
@@debug = set
end
# Logs to the console. # Logs to the console.
# #
# @param lv [Symbol] # @param lv [Symbol] the log level
# - If +:DEBUG+, this message will not be printed if the global debug # [+:INFO+] This message will always be printed.
# level is less than 1. # [+:DEBUG+] This message will not be printed if the global debug level is
# - If +:DEBUGV+, this message will not be printed if the global debug # less than 1.
# level is less than 2. # [+:DEBUGV+] This message will not be printed if the global debug level is
# less than 2.
# @return [Boolean] true if the message was printed, false otherwise # @return [Boolean] true if the message was printed, false otherwise
def self.log lv, *text def self.log lv, text
if (lv != :DEBUG || @@debug >= 1) && if (lv != :DEBUG || @@debug >= 1) &&
(lv != :DEBUGV || @@debug >= 2) (lv != :DEBUGV || @@debug >= 2)
puts "[" + lv.to_s.ljust(6) + "] " + text.join(?\s) puts "[#{lv.to_s.ljust 6}] #{text}"
true true
else else
false false
@ -25,13 +32,18 @@ module Vrobot4
end end
# Checks if the argument +s+ is a numeric string. # Checks if the argument +s+ is a numeric string.
# @param str [String] the string to check # @param s [String] the string to check
def self.is_num? str # @return [Boolean] true if +s+ is a numeric string, false otherwise
/\A[-+]?[0-9]*\.?[0-9]+\Z/ === str def self.is_num? s
/\A[-+]?[0-9]*\.?[0-9]+\Z/ === s
end end
private # Does a stable hash on +s+.
@@debug = 0 # @param s [String] the string to hash
# @return [Integer]
def self.hash_str s
Zlib.crc32 s
end
end end
## EOF ## EOF

View File

@ -6,54 +6,80 @@ require 'yaml'
module Vrobot4 module Vrobot4
private private
def self.loadBot botinfo
type = botinfo["type"]
serv = Server::get_server_type(type).new(botinfo)
if botinfo.key? "modules" then for mod in botinfo["modules"] do # Loads a bot from its configuration +botinfo+.
# @param botinfo [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 serv.load_mod mod
end end end
serv serv
end end
def self.runBots bots # Runs all bots in +bots+ in separate threads.
thrds = [] # @param bots [Array<Vrobot4::Server>] all bots to run
bots.each {|serv| thrds << Thread.new {serv.connect}} # @return [void]
thrds.each {|th| th.join} def self.run_bots bots
threads = []
bots.each do |server|
threads << Thread.new do
server.connect
end
end
threads.each do |thread|
thread.join
end
end end
public public
# Runs the program. # Runs the program.
# @param cfg [IO] configuration file to load # @param cfg [IO] configuration file to read
# @return [void]
def self.main cfg def self.main cfg
log :INFO, "vrobot version", Version Thread.abort_on_exception = true
log :INFO, "vrobot version #{Version}"
begin begin
cfg = YAML.load cfg.read cfg = YAML.load cfg.read
rescue rescue
log :ERROR, "error reading bot config:", $! log :ERROR, "error reading bot config: #{$!}"
return return
end end
Vrobot4.debug = cfg["debug"] if cfg.key? "debug" Vrobot4.set_debug(cfg["debug"] || 0)
cfg["loadmods"].each {|mod| load mod, true}
cfg["load"].each do |mod|
load mod, true
end
begin begin
bots = [] bots = []
cfg["bots"].each {|botinfo| bots << loadBot(botinfo)}
log :DEBUGV, "bots:", bots.to_s cfg["bots"].each do |botinfo|
bots << load_bot(botinfo)
end
log :DEBUGV, "bots: #{bots.to_s}"
rescue rescue
log :ERROR, "error loading bot config:", err = $!.to_s + ?\n + $!.backtrace.first(3).join(?\n)
$!.to_s + ?\n + $!.backtrace.first(3).join(?\n) log :ERROR, "error loading bot config: #{err}"
return return
end end
runBots bots run_bots bots
end end
end end
Thread.abort_on_exception = true Vrobot4.main open ARGV[0]
Vrobot4.main open(ARGV[0])
## EOF ## EOF

View File

@ -1,6 +1,6 @@
# Module for vrobot4 bot modules. # Module for vrobot4 bot modules.
module Vrobot4::Module module Vrobot4::Module
# A command function. Holds extra info for i.e. permissions. # A command function. Holds extra info such as help and permissions.
class Command class Command
attr_reader :help_str # @return [String] help string for this command attr_reader :help_str # @return [String] help string for this command
@ -15,6 +15,7 @@ module Vrobot4::Module
# Checks if this command is usable in a given context. # Checks if this command is usable in a given context.
# @param m [Vrobot4::Server::Message] message for context # @param m [Vrobot4::Server::Message] message for context
# @return [Boolean] true if command is usable, false otherwise
def usable_in? m def usable_in? m
m.user.roles.scan(@roles).any? and @function.owner.usable_in? m m.user.roles.scan(@roles).any? and @function.owner.usable_in? m
end end
@ -22,6 +23,7 @@ module Vrobot4::Module
# Calls the method attached to this command. # Calls the method attached to this command.
# @param m [Vrobot4::Server::Message] message for context # @param m [Vrobot4::Server::Message] message for context
# @param argv [String] argument string # @param argv [String] argument string
# @return [void]
def run m, argv def run m, argv
@function.call(m, argv) @function.call(m, argv)
end end
@ -31,6 +33,7 @@ module Vrobot4::Module
class Module class Module
# Checks if this module is usable in a given context. # Checks if this module is usable in a given context.
# @param m [Vrobot4::Server::Message] message for context # @param m [Vrobot4::Server::Message] message for context
# @return [Boolean] true if usable, false otherwise
def self.usable_in? m def self.usable_in? m
role = m.user.roles role = m.user.roles
mprm = m.serv.mprm mprm = m.serv.mprm
@ -45,7 +48,7 @@ module Vrobot4::Module
def initialize info def initialize info
@commands = {} @commands = {}
@info = info @info = info
Vrobot4.log :DEBUG, "initialized", self.to_s Vrobot4.log :DEBUG, "initialized #{self.to_s}"
end end
# Callback for message receiving. # Callback for message receiving.
@ -75,12 +78,16 @@ module Vrobot4::Module
# Gets all commands usable in a specified context. # Gets all commands usable in a specified context.
# @param m [Vrobot4::Server::Message] message for context # @param m [Vrobot4::Server::Message] message for context
# @return [Hash] a hash of all commands by name # @return [Hash<String, Vrobot4::Module::Command>]
# a hash of all commands by name
def cmds m def cmds m
@commands.select {|name, cmd| cmd.usable_in? m} @commands.select do |name, cmd|
cmd.usable_in? m
end
end end
protected protected
# Registers a command into this module. # Registers a command into this module.
# @param fn [Symbol] name of method to add # @param fn [Symbol] name of method to add
# @param names [String, Array] name(s) to add this command by # @param names [String, Array] name(s) to add this command by
@ -108,7 +115,7 @@ module Vrobot4::Module
# @param argv [String, Array] array of arguments or a string # @param argv [String, Array] array of arguments or a string
# @param req [String] specifier string of required arguments # @param req [String] specifier string of required arguments
# @param opt [String] specifier string of optional arguments # @param opt [String] specifier string of optional arguments
# @return argv # @return [String, Array] argv
def check_args argv, req, opt = "" def check_args argv, req, opt = ""
if argv.is_a? Array then if argv.is_a? Array then
if argv.length < req.length if argv.length < req.length
@ -137,15 +144,21 @@ module Vrobot4::Module
end end
private private
# Helper function for {#check_args}.
# @param arg [String] the argument to check
# @param i [Integer] which argument this is
# @param req [String] type that the argument should be
# @return [void]
def check_arg arg, i, req def check_arg arg, i, req
case req case req
when ?N when ?N
unless Vrobot4.is_num? arg.strip unless Vrobot4.is_num? arg.strip
raise ArgumentError, "Expected a number for arg " + i.to_s raise ArgumentError, "Expected a number for arg #{i}"
end end
when ?S # Don't need to check anything here. when ?S # Don't need to check anything here.
else else
raise ArgumentError, "Invalid argument specifier " + req.to_s raise ArgumentError, "Invalid argument specifier #{req}"
end end
end end
end end
@ -154,13 +167,14 @@ module Vrobot4::Module
# @param type [Class] module type to add # @param type [Class] module type to add
# @param server [String] server type this module is restricted to (or none) # @param server [String] server type this module is restricted to (or none)
# @param servflags [String] flags the server must have to use this (or none) # @param servflags [String] flags the server must have to use this (or none)
# @return [void]
def self.add_module_type type, server: nil, servflags: nil def self.add_module_type type, server: nil, servflags: nil
@@module_types[type.type] = { @@module_types[type.type] = {
type: type, type: type,
server: server, server: server,
servflags: servflags ? /[#{servflags}]/ : nil servflags: servflags ? /[#{servflags}]/ : nil
} }
Vrobot4.log :INFO, "added module type:", type.type Vrobot4.log :INFO, "added module type #{type.type}"
end end
# Gets a module type by name from the global list. # Gets a module type by name from the global list.
@ -170,7 +184,7 @@ module Vrobot4::Module
@@module_types[name] @@module_types[name]
end end
private # The set of all module types.
@@module_types = {} @@module_types = {}
end end

84
source/modules/base.rb Normal file
View File

@ -0,0 +1,84 @@
class Mod_Base < Vrobot4::Module::Module
def initialize info
super
register :c_help, "help", "Prints documentation for commands."
register :c_die, "die", "Kills all bot instances.", roles: ?o
register :c_modr, "modr", "Reloads a module.", roles: ?o
register :c_modl, "modl", "Loads a module.", roles: ?o
register :c_modu, "modu", "Unloads a module.", roles: ?o
register :c_dbg, "dbg", "Sets the debug level.", roles: ?o
register :c_info, "info", "Prints context info.", roles: ?o
end
def c_help m, argv
if argv.empty?
cmds = []
m.serv.each_mod do |mod|
cmds << mod.cmds(m).keys.join(", ")
end
cmds.delete ""
m.reply "Commands:", cmds.join(", ")
else
m.serv.each_mod do |mod|
if (cmd = mod.cmds(m)[argv])
return m.reply argv + ":", cmd.help_str
end
end
m.reply "Command not found:", argv
end
end
def c_die m, argv
m.serv.voice_quit m if m.serv.flags.include? ?A
m.reply \
["STATUS: DYING",
"ded",
"proceeding to die",
"bye",
"dedededed",
"Thanks, bye!",
"GOTTAGOBYE",
"the orks insisted upon dying"].sample
exit
end
def c_modr m, argv
argv = check_args argv.split, "S", "S"
m.serv.drop_mod argv[0]
load argv[1], true if argv.length > 1
m.serv.load_mod argv[0]
end
def c_modl m, argv
m.serv.load_mod argv
end
def c_modu m, argv
m.serv.drop_mod argv
end
def c_dbg m, argv
check_args argv, "N"
Vrobot4.debug = argv.to_i
end
def c_info m, argv
m.reply_b <<~_end_
chan.name: #{m.chan.name}
chan.id: #{m.chan.id}
user.name: #{m.user.name}
user.id: #{m.user.id}
user.roles: #{m.user.roles}
serv.flags: #{m.serv.flags}
serv.id: #{m.serv.id}
serv.mprm: #{m.serv.mprm}
_end_
end
def on_command m, cnam, argv
Vrobot4.log :DEBUGV, "command #{cnam.to_s} #{argv}"
super
end
end
## EOF

View File

@ -1,5 +1,7 @@
# Module for vrobot4 server interfaces. # Module for vrobot4 server interfaces.
module Vrobot4::Server module Vrobot4::Server
require "./modules/base.rb"
# Generic user information. May be extended. # Generic user information. May be extended.
class User class User
attr_reader :name # @return [String] plaintext name of the user attr_reader :name # @return [String] plaintext name of the user
@ -37,11 +39,15 @@ module Vrobot4::Server
end end
# Sends a message to the originating channel. # Sends a message to the originating channel.
# @param args [Array<String>] the text to send
# @return [void]
def reply *args def reply *args
@reply.call args.join(?\s) @reply.call args.join(?\s)
end end
# Sends a large message to the originating channel. # Sends a large message to the originating channel.
# @param args [Array<String>] the text to send
# @return [void]
def reply_b *args def reply_b *args
@reply_b.call args.join(?\s) @reply_b.call args.join(?\s)
end end
@ -61,6 +67,7 @@ module Vrobot4::Server
# Loads and initializes a module into the load list. # Loads and initializes a module into the load list.
# @param mod [Vrobot4::Module::Module] # @param mod [Vrobot4::Module::Module]
# @return [void]
def load_mod mod def load_mod mod
mt = Vrobot4::Module.get_module_type(mod) mt = Vrobot4::Module.get_module_type(mod)
if mt[:server] and mt[:server] != self.class.type or if mt[:server] and mt[:server] != self.class.type or
@ -72,6 +79,7 @@ module Vrobot4::Server
# Drops a module from the load list. # Drops a module from the load list.
# @param mod [Vrobot4::Module::Module] # @param mod [Vrobot4::Module::Module]
# @return [void]
def drop_mod mod def drop_mod mod
mt = Vrobot4::Module.get_module_type(mod) mt = Vrobot4::Module.get_module_type(mod)
@modules.each_index do |i| @modules.each_index do |i|
@ -80,12 +88,16 @@ module Vrobot4::Server
end end
# Yields for every module loaded in the server. # Yields for every module loaded in the server.
# @yieldparam mod [Vrobot4::Module::Module] an individual module
# @return [void]
def each_mod def each_mod
@modules.each {|mod| yield mod} @modules.each do |mod|
yield mod
end
end end
# (see Vrobot4::Module::Module#on_message) # (see Vrobot4::Module::Module#on_message)
# Passes information to all modules. # This function passes information to all modules.
def on_message m def on_message m
@modules.each do |mod| @modules.each do |mod|
break if mod.class.usable_in? m and mod.on_message m break if mod.class.usable_in? m and mod.on_message m
@ -93,7 +105,7 @@ module Vrobot4::Server
end end
# (see Vrobot4::Module::Module#on_command) # (see Vrobot4::Module::Module#on_command)
# Passes information to all modules. # This function passes information to all modules.
def on_command m, cnam, argv def on_command m, cnam, argv
@modules.each do |mod| @modules.each do |mod|
break if mod.class.usable_in? m and mod.on_command m, cnam, argv break if mod.class.usable_in? m and mod.on_command m, cnam, argv
@ -101,27 +113,27 @@ module Vrobot4::Server
end end
# Connect to the server. # Connect to the server.
# @abstract
# @return [void]
def connect def connect
raise NotImplementedError, "Server#connect not implemented" raise NotImplementedError, "Server#connect not implemented"
end end
# Flags for this server. # Flags for this server.
# [A] Server has audio capabilities and inherits from AudioServer. # [A] Server has audio capabilities and inherits from
# {Vrobot4::Server::AudioServer}.
# [L] Server cannot support large text dumps # [L] Server cannot support large text dumps
# (code should assume <=4 lines, 240 characters per as maximum) # (code should assume <=4 lines, 240 characters per as maximum)
# @return [String] # @return [String] all available flags
def flags def flags
"" ""
end end
# Basic command message handler. # Checks if the message starts with +.+ and splits the command from the
# # arguments, then emits an {#on_command} event. Otherwise, it will emit
# Checks if the message starts with "." and splits the command # an {#on_message} event.
# from the arguments, then emits an on_command event.
#
# Otherwise, it will emit an on_message event.
#
# @param m [Vrobot4::Server::Message] the message to parse and emit # @param m [Vrobot4::Server::Message] the message to parse and emit
# @return [void]
def handle_text_cmd m def handle_text_cmd m
if m.msg.start_with? ?. if m.msg.start_with? ?.
arg = m.msg.split(?\s, 2) arg = m.msg.split(?\s, 2)
@ -132,8 +144,12 @@ module Vrobot4::Server
end end
protected protected
# Implementation defined permission loader. # Implementation defined permission loader.
# Loads information into +@mprm+. # Loads information into +@mprm+.
# @abstract
# @param pinf [Hash] arbitrary permissions information
# @return [void]
def load_permissions pinf def load_permissions pinf
raise NotImplementedError, "Server#load_permissions not implemented" raise NotImplementedError, "Server#load_permissions not implemented"
end end
@ -141,94 +157,44 @@ module Vrobot4::Server
# Basis for an audio-enabled server interface. # Basis for an audio-enabled server interface.
class AudioServer < Server class AudioServer < Server
## TODO: make API for this
# (see Vrobot4::Server::Server#flags) # (see Vrobot4::Server::Server#flags)
def flags def flags
?A ?A
end end
end
class Mod_Base < Vrobot4::Module::Module # Quit using voice channels.
def initialize info # @abstract
super # @param m [Vrobot4::Server::Message] message for context
register :c_help, "help", "Prints documentation for commands." # @return [void]
register :c_die, "die", "Kills all bot instances.", roles: ?o def voice_quit m
register :c_modr, "modr", "Reloads a module.", roles: ?o raise NotImplementedError, "AudioServer#voice_quit not implemented"
register :c_modl, "modl", "Loads a module.", roles: ?o
register :c_modu, "modu", "Unloads a module.", roles: ?o
register :c_dbg, "dbg", "Sets the debug level.", roles: ?o
end end
# @!visibility private # Plays an audio file by name into the current voice channel.
def c_help m, argv # @abstract
if argv.empty? # @param m [Vrobot4::Server::Message] message for context
cmds = [] # @param fname [String] file name
m.serv.each_mod {|mod| cmds << mod.cmds(m).keys.join(", ")} # @return [void]
cmds.delete "" def voice_play m, fname
m.reply "Commands:", cmds.join(", ") raise NotImplementedError, "AudioServer#voice_play not implemented"
else
m.serv.each_mod do |mod|
if (cmd = mod.cmds(m)[argv])
return m.reply argv + ":", cmd.help_str
end
end
m.reply "Command not found:", argv
end
end end
# @!visibility private # Plays an audio I/O stream into the current voice channel.
def c_die m, argv # @abstract
m.serv.voice_quit m if m.serv.flags.include? ?A # @param m [Vrobot4::Server::Message] message for context
m.reply \ # @param io [IO] io stream
["STATUS: DYING", # @return [void]
"ded", def voice_play_io m, io
"proceeding to die", raise NotImplementedError, "AudioServer#voice_play_io not implemented"
"bye",
"dedededed",
"Thanks, bye!",
"GOTTAGOBYE",
"the orks insisted upon dying"].sample
exit
end
# @!visibility private
def c_modr m, argv
argv = check_args argv.split, "S", "S"
m.serv.drop_mod argv[0]
load argv[1], true if argv.length > 1
m.serv.load_mod argv[0]
end
# @!visibility private
def c_modl m, argv
m.serv.load_mod argv
end
# @!visibility private
def c_modu m, argv
m.serv.drop_mod argv
end
# @!visibility private
def c_dbg m, argv
check_args argv, "N"
Vrobot4.debug = argv.to_i
end
# @!visibility private
def on_command m, cnam, argv
Vrobot4.log :DEBUGV, "command", cnam.to_s, argv
super
end end
end end
private_constant :Mod_Base
# Adds a server type to the global list. # Adds a server type to the global list.
# @param type [Class] server type to add # @param type [Class] server type to add
# @return [void]
def self.add_server_type type def self.add_server_type type
@@server_types[type.type] = type @@server_types[type.type] = type
Vrobot4.log :INFO, "added server type:", type.type Vrobot4.log :INFO, "added server type: #{type.type}"
end end
# Gets a server type by name from the global list. # Gets a server type by name from the global list.
@ -238,7 +204,7 @@ module Vrobot4::Server
@@server_types[name] @@server_types[name]
end end
private # The set of all server types.
@@server_types = {} @@server_types = {}
end end

View File

@ -28,30 +28,32 @@ class Sv_Discord < Vrobot4::Server::AudioServer
chan: Channel.new(evt.channel), chan: Channel.new(evt.channel),
serv: self, serv: self,
reply: -> (text) {evt.respond text}, reply: -> (text) {evt.respond text},
reply_b: -> (text) {evt.respond "```\n" + text + "```"} reply_b: -> (text) {evt.respond "```\n#{text}```"}
handle_text_cmd m handle_text_cmd m
end end
end end
# (see Vrobot4::Server::AudioServer#voice_join) # (see Vrobot4::Server::AudioServer#voice_quit)
def voice_quit m def voice_quit m
@bot.voice_destroy m.chan.real.server.id @bot.voice_destroy m.chan.real.server.id
end end
# (see Vrobot4::Server::AudioServer#voice_play) # (see Vrobot4::Server::AudioServer#voice_play)
def voice_play m, fname def voice_play m, fname
vc = @bot.voice(m.chan.real) vc = @bot.voice m.chan.real
raise RuntimeError, "I'm not in a voice channel" unless vc raise RuntimeError, "I'm not in a voice channel" unless vc
vc.play_file fname vc.play_file fname
end end
# (see Vrobot4::Server::AudioServer#voice_play_io) # (see Vrobot4::Server::AudioServer#voice_play_io)
def voice_play_io m, io def voice_play_io m, io
vc = @bot.voice(m.chan.real) vc = @bot.voice m.chan.real
raise ArgumentError, "Invalid i/o stream" unless io raise ArgumentError, "Invalid IO stream" unless io
raise RuntimeError, "I'm not in a voice channel" unless vc raise RuntimeError, "I'm not in a voice channel" unless vc
Thread.new {vc.play_io io} Thread.new do
vc.play_io io
end
# HACK # HACK
sleep 1 sleep 1
oldst = nil oldst = nil
@ -72,6 +74,7 @@ class Sv_Discord < Vrobot4::Server::AudioServer
end end
protected protected
# (see Vrobot4::Server::Server#load_permissions) # (see Vrobot4::Server::Server#load_permissions)
def load_permissions pinf def load_permissions pinf
@mprm = {chan: {}, role: {}, glob: {}} @mprm = {chan: {}, role: {}, glob: {}}
@ -88,13 +91,18 @@ class Sv_Discord < Vrobot4::Server::AudioServer
end if pinf end if pinf
end end
# Helper for channel permissions handling.
class ChannelPerms class ChannelPerms
def initialize() @cprm = {} end def initialize() @cprm = {} end
# Returns true if the channel is enabled, false otherwise. # @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 def [](chan) @cprm[chan.name] or @cprm[chan.real.id] end
# Sets a channel's permission on/off. # 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 def []=(chan, set) @cprm[chan] = set end
end end
private_constant :ChannelPerms private_constant :ChannelPerms
@ -114,9 +122,9 @@ class Sv_Discord < Vrobot4::Server::AudioServer
if user.is_a? Discordrb::Member if user.is_a? Discordrb::Member
if user.owner? if user.owner?
@roles += "Ooh" @roles += "Ooh"
elsif ops and ops.any? {|role| user.role? role} elsif ops and ops.any? do |role| user.role? role end
@roles += "oh" @roles += "oh"
elsif hop and hop.any? {|role| user.role? role} elsif hop and hop.any? do |role| user.role? role end
@roles += "h" @roles += "h"
end end
end end