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

View File

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

View File

@ -1,6 +1,6 @@
# Module for vrobot4 bot modules.
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
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.
# @param m [Vrobot4::Server::Message] message for context
# @return [Boolean] true if command is usable, false otherwise
def usable_in? m
m.user.roles.scan(@roles).any? and @function.owner.usable_in? m
end
@ -22,6 +23,7 @@ module Vrobot4::Module
# Calls the method attached to this command.
# @param m [Vrobot4::Server::Message] message for context
# @param argv [String] argument string
# @return [void]
def run m, argv
@function.call(m, argv)
end
@ -31,6 +33,7 @@ module Vrobot4::Module
class Module
# Checks if this module is usable in a given context.
# @param m [Vrobot4::Server::Message] message for context
# @return [Boolean] true if usable, false otherwise
def self.usable_in? m
role = m.user.roles
mprm = m.serv.mprm
@ -45,7 +48,7 @@ module Vrobot4::Module
def initialize info
@commands = {}
@info = info
Vrobot4.log :DEBUG, "initialized", self.to_s
Vrobot4.log :DEBUG, "initialized #{self.to_s}"
end
# Callback for message receiving.
@ -75,12 +78,16 @@ module Vrobot4::Module
# Gets all commands usable in a specified 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
@commands.select {|name, cmd| cmd.usable_in? m}
@commands.select do |name, cmd|
cmd.usable_in? m
end
end
protected
# Registers a command into this module.
# @param fn [Symbol] name of method to add
# @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 req [String] specifier string of required arguments
# @param opt [String] specifier string of optional arguments
# @return argv
# @return [String, Array] argv
def check_args argv, req, opt = ""
if argv.is_a? Array then
if argv.length < req.length
@ -137,15 +144,21 @@ module Vrobot4::Module
end
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
case req
when ?N
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
when ?S # Don't need to check anything here.
else
raise ArgumentError, "Invalid argument specifier " + req.to_s
raise ArgumentError, "Invalid argument specifier #{req}"
end
end
end
@ -154,13 +167,14 @@ module Vrobot4::Module
# @param type [Class] module type to add
# @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)
# @return [void]
def self.add_module_type type, server: nil, servflags: nil
@@module_types[type.type] = {
type: type,
server: server,
servflags: servflags ? /[#{servflags}]/ : nil
}
Vrobot4.log :INFO, "added module type:", type.type
Vrobot4.log :INFO, "added module type #{type.type}"
end
# Gets a module type by name from the global list.
@ -170,7 +184,7 @@ module Vrobot4::Module
@@module_types[name]
end
private
# The set of all module types.
@@module_types = {}
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 Vrobot4::Server
require "./modules/base.rb"
# Generic user information. May be extended.
class User
attr_reader :name # @return [String] plaintext name of the user
@ -37,11 +39,15 @@ module Vrobot4::Server
end
# Sends a message to the originating channel.
# @param args [Array<String>] the text to send
# @return [void]
def reply *args
@reply.call args.join(?\s)
end
# Sends a large message to the originating channel.
# @param args [Array<String>] the text to send
# @return [void]
def reply_b *args
@reply_b.call args.join(?\s)
end
@ -61,6 +67,7 @@ module Vrobot4::Server
# Loads and initializes a module into the load list.
# @param mod [Vrobot4::Module::Module]
# @return [void]
def load_mod mod
mt = Vrobot4::Module.get_module_type(mod)
if mt[:server] and mt[:server] != self.class.type or
@ -72,6 +79,7 @@ module Vrobot4::Server
# Drops a module from the load list.
# @param mod [Vrobot4::Module::Module]
# @return [void]
def drop_mod mod
mt = Vrobot4::Module.get_module_type(mod)
@modules.each_index do |i|
@ -80,12 +88,16 @@ module Vrobot4::Server
end
# Yields for every module loaded in the server.
# @yieldparam mod [Vrobot4::Module::Module] an individual module
# @return [void]
def each_mod
@modules.each {|mod| yield mod}
@modules.each do |mod|
yield mod
end
end
# (see Vrobot4::Module::Module#on_message)
# Passes information to all modules.
# This function passes information to all modules.
def on_message m
@modules.each do |mod|
break if mod.class.usable_in? m and mod.on_message m
@ -93,7 +105,7 @@ module Vrobot4::Server
end
# (see Vrobot4::Module::Module#on_command)
# Passes information to all modules.
# This function passes information to all modules.
def on_command m, cnam, argv
@modules.each do |mod|
break if mod.class.usable_in? m and mod.on_command m, cnam, argv
@ -101,27 +113,27 @@ module Vrobot4::Server
end
# Connect to the server.
# @abstract
# @return [void]
def connect
raise NotImplementedError, "Server#connect not implemented"
end
# 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
# (code should assume <=4 lines, 240 characters per as maximum)
# @return [String]
# @return [String] all available flags
def flags
""
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 an on_message event.
#
# Checks if the message starts with +.+ and splits the command 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
# @return [void]
def handle_text_cmd m
if m.msg.start_with? ?.
arg = m.msg.split(?\s, 2)
@ -132,8 +144,12 @@ module Vrobot4::Server
end
protected
# Implementation defined permission loader.
# Loads information into +@mprm+.
# @abstract
# @param pinf [Hash] arbitrary permissions information
# @return [void]
def load_permissions pinf
raise NotImplementedError, "Server#load_permissions not implemented"
end
@ -141,94 +157,44 @@ module Vrobot4::Server
# Basis for an audio-enabled server interface.
class AudioServer < Server
## TODO: make API for this
# (see Vrobot4::Server::Server#flags)
def flags
?A
end
end
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
# Quit using voice channels.
# @abstract
# @param m [Vrobot4::Server::Message] message for context
# @return [void]
def voice_quit m
raise NotImplementedError, "AudioServer#voice_quit not implemented"
end
# @!visibility private
def c_help m, argv
if argv.empty?
cmds = []
m.serv.each_mod {|mod| cmds << mod.cmds(m).keys.join(", ")}
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
# Plays an audio file by name into the current voice channel.
# @abstract
# @param m [Vrobot4::Server::Message] message for context
# @param fname [String] file name
# @return [void]
def voice_play m, fname
raise NotImplementedError, "AudioServer#voice_play not implemented"
end
# @!visibility private
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
# @!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
# Plays an audio I/O stream into the current voice channel.
# @abstract
# @param m [Vrobot4::Server::Message] message for context
# @param io [IO] io stream
# @return [void]
def voice_play_io m, io
raise NotImplementedError, "AudioServer#voice_play_io not implemented"
end
end
private_constant :Mod_Base
# 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
Vrobot4.log :INFO, "added server type: #{type.type}"
end
# Gets a server type by name from the global list.
@ -238,7 +204,7 @@ module Vrobot4::Server
@@server_types[name]
end
private
# The set of all server types.
@@server_types = {}
end

View File

@ -28,30 +28,32 @@ class Sv_Discord < Vrobot4::Server::AudioServer
chan: Channel.new(evt.channel),
serv: self,
reply: -> (text) {evt.respond text},
reply_b: -> (text) {evt.respond "```\n" + text + "```"}
reply_b: -> (text) {evt.respond "```\n#{text}```"}
handle_text_cmd m
end
end
# (see Vrobot4::Server::AudioServer#voice_join)
# (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)
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 i/o stream" unless 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 {vc.play_io io}
Thread.new do
vc.play_io io
end
# HACK
sleep 1
oldst = nil
@ -72,6 +74,7 @@ class Sv_Discord < Vrobot4::Server::AudioServer
end
protected
# (see Vrobot4::Server::Server#load_permissions)
def load_permissions pinf
@mprm = {chan: {}, role: {}, glob: {}}
@ -88,13 +91,18 @@ class Sv_Discord < Vrobot4::Server::AudioServer
end if pinf
end
# Helper for channel permissions handling.
class ChannelPerms
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
# 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
@ -114,9 +122,9 @@ class Sv_Discord < Vrobot4::Server::AudioServer
if user.is_a? Discordrb::Member
if user.owner?
@roles += "Ooh"
elsif ops and ops.any? {|role| user.role? role}
elsif ops and ops.any? do |role| user.role? role end
@roles += "oh"
elsif hop and hop.any? {|role| user.role? role}
elsif hop and hop.any? do |role| user.role? role end
@roles += "h"
end
end