refactored config
This commit is contained in:
parent
d06b8f2fdc
commit
b552c35baa
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,3 +10,4 @@ logs/
|
||||||
*.txt
|
*.txt
|
||||||
*.db-journal
|
*.db-journal
|
||||||
tourettes.py
|
tourettes.py
|
||||||
|
config.py
|
||||||
|
|
46
README.md
46
README.md
|
@ -1,56 +1,10 @@
|
||||||
# Fulvia
|
# Fulvia
|
||||||
## NIGGER DICKS 2: Electric Boogaloo
|
|
||||||
It's like Sopel, except rewritten from scratch using Twisted as a base and over half the features ripped out.
|
It's like Sopel, except rewritten from scratch using Twisted as a base and over half the features ripped out.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
Python 3.6+
|
Python 3.6+
|
||||||
Python packages: `twisted python-dateutil requests bs4 wolframalpha pyenchant emoji Pillow xml2dict ipython numpy numpngw`
|
Python packages: `twisted python-dateutil requests bs4 wolframalpha pyenchant emoji Pillow xml2dict ipython numpy numpngw`
|
||||||
|
|
||||||
## Config
|
|
||||||
`nickname` - The nickname the bot will use.
|
|
||||||
`realname` - The realname the bot will use.
|
|
||||||
`username` - The user ident the bot will use.
|
|
||||||
`prefix` - The command prefix the bot will listen for.
|
|
||||||
`homedir` - The bot's home directory. Required for the bot to get certain pathing right. In the future that will be obtained automatically.
|
|
||||||
`server` - The server address to connect to.
|
|
||||||
`port` - The server port to connect to. SSL will probably not work.
|
|
||||||
`use_ssl` - Place holder.
|
|
||||||
`channels` - Which channels to join upon connection.
|
|
||||||
`db_filename` - Filename to use for the bot's database.
|
|
||||||
`owner` - The bot's owner. Use for permission purposes on restricted commands. Outranks admins. Should be the full hostmask of the user.
|
|
||||||
`admins` - Comma-delineated list of admins the bot will recognize for restricted commands. Should be the full hostmask for each one.
|
|
||||||
`default_time_format` - The format used for all timestamp operations. See the official python docs for the `time` library for more information.
|
|
||||||
`disabled_modules` - Comma-delineated list of modules *not* to load on startup. Modules should be specified without the `.py` extension.
|
|
||||||
|
|
||||||
### Example default.cfg
|
|
||||||
```
|
|
||||||
[core]
|
|
||||||
nickname = DiceBot9002
|
|
||||||
realname = DiceBot9002
|
|
||||||
username = DiceBot9002
|
|
||||||
prefix = .
|
|
||||||
homedir = /home/iou1name/fulvia
|
|
||||||
server = irc.steelbea.me
|
|
||||||
port = 6667
|
|
||||||
use_ssl = false
|
|
||||||
channels = #SomaIsGay,#test
|
|
||||||
db_filename = DiceBot9002.db
|
|
||||||
owner = iou1name!~iou1name@operational.operator
|
|
||||||
admins =
|
|
||||||
default_time_format = [%Y-%m-%d %H:%M:%S]
|
|
||||||
disabled_modules = countdown
|
|
||||||
|
|
||||||
[wolfram]
|
|
||||||
app_id = API_KEY
|
|
||||||
units = nonmetric
|
|
||||||
|
|
||||||
[movie]
|
|
||||||
tmdb_api_key = API_KEY
|
|
||||||
|
|
||||||
[currency]
|
|
||||||
api_key = API_KEY
|
|
||||||
```
|
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
Fix the movie table
|
Fix the movie table
|
||||||
Consider re-adding the following modules: `etymology, ip`
|
Consider re-adding the following modules: `etymology, ip`
|
||||||
|
|
50
bot.py
50
bot.py
|
@ -5,7 +5,6 @@ The core bot class for Fulvia.
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import functools
|
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
@ -15,35 +14,30 @@ from twisted.words.protocols import irc
|
||||||
|
|
||||||
import db
|
import db
|
||||||
import tools
|
import tools
|
||||||
|
import config
|
||||||
import loader
|
import loader
|
||||||
from trigger import Trigger
|
from trigger import Trigger
|
||||||
|
|
||||||
class Fulvia(irc.IRCClient):
|
class Fulvia(irc.IRCClient):
|
||||||
def __init__(self, config):
|
def __init__(self):
|
||||||
self.config = config
|
self.nickname = config.nickname
|
||||||
"""The bot's config, loaded from file."""
|
self.nick = config.nickname
|
||||||
|
|
||||||
self.nickname = config.core.nickname
|
|
||||||
self.nick = config.core.nickname
|
|
||||||
"""The bot's current nickname."""
|
"""The bot's current nickname."""
|
||||||
|
|
||||||
self.realname = config.core.realname
|
self.realname = config.realname
|
||||||
"""The bot's 'real name', used in whois."""
|
"""The bot's 'real name', used in whois."""
|
||||||
|
|
||||||
self.username = config.core.username
|
self.username = config.username
|
||||||
"""The bot's username ident used for logging into the server."""
|
"""The bot's username ident used for logging into the server."""
|
||||||
|
|
||||||
self.host = ""
|
self.host = ""
|
||||||
"""The bot's host, virtual or otherwise. To be filled in later."""
|
"""The bot's host, virtual or otherwise. To be filled in later."""
|
||||||
|
|
||||||
self.prefix = config.core.prefix
|
self.static = "static"
|
||||||
"""The command prefix the bot watches for."""
|
|
||||||
|
|
||||||
self.static = os.path.join(config.homedir, "static")
|
|
||||||
os.makedirs(self.static, exist_ok=True)
|
os.makedirs(self.static, exist_ok=True)
|
||||||
"""The path to the bot's static file directory."""
|
"""The path to the bot's static file directory."""
|
||||||
|
|
||||||
self.log_path = os.path.join(config.homedir, "logs")
|
self.log_path = "logs"
|
||||||
os.makedirs(self.static, exist_ok=True)
|
os.makedirs(self.static, exist_ok=True)
|
||||||
"""The path to the bot's log files."""
|
"""The path to the bot's log files."""
|
||||||
|
|
||||||
|
@ -68,7 +62,7 @@ class Fulvia(irc.IRCClient):
|
||||||
modules need it.
|
modules need it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.db = db.FulviaDB(self.config)
|
self.db = db.FulviaDB()
|
||||||
"""
|
"""
|
||||||
A class with some basic interactions for the bot's sqlite3 databse.
|
A class with some basic interactions for the bot's sqlite3 databse.
|
||||||
"""
|
"""
|
||||||
|
@ -100,7 +94,7 @@ class Fulvia(irc.IRCClient):
|
||||||
self._user_joined = []
|
self._user_joined = []
|
||||||
"""These get called when a user joins a channel."""
|
"""These get called when a user joins a channel."""
|
||||||
|
|
||||||
self._disabled_modules = self.config.core.disabled_modules.split(",")
|
self._disabled_modules = config.disabled_modules
|
||||||
"""These modules will NOT be loaded when load_modules() is called."""
|
"""These modules will NOT be loaded when load_modules() is called."""
|
||||||
|
|
||||||
self.load_modules()
|
self.load_modules()
|
||||||
|
@ -117,7 +111,7 @@ class Fulvia(irc.IRCClient):
|
||||||
self.url_callbacks = {}
|
self.url_callbacks = {}
|
||||||
# ensure they're empty
|
# ensure they're empty
|
||||||
|
|
||||||
modules = loader.find_modules(self.config.homedir)
|
modules = loader.find_modules()
|
||||||
loaded = 0
|
loaded = 0
|
||||||
failed = 0
|
failed = 0
|
||||||
for name, path in modules.items():
|
for name, path in modules.items():
|
||||||
|
@ -215,7 +209,7 @@ class Fulvia(irc.IRCClient):
|
||||||
"""
|
"""
|
||||||
# TODO: use time module instead of datetime
|
# TODO: use time module instead of datetime
|
||||||
t = datetime.fromtimestamp(time.time())
|
t = datetime.fromtimestamp(time.time())
|
||||||
timestamp = t.strftime(self.config.core.default_time_format)
|
timestamp = t.strftime(config.default_time_format)
|
||||||
self._log_dump[channel].append(timestamp + " " + text)
|
self._log_dump[channel].append(timestamp + " " + text)
|
||||||
|
|
||||||
if time.time() - self._last_log_dump > 1:
|
if time.time() - self._last_log_dump > 1:
|
||||||
|
@ -248,9 +242,9 @@ class Fulvia(irc.IRCClient):
|
||||||
self.log(channel, line)
|
self.log(channel, line)
|
||||||
|
|
||||||
funcs = []
|
funcs = []
|
||||||
if message.startswith(self.prefix) and message != self.prefix:
|
if message.startswith(config.prefix) and message != config.prefix:
|
||||||
command = message.partition(" ")[0]
|
command = message.partition(" ")[0]
|
||||||
command = command.replace(self.prefix, "", 1)
|
command = command.replace(config.prefix, "", 1)
|
||||||
cmd = self.commands.get(command)
|
cmd = self.commands.get(command)
|
||||||
if not cmd:
|
if not cmd:
|
||||||
return
|
return
|
||||||
|
@ -259,7 +253,7 @@ class Fulvia(irc.IRCClient):
|
||||||
funcs += self._hooks
|
funcs += self._hooks
|
||||||
|
|
||||||
for func in funcs:
|
for func in funcs:
|
||||||
trigger = Trigger(user, channel, message, "PRIVMSG", self.config)
|
trigger = Trigger(user, channel, message, "PRIVMSG")
|
||||||
bot = FulviaWrapper(self, trigger)
|
bot = FulviaWrapper(self, trigger)
|
||||||
|
|
||||||
if func.rate:
|
if func.rate:
|
||||||
|
@ -352,8 +346,8 @@ class Fulvia(irc.IRCClient):
|
||||||
|
|
||||||
def signedOn(self):
|
def signedOn(self):
|
||||||
"""Called when the bot successfully connects to the server."""
|
"""Called when the bot successfully connects to the server."""
|
||||||
if self.config.core.oper_password:
|
if config.oper_password:
|
||||||
self.sendLine("OPER " + self.config.core.nickname + ' ' + self.config.core.oper_password)
|
self.sendLine("OPER " + config.nickname + ' ' + config.oper_password)
|
||||||
print(f"Signed on as {self.nickname}")
|
print(f"Signed on as {self.nickname}")
|
||||||
self.whois(self.nickname)
|
self.whois(self.nickname)
|
||||||
|
|
||||||
|
@ -361,7 +355,7 @@ class Fulvia(irc.IRCClient):
|
||||||
line += f"{self.nickname}!{self.username}@{self.host}"
|
line += f"{self.nickname}!{self.username}@{self.host}"
|
||||||
self.log(self.hostname, line)
|
self.log(self.hostname, line)
|
||||||
|
|
||||||
for channel in self.config.core.channels.split(","):
|
for channel in config.channels:
|
||||||
self.join(channel)
|
self.join(channel)
|
||||||
|
|
||||||
|
|
||||||
|
@ -400,7 +394,7 @@ class Fulvia(irc.IRCClient):
|
||||||
self.channels[channel].users[nick] = user
|
self.channels[channel].users[nick] = user
|
||||||
|
|
||||||
for func in self._user_joined:
|
for func in self._user_joined:
|
||||||
trigger = Trigger(user, channel, f"{user} has joined", "PRIVMSG", self.config)
|
trigger = Trigger(user, channel, f"{user} has joined", "PRIVMSG")
|
||||||
bot = FulviaWrapper(self, trigger)
|
bot = FulviaWrapper(self, trigger)
|
||||||
t = threading.Thread(target=self.call,args=(func, bot, trigger))
|
t = threading.Thread(target=self.call,args=(func, bot, trigger))
|
||||||
t.start()
|
t.start()
|
||||||
|
@ -667,8 +661,4 @@ class FulviaWrapper():
|
||||||
|
|
||||||
|
|
||||||
class FulviaFactory(protocol.ReconnectingClientFactory):
|
class FulviaFactory(protocol.ReconnectingClientFactory):
|
||||||
# black magic going on here
|
protocol = Fulvia
|
||||||
protocol = property(lambda s: functools.partial(Fulvia, s.config))
|
|
||||||
|
|
||||||
def __init__(self, config):
|
|
||||||
self.config = config
|
|
||||||
|
|
116
config.py
116
config.py
|
@ -1,116 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
For parsing and generating config files.
|
|
||||||
"""
|
|
||||||
from configparser import ConfigParser
|
|
||||||
|
|
||||||
class Config():
|
|
||||||
def __init__(self, filename):
|
|
||||||
"""
|
|
||||||
The bot's configuration.
|
|
||||||
|
|
||||||
The given filename will be associated with the configuration, and is
|
|
||||||
the file which will be written if write() is called. If load is not
|
|
||||||
given or True, the configuration object will load the attributes from
|
|
||||||
the file at filename.
|
|
||||||
|
|
||||||
A few default values will be set here if they are not defined in the
|
|
||||||
config file, or a config file is not loaded. They are documented below.
|
|
||||||
"""
|
|
||||||
self.filename = filename
|
|
||||||
"""The config object's associated file, as noted above."""
|
|
||||||
|
|
||||||
self.parser = ConfigParser(allow_no_value=True, interpolation=None)
|
|
||||||
self.parser.read(self.filename)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def homedir(self):
|
|
||||||
"""An alias to config.core.homedir"""
|
|
||||||
# Technically it's the other way around, so we can bootstrap filename
|
|
||||||
# attributes in the core section, but whatever.
|
|
||||||
configured = None
|
|
||||||
if self.parser.has_option('core', 'homedir'):
|
|
||||||
configured = self.parser.get('core', 'homedir')
|
|
||||||
if configured:
|
|
||||||
return configured
|
|
||||||
else:
|
|
||||||
return os.path.dirname(self.filename)
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
"""Save all changes to the config file."""
|
|
||||||
with open(self.filename, 'w') as cfgfile:
|
|
||||||
self.parser.write(cfgfile)
|
|
||||||
|
|
||||||
def add_section(self, name):
|
|
||||||
"""
|
|
||||||
Add a section to the config file.
|
|
||||||
|
|
||||||
Returns ``False`` if already exists.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self.parser.add_section(name)
|
|
||||||
except ConfigParser.DuplicateSectionError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
"""Allows sections to be called like class attributes."""
|
|
||||||
if name in self.parser.sections():
|
|
||||||
items = self.parser.items(name)
|
|
||||||
section = ConfigSection(name, items, self) # Return a section
|
|
||||||
setattr(self, name, section)
|
|
||||||
return section
|
|
||||||
else:
|
|
||||||
raise AttributeError("%r object has no attribute %r"
|
|
||||||
% (type(self).__name__, name))
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigSection(object):
|
|
||||||
"""
|
|
||||||
Represents a section of the config file.
|
|
||||||
|
|
||||||
Contains all keys in thesection as attributes.
|
|
||||||
"""
|
|
||||||
def __init__(self, name, items, parent):
|
|
||||||
object.__setattr__(self, '_name', name)
|
|
||||||
object.__setattr__(self, '_parent', parent)
|
|
||||||
for item in items:
|
|
||||||
value = item[1].strip()
|
|
||||||
if not value.lower() == 'none':
|
|
||||||
if value.lower() == 'false':
|
|
||||||
value = False
|
|
||||||
object.__setattr__(self, item[0], value)
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
object.__setattr__(self, name, value)
|
|
||||||
if type(value) is list:
|
|
||||||
value = ','.join(value)
|
|
||||||
self._parent.parser.set(self._name, name, value)
|
|
||||||
|
|
||||||
def get_list(self, name):
|
|
||||||
value = getattr(self, name)
|
|
||||||
if not value:
|
|
||||||
return []
|
|
||||||
if isinstance(value, str):
|
|
||||||
value = value.split(',')
|
|
||||||
# Keep the split value, so we don't have to keep doing this
|
|
||||||
setattr(self, name, value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def readConfig(filename):
|
|
||||||
"""
|
|
||||||
Parses the provided filename and returns the config object.
|
|
||||||
"""
|
|
||||||
config = ConfigParser(allow_no_value=True, interpolation=None)
|
|
||||||
config.read(filename)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def generateConfig(filename):
|
|
||||||
"""
|
|
||||||
Generates a blank config file with minimal defaults.
|
|
||||||
"""
|
|
||||||
pass
|
|
33
config.template.py
Executable file
33
config.template.py
Executable file
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
The bot's config file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
""" Core """
|
||||||
|
nickname = 'DiceBot9002'
|
||||||
|
realname = 'DiceBot9002'
|
||||||
|
username = 'DiceBot9002'
|
||||||
|
prefix = '.'
|
||||||
|
server = 'irc.steelbea.me'
|
||||||
|
port = 6667
|
||||||
|
use_ssl = False
|
||||||
|
channels = ['#test', '#SomaIsGay']
|
||||||
|
db_filename = 'DiceBot9002.db'
|
||||||
|
owner = 'iou1name!~iou1name@operational.operator'
|
||||||
|
admins = []
|
||||||
|
default_time_format = '[%Y-%m-%d %H:%M:%S]'
|
||||||
|
disabled_modules = ['countdown']
|
||||||
|
oper_password = 'password'
|
||||||
|
|
||||||
|
""" Wolfram """
|
||||||
|
wolfram_app_id = 'password'
|
||||||
|
wolfram_units = 'nonmetric'
|
||||||
|
|
||||||
|
""" Movie """
|
||||||
|
tmdb_api_key = 'password'
|
||||||
|
|
||||||
|
""" Currency """
|
||||||
|
exchangerate_api_key = 'password'
|
||||||
|
|
||||||
|
""" Crypto """
|
||||||
|
coinlib_api_key = 'password'
|
8
db.py
8
db.py
|
@ -6,13 +6,15 @@ import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
class FulviaDB(object):
|
import config
|
||||||
|
|
||||||
|
class FulviaDB:
|
||||||
"""
|
"""
|
||||||
Defines a basic interface and some convenience functionsfor the bot's
|
Defines a basic interface and some convenience functionsfor the bot's
|
||||||
database.
|
database.
|
||||||
"""
|
"""
|
||||||
def __init__(self, config):
|
def __init__(self):
|
||||||
path = config.core.db_filename
|
path = config.db_filename
|
||||||
self.filename = path
|
self.filename = path
|
||||||
self.db_lock = threading.Lock()
|
self.db_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
17
loader.py
17
loader.py
|
@ -14,7 +14,7 @@ def load_module(bot, path):
|
||||||
module = importlib.import_module(path)
|
module = importlib.import_module(path)
|
||||||
if hasattr(module, 'setup'):
|
if hasattr(module, 'setup'):
|
||||||
module.setup(bot)
|
module.setup(bot)
|
||||||
relevant_parts = process_module(module, bot.config)
|
relevant_parts = process_module(module)
|
||||||
for part in relevant_parts:
|
for part in relevant_parts:
|
||||||
bot.register_callable(part)
|
bot.register_callable(part)
|
||||||
|
|
||||||
|
@ -34,14 +34,13 @@ def unload_module(bot, name):
|
||||||
del sys.modules[name]
|
del sys.modules[name]
|
||||||
|
|
||||||
|
|
||||||
def find_modules(homedir):
|
def find_modules():
|
||||||
"""
|
"""
|
||||||
Searches through homedir/modules for python files and returns a dictionary
|
Searches through 'modules/' for python files and returns a dictionary
|
||||||
with the module name as the key and the path as the value.
|
with the module name as the key and the path as the value.
|
||||||
"""
|
"""
|
||||||
modules_dir = os.path.join(homedir, "modules")
|
|
||||||
modules = {}
|
modules = {}
|
||||||
for file in os.listdir(modules_dir):
|
for file in os.listdir('modules'):
|
||||||
if not file.endswith(".py"):
|
if not file.endswith(".py"):
|
||||||
continue
|
continue
|
||||||
name = file.replace(".py", "")
|
name = file.replace(".py", "")
|
||||||
|
@ -49,7 +48,7 @@ def find_modules(homedir):
|
||||||
return modules
|
return modules
|
||||||
|
|
||||||
|
|
||||||
def process_module(module, config):
|
def process_module(module):
|
||||||
"""
|
"""
|
||||||
Takes a module object and extracts relevant data objects out of it.
|
Takes a module object and extracts relevant data objects out of it.
|
||||||
Returns all callables(read: functions) and shutdowns(?).
|
Returns all callables(read: functions) and shutdowns(?).
|
||||||
|
@ -58,7 +57,7 @@ def process_module(module, config):
|
||||||
for key, obj in dict.items(vars(module)):
|
for key, obj in dict.items(vars(module)):
|
||||||
if callable(obj):
|
if callable(obj):
|
||||||
if is_triggerable(obj):
|
if is_triggerable(obj):
|
||||||
process_callable(obj, config)
|
process_callable(obj)
|
||||||
callables.append(obj)
|
callables.append(obj)
|
||||||
return callables
|
return callables
|
||||||
|
|
||||||
|
@ -72,12 +71,10 @@ def is_triggerable(obj):
|
||||||
return any(hasattr(obj, attr) for attr in triggerable_attributes)
|
return any(hasattr(obj, attr) for attr in triggerable_attributes)
|
||||||
|
|
||||||
|
|
||||||
def process_callable(func, config):
|
def process_callable(func):
|
||||||
"""
|
"""
|
||||||
Sets various helper atributes about a given function.
|
Sets various helper atributes about a given function.
|
||||||
"""
|
"""
|
||||||
prefix = config.core.prefix
|
|
||||||
|
|
||||||
func.thread = getattr(func, "thread", True)
|
func.thread = getattr(func, "thread", True)
|
||||||
func.hook = getattr(func, "hook", False)
|
func.hook = getattr(func, "hook", False)
|
||||||
func.rate = getattr(func, "rate", 0)
|
func.rate = getattr(func, "rate", 0)
|
||||||
|
|
|
@ -15,6 +15,6 @@ def BQstatus(bot, trigger):
|
||||||
status = "\x0304DEAD"
|
status = "\x0304DEAD"
|
||||||
deathdate = "[2017-02-16 00:19:00]"
|
deathdate = "[2017-02-16 00:19:00]"
|
||||||
msg = "Banished Quest status: " + status + "\nTime since death: "
|
msg = "Banished Quest status: " + status + "\nTime since death: "
|
||||||
msg += relativeTime(bot.config, datetime.now(), deathdate) + " ago "
|
msg += relativeTime(datetime.now(), deathdate) + " ago "
|
||||||
msg += deathdate
|
msg += deathdate
|
||||||
bot.msg(msg)
|
bot.msg(msg)
|
||||||
|
|
|
@ -27,6 +27,6 @@ def generic_countdown(bot, trigger):
|
||||||
except:
|
except:
|
||||||
return bot.msg("Please use correct format: .countdown 2012 12 21")
|
return bot.msg("Please use correct format: .countdown 2012 12 21")
|
||||||
|
|
||||||
msg = relativeTime(bot.config, datetime.now(), date)
|
msg = relativeTime(datetime.now(), date)
|
||||||
msg += " until " + trigger.group(2)
|
msg += " until " + trigger.group(2)
|
||||||
bot.msg(msg)
|
bot.msg(msg)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import re
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
import config
|
||||||
from module import commands, example, require_admin
|
from module import commands, example, require_admin
|
||||||
|
|
||||||
URI = "https://coinlib.io/api/v1"
|
URI = "https://coinlib.io/api/v1"
|
||||||
|
@ -19,7 +20,7 @@ def crypto(bot, trigger):
|
||||||
Queries coinlib.io for information about various crytocurrencies.
|
Queries coinlib.io for information about various crytocurrencies.
|
||||||
"""
|
"""
|
||||||
params = {
|
params = {
|
||||||
"key": bot.config.crypto.api_key,
|
"key": config.coinlib_api_key,
|
||||||
"pref": "USD",
|
"pref": "USD",
|
||||||
}
|
}
|
||||||
symbol = trigger.group(3)
|
symbol = trigger.group(3)
|
||||||
|
|
|
@ -33,7 +33,7 @@ def exchange(bot, trigger):
|
||||||
cur_to = cur_to.upper()
|
cur_to = cur_to.upper()
|
||||||
cur_from = cur_from.upper()
|
cur_from = cur_from.upper()
|
||||||
|
|
||||||
api_key = bot.config.currency.api_key
|
api_key = config.exchangerate_api_key
|
||||||
url = CUR_URI.format(**{"API_KEY": api_key, "CUR_FROM": cur_from})
|
url = CUR_URI.format(**{"API_KEY": api_key, "CUR_FROM": cur_from})
|
||||||
res = requests.get(url, verify=True)
|
res = requests.get(url, verify=True)
|
||||||
res.raise_for_status()
|
res.raise_for_status()
|
||||||
|
|
|
@ -11,6 +11,7 @@ from sqlite3 import IntegrityError, OperationalError
|
||||||
import bs4
|
import bs4
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
import config
|
||||||
from module import commands, example, require_admin
|
from module import commands, example, require_admin
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
|
@ -50,7 +51,7 @@ def movieInfo(bot, trigger):
|
||||||
return bot.reply("What movie?")
|
return bot.reply("What movie?")
|
||||||
word = word.replace(" ", "+")
|
word = word.replace(" ", "+")
|
||||||
|
|
||||||
api_key = bot.config.movie.tmdb_api_key
|
api_key = config.tmdb_api_key
|
||||||
uri = "https://api.themoviedb.org/3/search/movie?" + \
|
uri = "https://api.themoviedb.org/3/search/movie?" + \
|
||||||
f"api_key={api_key}&query={word}"
|
f"api_key={api_key}&query={word}"
|
||||||
data = requests.get(uri, timeout=30, verify=True).json()
|
data = requests.get(uri, timeout=30, verify=True).json()
|
||||||
|
@ -151,7 +152,7 @@ def pickMovie(bot, trigger):
|
||||||
bot.reply(movie[0])
|
bot.reply(movie[0])
|
||||||
|
|
||||||
if trigger.group(2) == "-m":
|
if trigger.group(2) == "-m":
|
||||||
trigger.set_group(f".movie {movie}", bot.config)
|
trigger.set_group(f".movie {movie}")
|
||||||
movieInfo(bot, trigger)
|
movieInfo(bot, trigger)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import datetime
|
||||||
import threading
|
import threading
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
|
import config
|
||||||
from module import commands, example
|
from module import commands, example
|
||||||
|
|
||||||
class MonitorThread(threading.Thread):
|
class MonitorThread(threading.Thread):
|
||||||
|
@ -213,7 +214,7 @@ def create_reminder(bot, trigger, duration, message):
|
||||||
|
|
||||||
if duration >= 60:
|
if duration >= 60:
|
||||||
remind_at = datetime.datetime.fromtimestamp(t)
|
remind_at = datetime.datetime.fromtimestamp(t)
|
||||||
t_format = bot.config.core.default_time_format
|
t_format = config.default_time_format
|
||||||
timef = datetime.datetime.strftime(remind_at, t_format)
|
timef = datetime.datetime.strftime(remind_at, t_format)
|
||||||
|
|
||||||
bot.reply('Okay, will remind at %s' % timef)
|
bot.reply('Okay, will remind at %s' % timef)
|
||||||
|
|
|
@ -73,7 +73,7 @@ def findandreplace(bot, trigger):
|
||||||
if not group:
|
if not group:
|
||||||
return
|
return
|
||||||
g = (trigger.group(0),) + group.groups()
|
g = (trigger.group(0),) + group.groups()
|
||||||
trigger.set_group(g, bot.config)
|
trigger.set_group(g)
|
||||||
|
|
||||||
# Correcting other person vs self.
|
# Correcting other person vs self.
|
||||||
rnick = (trigger.group(1) or trigger.nick)
|
rnick = (trigger.group(1) or trigger.nick)
|
||||||
|
|
|
@ -11,6 +11,7 @@ from sqlite3 import OperationalError
|
||||||
|
|
||||||
from requests.structures import CaseInsensitiveDict
|
from requests.structures import CaseInsensitiveDict
|
||||||
|
|
||||||
|
import config
|
||||||
from tools.time import relativeTime
|
from tools.time import relativeTime
|
||||||
from module import commands, example, hook, require_chanmsg, rate
|
from module import commands, example, hook, require_chanmsg, rate
|
||||||
|
|
||||||
|
@ -87,9 +88,9 @@ def seen(bot, trigger):
|
||||||
return bot.msg(f"I haven't seen \x0308{args.nick}")
|
return bot.msg(f"I haven't seen \x0308{args.nick}")
|
||||||
|
|
||||||
timestamp = datetime.fromtimestamp(timestamp)
|
timestamp = datetime.fromtimestamp(timestamp)
|
||||||
t_format = bot.config.core.default_time_format
|
t_format = config.default_time_format
|
||||||
timestamp = datetime.strftime(timestamp, t_format)
|
timestamp = datetime.strftime(timestamp, t_format)
|
||||||
reltime = relativeTime(bot.config, datetime.now(), timestamp)
|
reltime = relativeTime(datetime.now(), timestamp)
|
||||||
|
|
||||||
if args.first:
|
if args.first:
|
||||||
msg = "First"
|
msg = "First"
|
||||||
|
|
|
@ -9,6 +9,7 @@ import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlite3 import OperationalError
|
from sqlite3 import OperationalError
|
||||||
|
|
||||||
|
import config
|
||||||
from tools.time import relativeTime
|
from tools.time import relativeTime
|
||||||
from module import commands, example, hook
|
from module import commands, example, hook
|
||||||
|
|
||||||
|
@ -107,8 +108,8 @@ def tell_hook(bot, trigger):
|
||||||
teller, unixtime, message = tell
|
teller, unixtime, message = tell
|
||||||
|
|
||||||
telldate = datetime.fromtimestamp(unixtime)
|
telldate = datetime.fromtimestamp(unixtime)
|
||||||
reltime = relativeTime(bot.config, datetime.now(), telldate)
|
reltime = relativeTime(datetime.now(), telldate)
|
||||||
t_format = bot.config.core.default_time_format
|
t_format = config.default_time_format
|
||||||
telldate = datetime.strftime(telldate, t_format)
|
telldate = datetime.strftime(telldate, t_format)
|
||||||
|
|
||||||
msg = f"{tellee}: \x0310{message}\x03 (\x0308{teller}\x03) {telldate}" \
|
msg = f"{tellee}: \x0310{message}\x03 (\x0308{teller}\x03) {telldate}" \
|
||||||
|
|
|
@ -4,9 +4,9 @@ Querying Wolfram Alpha.
|
||||||
"""
|
"""
|
||||||
import wolframalpha
|
import wolframalpha
|
||||||
|
|
||||||
|
import config
|
||||||
from module import commands, example
|
from module import commands, example
|
||||||
|
|
||||||
|
|
||||||
@commands('wa', 'wolfram')
|
@commands('wa', 'wolfram')
|
||||||
@example('.wa 2+2', '[W|A] 2+2 = 4')
|
@example('.wa 2+2', '[W|A] 2+2 = 4')
|
||||||
@example(".wa python language release date",
|
@example(".wa python language release date",
|
||||||
|
@ -17,11 +17,11 @@ def wa_command(bot, trigger):
|
||||||
"""
|
"""
|
||||||
if not trigger.group(2):
|
if not trigger.group(2):
|
||||||
return bot.reply("You must provide a query.")
|
return bot.reply("You must provide a query.")
|
||||||
if not bot.config.wolfram.app_id:
|
if not config.wolfram_app_id:
|
||||||
bot.reply("Wolfram|Alpha API app ID not configured.")
|
bot.reply("Wolfram|Alpha API app ID not configured.")
|
||||||
query = trigger.group(2).strip()
|
query = trigger.group(2).strip()
|
||||||
app_id = bot.config.wolfram.app_id
|
app_id = config.wolfram_app_id
|
||||||
units = bot.config.wolfram.units
|
units = config.wolfram_units
|
||||||
|
|
||||||
res = wa_query(query, app_id, units)
|
res = wa_query(query, app_id, units)
|
||||||
|
|
||||||
|
|
23
run.py
23
run.py
|
@ -6,26 +6,11 @@ import os
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
|
|
||||||
|
import config
|
||||||
from bot import FulviaFactory
|
from bot import FulviaFactory
|
||||||
from config import Config
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
server = config.server
|
||||||
|
port = config.port
|
||||||
parser = argparse.ArgumentParser(
|
reactor.connectTCP(server, port, FulviaFactory())
|
||||||
description="Fulvia IRC Bot")
|
|
||||||
parser.add_argument(
|
|
||||||
"-c",
|
|
||||||
"--config",
|
|
||||||
help="Use a specific config file.")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not args.config:
|
|
||||||
args.config = "default.cfg"
|
|
||||||
|
|
||||||
config = Config(args.config)
|
|
||||||
|
|
||||||
server = config.core.server
|
|
||||||
port = int(config.core.port)
|
|
||||||
reactor.connectTCP(server, port, FulviaFactory(config))
|
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|
|
@ -6,15 +6,17 @@ from datetime import datetime
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
import config
|
||||||
|
|
||||||
def relativeTime(config, time_1, time_2):
|
|
||||||
|
def relativeTime(time_1, time_2):
|
||||||
"""
|
"""
|
||||||
Returns the relative time difference between 'time_1' and 'time_2'.
|
Returns the relative time difference between 'time_1' and 'time_2'.
|
||||||
If either 'time_1' or 'time_2' is a string, it will be converted to a
|
If either 'time_1' or 'time_2' is a string, it will be converted to a
|
||||||
datetime object according to the 'default_time_format' variable in the
|
datetime object according to the 'default_time_format' variable in the
|
||||||
config.
|
config.
|
||||||
"""
|
"""
|
||||||
t_format = config.core.default_time_format
|
t_format = config.default_time_format
|
||||||
if type(time_1) == str:
|
if type(time_1) == str:
|
||||||
time_1 = datetime.strptime(time_1, t_format)
|
time_1 = datetime.strptime(time_1, t_format)
|
||||||
if type(time_2) == str:
|
if type(time_2) == str:
|
||||||
|
|
23
trigger.py
23
trigger.py
|
@ -4,6 +4,8 @@ The trigger abstraction layer.
|
||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
import config
|
||||||
|
|
||||||
def split_user(user):
|
def split_user(user):
|
||||||
"""
|
"""
|
||||||
Splits a user hostmask into <nick>!<ident>@<host>
|
Splits a user hostmask into <nick>!<ident>@<host>
|
||||||
|
@ -21,14 +23,14 @@ class Group(list):
|
||||||
Custom list class that permits calling it like a function so as to
|
Custom list class that permits calling it like a function so as to
|
||||||
emulate a re.group instance.
|
emulate a re.group instance.
|
||||||
"""
|
"""
|
||||||
def __init__(self, message, config):
|
def __init__(self, message):
|
||||||
"""
|
"""
|
||||||
Initializes the group class. If 'message' is a string, we split
|
Initializes the group class. If 'message' is a string, we split
|
||||||
it into groups according to the usual trigger.group structure.
|
it into groups according to the usual trigger.group structure.
|
||||||
Otherwise we assume it's already been split appropriately.
|
Otherwise we assume it's already been split appropriately.
|
||||||
"""
|
"""
|
||||||
if type(message) == str:
|
if type(message) == str:
|
||||||
message = self.split_group(message, config)
|
message = self.split_group(message)
|
||||||
list.__init__(self, message)
|
list.__init__(self, message)
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ class Group(list):
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
def split_group(self, message, config):
|
def split_group(self, message):
|
||||||
"""
|
"""
|
||||||
Splits the message by spaces.
|
Splits the message by spaces.
|
||||||
group(0) is always the entire message.
|
group(0) is always the entire message.
|
||||||
|
@ -54,11 +56,10 @@ class Group(list):
|
||||||
group(2) is always the entire message after the first word.
|
group(2) is always the entire message after the first word.
|
||||||
group(3+) is always every individual word after the first word.
|
group(3+) is always every individual word after the first word.
|
||||||
"""
|
"""
|
||||||
prefix = config.core.prefix
|
|
||||||
group = []
|
group = []
|
||||||
group.append(message)
|
group.append(message)
|
||||||
words = message.split()
|
words = message.split()
|
||||||
group.append(words[0].replace(prefix, "", 1))
|
group.append(words[0].replace(config.prefix, "", 1))
|
||||||
group.append(" ".join(words[1:]))
|
group.append(" ".join(words[1:]))
|
||||||
group += words[1:]
|
group += words[1:]
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ class Group(list):
|
||||||
|
|
||||||
|
|
||||||
class Trigger():
|
class Trigger():
|
||||||
def __init__(self, user, channel, message, event, config):
|
def __init__(self, user, channel, message, event):
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
"""
|
"""
|
||||||
The channel from which the message was sent.
|
The channel from which the message was sent.
|
||||||
|
@ -112,7 +113,7 @@ class Trigger():
|
||||||
message.
|
message.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.group = Group(message, config)
|
self.group = Group(message)
|
||||||
"""The ``group`` function of the ``match`` attribute.
|
"""The ``group`` function of the ``match`` attribute.
|
||||||
|
|
||||||
See Python :mod:`re` documentation for details."""
|
See Python :mod:`re` documentation for details."""
|
||||||
|
@ -125,14 +126,14 @@ class Trigger():
|
||||||
``('#example', '-m')``
|
``('#example', '-m')``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
admins = config.core.admins.split(",") + [config.core.owner]
|
admins = config.admins + [config.owner]
|
||||||
self.admin = any([self.compare_hostmask(admin) for admin in admins])
|
self.admin = any([self.compare_hostmask(admin) for admin in admins])
|
||||||
"""
|
"""
|
||||||
True if the nick which triggered the command is one of the bot's
|
True if the nick which triggered the command is one of the bot's
|
||||||
admins.
|
admins.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.owner = self.compare_hostmask(config.core.owner)
|
self.owner = self.compare_hostmask(config.owner)
|
||||||
"""True if the nick which triggered the command is the bot's owner."""
|
"""True if the nick which triggered the command is the bot's owner."""
|
||||||
|
|
||||||
|
|
||||||
|
@ -147,9 +148,9 @@ class Trigger():
|
||||||
return compare_against == "@".join(self.nick, self.host)
|
return compare_against == "@".join(self.nick, self.host)
|
||||||
|
|
||||||
|
|
||||||
def set_group(self, line, config):
|
def set_group(self, line):
|
||||||
"""
|
"""
|
||||||
Allows a you to easily change the current group to a new Group
|
Allows a you to easily change the current group to a new Group
|
||||||
instance.
|
instance.
|
||||||
"""
|
"""
|
||||||
self.group = Group(line, config)
|
self.group = Group(line)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user