zetabot 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +281 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/Zeta.gemspec +74 -0
- data/bin/console +14 -0
- data/bin/setup +13 -0
- data/bin/zeta +9 -0
- data/bin/zeta-setup +13 -0
- data/lib/Zeta.rb +13 -0
- data/lib/Zeta/access.rb +84 -0
- data/lib/Zeta/admin.rb +10 -0
- data/lib/Zeta/admin/autojoin.rb +25 -0
- data/lib/Zeta/admin/bot.rb +43 -0
- data/lib/Zeta/admin/channels.rb +36 -0
- data/lib/Zeta/admin/eval.rb +43 -0
- data/lib/Zeta/admin/fifo.rb +45 -0
- data/lib/Zeta/admin/ignore.rb +112 -0
- data/lib/Zeta/admin/oper.rb +50 -0
- data/lib/Zeta/admin/plugins.rb +109 -0
- data/lib/Zeta/admin/users.rb +5 -0
- data/lib/Zeta/blacklist.rb +25 -0
- data/lib/Zeta/cache.rb +0 -0
- data/lib/Zeta/cinch.rb +35 -0
- data/lib/Zeta/config.rb +42 -0
- data/lib/Zeta/gems.rb +0 -0
- data/lib/Zeta/locale.rb +4 -0
- data/lib/Zeta/log.rb +2 -0
- data/lib/Zeta/models.rb +5 -0
- data/lib/Zeta/models/channel.rb +3 -0
- data/lib/Zeta/models/plugin.rb +3 -0
- data/lib/Zeta/models/user.rb +3 -0
- data/lib/Zeta/plugins.rb +27 -0
- data/lib/Zeta/plugins/attack.rb +61 -0
- data/lib/Zeta/plugins/botinfo.rb +83 -0
- data/lib/Zeta/plugins/darkscience.rb +215 -0
- data/lib/Zeta/plugins/dbz.rb +31 -0
- data/lib/Zeta/plugins/dcc.rb +25 -0
- data/lib/Zeta/plugins/dnsbl.rb +36 -0
- data/lib/Zeta/plugins/echo.rb +15 -0
- data/lib/Zeta/plugins/eightball.rb +53 -0
- data/lib/Zeta/plugins/fml.rb +35 -0
- data/lib/Zeta/plugins/fnord.rb +329 -0
- data/lib/Zeta/plugins/gem.rb +0 -0
- data/lib/Zeta/plugins/gif.rb +73 -0
- data/lib/Zeta/plugins/help.rb +32 -0
- data/lib/Zeta/plugins/libsecure.rb +34 -0
- data/lib/Zeta/plugins/macros.rb +124 -0
- data/lib/Zeta/plugins/movie.rb +67 -0
- data/lib/Zeta/plugins/pdfinfo.rb +69 -0
- data/lib/Zeta/plugins/rainbow.rb +65 -0
- data/lib/Zeta/plugins/russian_roulette.rb +90 -0
- data/lib/Zeta/plugins/seen.rb +77 -0
- data/lib/Zeta/plugins/silly.rb +183 -0
- data/lib/Zeta/plugins/snooper.rb +146 -0
- data/lib/Zeta/plugins/urban.rb +60 -0
- data/lib/Zeta/plugins/weather.rb +203 -0
- data/lib/Zeta/plugins/whois.rb +25 -0
- data/lib/Zeta/plugins/wiki.rb +75 -0
- data/lib/Zeta/plugins/wolfram.rb +46 -0
- data/lib/Zeta/tasks/db.rb +0 -0
- data/lib/Zeta/version.rb +3 -0
- data/lib/generators/config/config.rb +0 -0
- data/lib/generators/plugin/new_plugin.rb +0 -0
- data/locale/en/8ball.yml +0 -0
- data/locale/en/attack.yml +148 -0
- data/locale/en/dbz.yml +10 -0
- data/locale/en/ircop.yml +5 -0
- data/locale/en/macros.yml +98 -0
- data/locale/en/meme.yml +27 -0
- metadata +636 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# This is considered a dangerous plugin, use it only if you know what you are doing
|
2
|
+
module Admin
|
3
|
+
class Oper
|
4
|
+
include Cinch::Plugin
|
5
|
+
include Cinch::Helpers
|
6
|
+
|
7
|
+
enable_acl(:oper)
|
8
|
+
|
9
|
+
# Regex
|
10
|
+
match 'operup', method: :oper_up
|
11
|
+
match /kill ([\S]+) (.+)/, method: :oper_kill
|
12
|
+
match /clearchan (.+) (true|yes)/, method: :oper_clearchan
|
13
|
+
|
14
|
+
# Methods
|
15
|
+
def oper_up(m)
|
16
|
+
if Config.oper_username && Config.oper_password
|
17
|
+
@bot.oper(Config.oper_password, Config.oper_username)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def oper_kill(m, nick, message)
|
22
|
+
return if User(nick).oper?
|
23
|
+
if @bot.irc.send("KILL #{nick} #{message}")
|
24
|
+
m.reply "#{nick}: has been killed by #{m.user.nick} for #{message}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def oper_clearchan(m, chan, confirm=false)
|
29
|
+
if confirm == 'yes' || confirm == 'true'
|
30
|
+
Channel(chan).join
|
31
|
+
User('OperServ').send("mode #{chan} +o #{Zeta}")
|
32
|
+
User('OperServ').send("mode #{chan} +mi")
|
33
|
+
Channel(chan).send('This channel is being cleared')
|
34
|
+
|
35
|
+
number_killed = 0
|
36
|
+
Channel(chan).users.each_key do |u|
|
37
|
+
if u != Zeta
|
38
|
+
number_killed += 1
|
39
|
+
@bot.irc.send("KILL #{u.nick} This channel is being cleared")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
m.safe_reply "#{chan} has been cleared, #{number_killed} clients killed"
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Bot.config.plugins.plugins.push Admin::Oper
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Admin
|
2
|
+
class Plugins
|
3
|
+
include Cinch::Plugin
|
4
|
+
include Cinch::Helpers
|
5
|
+
|
6
|
+
enable_acl(:admin, true)
|
7
|
+
|
8
|
+
set(
|
9
|
+
plugin_name: 'PluginsAdmin',
|
10
|
+
help: "Bot administrator-only private commands.\nUsage: `~join [channel]`; `~part [channel] <reason>`; `~quit [reason]`;",
|
11
|
+
# prefix: /^\?/
|
12
|
+
)
|
13
|
+
|
14
|
+
# Regex
|
15
|
+
match(/plugin load (\S+)(?: (\S+))?/, method: :load_plugin)
|
16
|
+
match(/plugin unload (\S+)/, method: :unload_plugin)
|
17
|
+
match(/plugin reload (\S+)(?: (\S+))?/, method: :reload_plugin)
|
18
|
+
match(/plugin set (\S+) (\S+) (.+)$/, method: :set_option)
|
19
|
+
|
20
|
+
# Methods
|
21
|
+
def load_plugin(m, plugin, mapping)
|
22
|
+
mapping ||= plugin.gsub(/(.)([A-Z])/) { |_|
|
23
|
+
$1 + "_" + $2
|
24
|
+
}.downcase # we downcase here to also catch the first letter
|
25
|
+
|
26
|
+
#file_name = "lib/plugins/#{mapping}.rb"
|
27
|
+
stdlib_plugin = File.join('Zeta', 'plugins', p.to_s)
|
28
|
+
custom_plugin = File.join(Dir.home, '.zeta', 'plugins', p.to_s, "#{p.to_s}.rb")
|
29
|
+
|
30
|
+
unless File.exist?(custom_plugin)
|
31
|
+
m.reply "Could not load #{plugin} because #{file_name} does not exist."
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
load(custom_plugin)
|
37
|
+
rescue
|
38
|
+
m.reply "Could not load #{plugin}."
|
39
|
+
raise
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
const = Plugins.const_get(plugin)
|
44
|
+
rescue NameError
|
45
|
+
m.reply "Could not load #{plugin} because no matching class was found."
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
@bot.plugins.register_plugin(const)
|
50
|
+
m.reply "Successfully loaded #{plugin}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def unload_plugin(m, plugin)
|
54
|
+
begin
|
55
|
+
plugin_class = Plugins.const_get(plugin)
|
56
|
+
rescue NameError
|
57
|
+
m.reply "Could not unload #{plugin} because no matching class was found."
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
@bot.plugins.select { |p| p.class == plugin_class }.each do |p|
|
62
|
+
@bot.plugins.unregister_plugin(p)
|
63
|
+
end
|
64
|
+
|
65
|
+
## FIXME not doing this at the moment because it'll break
|
66
|
+
## plugin options. This means, however, that reloading a
|
67
|
+
## plugin is relatively dirty: old methods will not be removed
|
68
|
+
## but only overwritten by new ones. You will also not be able
|
69
|
+
## to change a classes superclass this way.
|
70
|
+
# Cinch::Plugins.__send__(:remove_const, plugin)
|
71
|
+
|
72
|
+
# Because we're not completely removing the plugin class,
|
73
|
+
# reset everything to the starting values.
|
74
|
+
plugin_class.hooks.clear
|
75
|
+
plugin_class.matchers.clear
|
76
|
+
plugin_class.listeners.clear
|
77
|
+
plugin_class.timers.clear
|
78
|
+
plugin_class.ctcps.clear
|
79
|
+
plugin_class.react_on = :message
|
80
|
+
plugin_class.plugin_name = nil
|
81
|
+
plugin_class.help = nil
|
82
|
+
plugin_class.prefix = nil
|
83
|
+
plugin_class.suffix = nil
|
84
|
+
plugin_class.required_options.clear
|
85
|
+
|
86
|
+
m.reply "Successfully unloaded #{plugin}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def reload_plugin(m, plugin, mapping)
|
90
|
+
unload_plugin(m, plugin)
|
91
|
+
load_plugin(m, plugin, mapping)
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_option(m, plugin, option, value)
|
95
|
+
begin
|
96
|
+
const = Plugins.const_get(plugin)
|
97
|
+
rescue NameError
|
98
|
+
m.reply "Could not set plugin option for #{plugin} because no matching class was found."
|
99
|
+
return
|
100
|
+
end
|
101
|
+
@bot.config.plugins.options[const][option.to_sym] = eval(value)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# AutoLoad
|
109
|
+
Bot.config.plugins.plugins.push Admin::Plugins
|
@@ -0,0 +1,25 @@
|
|
1
|
+
BlackListStruct = Struct.new :channels, :users, :plugins
|
2
|
+
|
3
|
+
# Load Cached
|
4
|
+
if File.exists?(File.join(Dir.home, '.zeta', 'cache', 'blacklist.rb'))
|
5
|
+
File.open(File.join(Dir.home, '.zeta', 'cache', 'blacklist.rb')) do |file|
|
6
|
+
Blacklist = Marshal.load(file)
|
7
|
+
end
|
8
|
+
else
|
9
|
+
Blacklist = BlackListStruct.new([], [], [])
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
## Methods
|
14
|
+
def save_blacklist()
|
15
|
+
File.open(File.join(Dir.home, '.zeta', 'cache', 'blacklist.rb'), 'w+') do |file|
|
16
|
+
Marshal.dump(Blacklist, file)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def clear_blacklist()
|
21
|
+
Blacklist.users = []
|
22
|
+
Blacklist.plugins = []
|
23
|
+
Blacklist.channels = []
|
24
|
+
File.delete(File.join(Dir.home, '.zeta', 'cache', 'blacklist.rb'))
|
25
|
+
end
|
data/lib/Zeta/cache.rb
ADDED
File without changes
|
data/lib/Zeta/cinch.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'cinch'
|
2
|
+
|
3
|
+
Bot = Cinch::Bot.new do
|
4
|
+
configure do |c|
|
5
|
+
c.nick = Config.nickname
|
6
|
+
c.nicks = Config.nicks.split(',')
|
7
|
+
c.user = Config.username
|
8
|
+
c.realname = Config.realname
|
9
|
+
c.sasl.username = Config.sasl_username
|
10
|
+
c.sasl.password = Config.sasl_password
|
11
|
+
c.server = Config.server
|
12
|
+
c.password = Config.password
|
13
|
+
c.port = Config.port
|
14
|
+
c.ssl.use = Config.ssl
|
15
|
+
c.max_messages = Config.max_messages
|
16
|
+
c.messages_per_second = Config.messages_per_second
|
17
|
+
c.modes = Config.modes.split(',')
|
18
|
+
c.channels = Config.channels.split(',')
|
19
|
+
c.plugins.prefix = /^#{Config.prefix}/
|
20
|
+
end
|
21
|
+
|
22
|
+
# Execute on confirmation of connection
|
23
|
+
on :connect do
|
24
|
+
# Gain operator privileges if oper username and password are set in config
|
25
|
+
if Config.oper_username && Config.oper_password
|
26
|
+
if !Config.oper_password.empty?
|
27
|
+
@bot.oper(Config.oper_password, Config.oper_username)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
data/lib/Zeta/config.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
ConfigStruct = Struct.new(
|
2
|
+
:locale,
|
3
|
+
:nickname,
|
4
|
+
:nicks,
|
5
|
+
:username,
|
6
|
+
:realname,
|
7
|
+
:prefix,
|
8
|
+
:server,
|
9
|
+
:password,
|
10
|
+
:port,
|
11
|
+
:ssl,
|
12
|
+
:sasl_username,
|
13
|
+
:sasl_password,
|
14
|
+
:max_messages,
|
15
|
+
:messages_per_second,
|
16
|
+
:modes,
|
17
|
+
:channels,
|
18
|
+
:custom_plugins,
|
19
|
+
:plugins,
|
20
|
+
:oper_username,
|
21
|
+
:oper_password,
|
22
|
+
:oper_overide,
|
23
|
+
:log_channel,
|
24
|
+
:services,
|
25
|
+
:secure_mode,
|
26
|
+
:secure_channel,
|
27
|
+
:secure_host,
|
28
|
+
:options,
|
29
|
+
:secrets
|
30
|
+
)
|
31
|
+
# Initialize Config
|
32
|
+
Config = ConfigStruct.new
|
33
|
+
|
34
|
+
if File.exists? File.join(Dir.home, '.zeta', 'config.rb')
|
35
|
+
begin
|
36
|
+
require File.join(Dir.home, '.zeta', 'config.rb')
|
37
|
+
rescue => e
|
38
|
+
puts e
|
39
|
+
abort('Unable to read config')
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/lib/Zeta/gems.rb
ADDED
File without changes
|
data/lib/Zeta/locale.rb
ADDED
data/lib/Zeta/log.rb
ADDED
data/lib/Zeta/models.rb
ADDED
data/lib/Zeta/plugins.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Plugins; end
|
2
|
+
|
3
|
+
# Load STDLib plugins
|
4
|
+
if Config.plugins.class == Array
|
5
|
+
Config.plugins.each do |p|
|
6
|
+
begin
|
7
|
+
require File.join('Zeta', 'plugins', p.to_s)
|
8
|
+
rescue => e
|
9
|
+
puts e
|
10
|
+
warn "## Unable to load plugin: #{p} ##"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
## Load Custom Plugins
|
17
|
+
if Config.custom_plugins.class == Array
|
18
|
+
Config.custom_plugins.each do |p|
|
19
|
+
begin
|
20
|
+
require File.join(Dir.home, '.zeta', 'plugins', p.to_s, "#{p.to_s}.rb")
|
21
|
+
rescue => e
|
22
|
+
puts e
|
23
|
+
warn "## Unable to load Custom plugin: #{p} ##"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Plugins
|
6
|
+
class Attack
|
7
|
+
include Cinch::Plugin
|
8
|
+
include Cinch::Helpers
|
9
|
+
|
10
|
+
enable_acl
|
11
|
+
|
12
|
+
set(
|
13
|
+
plugin_name: 'Random Attacker',
|
14
|
+
help: 'Attacks a user with a random attack.\nUsage: `?attack <nick or phrase>`; `<nick or phrase>` may be omitted for a random attack on a random nick.',
|
15
|
+
react_on: :channel
|
16
|
+
)
|
17
|
+
|
18
|
+
def initialize(*args)
|
19
|
+
@attackdict = []
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
# Regex
|
24
|
+
match /attack(?: (.+))?/, group: :attack
|
25
|
+
|
26
|
+
# Methods
|
27
|
+
def execute(m, target=nil)
|
28
|
+
target = target.rstrip
|
29
|
+
target = m.user.nick if !target.nil? && target.match(/(\bmy\b|\b#{@bot.nick}\S*\b|\b\S*self\b)/i)
|
30
|
+
target.gsub(/(\bmy\b|\b#{@bot.nick}\S*\b|\b\S*self\b)/i,m.user.nick+"'s") if !target.nil?;
|
31
|
+
populate_attacks!
|
32
|
+
output = if target
|
33
|
+
attack!(target.gsub(/\x03([0-9]{2}(,[0-9]{2})?)?/,''), m.user.nick)
|
34
|
+
else
|
35
|
+
random_attack!(m.channel.users, m.user.nick)
|
36
|
+
end
|
37
|
+
m.channel.action output
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def populate_attacks!
|
42
|
+
@attackdict.replace load_locale('attack').map {|e| e.gsub(/<(\w+)>/, "%<#{'\1'.downcase}>s") }
|
43
|
+
end
|
44
|
+
|
45
|
+
def grab_random_nick(users)
|
46
|
+
users.to_a.sample[0].nick;
|
47
|
+
end
|
48
|
+
|
49
|
+
def attack!(target, assailant)
|
50
|
+
return nil if target.nil?;
|
51
|
+
@attackdict.sample % {:target => target, :assailant => assailant, :bot => @bot.nick};
|
52
|
+
end
|
53
|
+
|
54
|
+
def random_attack!(targets, assailant)
|
55
|
+
@attackdict.sample % {:target => grab_random_nick(targets), :assailant => assailant, :bot => @bot.nick};
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# AutoLoad
|
61
|
+
Bot.config.plugins.plugins.push Plugins::Attack
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'tag_formatter'
|
3
|
+
require 'active_support/core_ext/object/blank'
|
4
|
+
require 'active_support/core_ext/array/conversions'
|
5
|
+
require 'chronic_duration'
|
6
|
+
|
7
|
+
|
8
|
+
module Plugins
|
9
|
+
class BotInfo
|
10
|
+
include Cinch::Plugin
|
11
|
+
include Cinch::Helpers
|
12
|
+
|
13
|
+
enable_acl
|
14
|
+
|
15
|
+
set(
|
16
|
+
plugin_name: "Botinfo",
|
17
|
+
help: "Notices you information about me.\nUsage: `/msg <nick> info`\nUsage: `/msg <nick> list plugins`",
|
18
|
+
react_on: :private)
|
19
|
+
|
20
|
+
def initialize *args
|
21
|
+
super
|
22
|
+
@started_at = Time.now
|
23
|
+
end
|
24
|
+
|
25
|
+
# How to config:
|
26
|
+
# :template -- a path to a textual file (such as *.txt) with fields in it.
|
27
|
+
# :owner -- What to display for the "owner_name" field.
|
28
|
+
# All fields in the text file must be surrounded by '<>', and lines can be commented out using '#'.
|
29
|
+
|
30
|
+
# Regex
|
31
|
+
match 'info', use_prefix: false
|
32
|
+
match 'list plugins', method: :execute_list, use_prefix: false
|
33
|
+
match /^help (.+)$/i, method: :execute_help, use_prefix: false
|
34
|
+
|
35
|
+
# Methods
|
36
|
+
def execute(m)
|
37
|
+
tags = {
|
38
|
+
bot_name: @bot.nick,
|
39
|
+
cinch_version: Cinch::VERSION,
|
40
|
+
#is_admin: config[:admins].is_admin?(m.user.mask) ? "an admin" : "not an admin",
|
41
|
+
owner_name: 'Liothen',
|
42
|
+
plugins_count_remaining: @bot.plugins.length - 10,
|
43
|
+
plugins_head: @bot.plugins[0..9].map {|p| p.class.plugin_name }.join(", "),
|
44
|
+
ruby_platform: RUBY_PLATFORM,
|
45
|
+
ruby_release_date: RUBY_RELEASE_DATE,
|
46
|
+
ruby_version: RUBY_VERSION,
|
47
|
+
session_start_date: @started_at.strftime("%A, %B %e, %Y, at %l:%M:%S %P"),
|
48
|
+
total_channels: @bot.channels.length,
|
49
|
+
total_users: proc {
|
50
|
+
users = [];
|
51
|
+
@bot.channels.each {|c|
|
52
|
+
c.users.each {|u| users << u[0].nick
|
53
|
+
}
|
54
|
+
};
|
55
|
+
users.uniq.size
|
56
|
+
}.call,
|
57
|
+
uptime: ChronicDuration.output(Time.now.to_i - @started_at.to_i)
|
58
|
+
}
|
59
|
+
|
60
|
+
tf = TagFormatter.new open(File.join(__dir__, '../botinfo.txt'),&:read), tags: tags
|
61
|
+
|
62
|
+
m.user.notice tf.parse
|
63
|
+
end
|
64
|
+
|
65
|
+
def execute_list(m)
|
66
|
+
|
67
|
+
list = []
|
68
|
+
@bot.plugins.each {|p| list << p.class.plugin_name };
|
69
|
+
m.user.notice("All #{list.size} currently loaded plugins for #{@bot.nick}:\n#{list.to_sentence}.\nTo view help for a plugin, use `/msg #{@bot.nick} help <plugin name>`.")
|
70
|
+
end
|
71
|
+
|
72
|
+
def execute_help(m, name)
|
73
|
+
list = {}
|
74
|
+
@bot.plugins.each {|p| list[p.class.plugin_name.downcase] = {name: p.class.plugin_name, help: p.class.help} };
|
75
|
+
return m.user.notice("Help for \"#{name}\" could not be found.") if !list.has_key?(name.downcase)
|
76
|
+
m.user.notice("Help for #{Format(:bold,list[name.downcase][:name])}:\n#{list[name.downcase][:help]}")
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# AutoLoad
|
83
|
+
Bot.config.plugins.plugins.push Plugins::BotInfo
|