diff --git a/bot/__init__.py b/bot/__init__.py index ea52ec8..15c3498 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -1,4 +1,56 @@ # -*- coding: utf-8 -*- import os +import json +import logging -REPO_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +import irc3 +from dotenv import load_dotenv + +MODULE = __name__ + + +# TODO: ddg +class Bot(irc3.IrcBot): + DEV = 'BOT_DEV' in os.environ + REPO_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + CONFIG_DIR = REPO_DIR if DEV else os.path.dirname(REPO_DIR) + + @classmethod + def from_json(cls, cfg_file: str = 'config.json', env_file: str = '.env'): + cfg_file = os.path.join(cls.CONFIG_DIR, cfg_file) + env_file = os.path.join(cls.CONFIG_DIR, env_file) + + load_dotenv('.env') + + with open(cfg_file, 'r') as fp: + conf = json.load(fp) + + return cls.from_config(conf) + + +@irc3.plugin +class BasePlugin: + requires = ['irc3.plugins.command'] + + def __init__(self, bot: Bot): + self.bot = bot + self.log = logging.getLogger('irc3.{}'.format(self.__class__.__name__.lower())) + + +class Plugin(BasePlugin): + @classmethod + def reload(cls, old: BasePlugin): + return cls(old.bot) + + +from .storage import Storage # noqa + + +class DatabasePlugin(Plugin): + requires = Plugin.requires + ['bot.storage'] + + def __init__(self, bot: Bot): + super().__init__(bot) + self.db = bot.get_plugin(Storage) + self.con = self.db.con + self.cur = self.db.cur diff --git a/bot/__main__.py b/bot/__main__.py index 29068e0..e96359a 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,51 +1,8 @@ # -*- coding: utf-8 -*- -import os import sys -import json - -# noinspection PyPackageRequirements -from dotenv import load_dotenv -from irc3 import IrcBot - -# This is a development config for use with irc3s server -CFG_DEV = { - 'nick': 'nxy', - 'autojoins': ['#dev'], - 'host': 'localhost', - 'port': 6667, - 'ssl': False, - 'raw': True, - 'debug': True, - 'verbose': True, - 'irc3.plugins.command.masks': { - '*!admin@127.0.0.1': 'all_permissions', - '*': 'view', - } -} - - -# TODO: ddg -def main(cfg_file): - # Load dotenv from file - load_dotenv('.env') - - # Load config from json file - with open(cfg_file, 'r') as fp: - cfg = json.load(fp) - - # Apply dev config if env variable is set - if bool(os.environ.get('DEV')): - cfg.update(CFG_DEV) - # If PASSWORD in env set it in config - if 'PASSWORD' in os.environ: - cfg['password'] = os.environ['PASSWORD'] - - # Start the bot with constructed config - bot = IrcBot.from_config(cfg) - - bot.run() - bot.mode(bot.nick, '+R') +from bot import Bot if __name__ == '__main__': - main(sys.argv[1]) + bot = Bot.from_json(*sys.argv[1:]) + bot.run() diff --git a/bot/plugins/admin.py b/bot/admin.py similarity index 74% rename from bot/plugins/admin.py rename to bot/admin.py index 63c2ede..8fae595 100644 --- a/bot/plugins/admin.py +++ b/bot/admin.py @@ -7,14 +7,13 @@ from docopt import Dict from irc3.plugins.command import command from irc3.utils import IrcString -from . import MODULE, Plugin -from .. import REPO_DIR +from . import MODULE, Bot, Plugin logger = logging.getLogger(__name__) @command(permission='admin', show_in_help_list=False) -def reload(bot: irc3.IrcBot, mask: IrcString, target: IrcString, args: Dict): +def reload(bot: Bot, mask: IrcString, target: IrcString, args: Dict): """Reloads a plugin or the whole bot. %%reload [] @@ -34,9 +33,9 @@ def connected(bot, srv, me, data): class Admin(Plugin): - def __init__(self, bot: irc3.IrcBot): + def __init__(self, bot: Bot): super().__init__(bot) - self.repo = Repo(REPO_DIR) + self.repo = Repo(self.bot.REPO_DIR) @command(permission='all_permissions') def pull(self, mask: IrcString, target: IrcString, args: Dict): @@ -68,7 +67,10 @@ class Admin(Plugin): %%join """ - self.join_part('join', mask, target, args) + channel = args.get('', target) + + self.bot.join(channel) + self.bot.notice(mask.nick, 'Joined channel {}'.format(channel)) @command(permission='all_permissions') def part(self, mask: IrcString, target: IrcString, args: Dict): @@ -76,7 +78,10 @@ class Admin(Plugin): %%part [] """ - self.join_part('part', mask, target, args) + channel = args.get('', target) + + self.bot.join(channel) + self.bot.notice(mask.nick, 'Parted channel {}'.format(channel)) @command(permission='all_permissions') def cycle(self, mask: IrcString, target: IrcString, args: Dict): @@ -84,12 +89,8 @@ class Admin(Plugin): %%cycle [] """ - self.join_part('part', mask, target, args) - self.join_part('join', mask, target, args) + channel = args.get('', target) - def join_part(self, func: str, mask: IrcString, target: IrcString, args: Dict): - channel = IrcString(args.get('') or target) - - if channel.is_channel: - getattr(self.bot, func)(channel) - self.bot.notice(mask.nick, '{}ed channel {}'.format(func, channel)) + self.bot.part(channel) + self.bot.join(channel) + self.bot.notice(mask.nick, 'Cycled channel {}'.format(channel)) diff --git a/bot/plugins/coins.py b/bot/coins.py similarity index 98% rename from bot/plugins/coins.py rename to bot/coins.py index d76cc9e..de3263f 100644 --- a/bot/plugins/coins.py +++ b/bot/coins.py @@ -8,8 +8,6 @@ from . import Plugin class Coins(Plugin): - requires = ['irc3.plugins.command'] - API_URL = 'https://api.cryptowat.ch/markets/coinbase/{crypto}{currency}/summary' CURRENCIES = { 'usd': '$', diff --git a/bot/plugins/ctcp.py b/bot/ctcp.py similarity index 96% rename from bot/plugins/ctcp.py rename to bot/ctcp.py index eb883b4..156c452 100644 --- a/bot/plugins/ctcp.py +++ b/bot/ctcp.py @@ -9,28 +9,10 @@ from . import Plugin class CTCP(Plugin): - requires = ['irc3.plugins.async', - 'irc3.plugins.command'] + requires = Plugin.requires + ['irc3.plugins.async'] TIMEOUT = 5 - # noinspection PyMethodMayBeStatic - def _ctcp(self, name: str, nick: str, reply: str): - return '\x02[{}]\x02 {}: {}'.format(name.upper(), nick, reply) - - async def ctcp(self, name: str, mask: IrcString, args: Dict): - nick = args.get('', mask.nick) - data = await self.bot.ctcp_async(nick, name.upper(), self.TIMEOUT) - - if not data or data['timeout']: - reply = 'timeout' - elif not data['success']: - reply = 'Error: {}'.format(data['reply']) - else: - reply = data['reply'] - - return self._ctcp(name, nick, reply) - @command async def ping(self, mask: IrcString, target: IrcString, args: Dict): """Sends ping via CTCP to user and sends the time needed @@ -70,3 +52,20 @@ class CTCP(Plugin): %%ver [] """ return await self.ctcp('version', mask, args) + + async def ctcp(self, name: str, mask: IrcString, args: Dict): + nick = args.get('', mask.nick) + data = await self.bot.ctcp_async(nick, name.upper(), self.TIMEOUT) + + if not data or data['timeout']: + reply = 'timeout' + elif not data['success']: + reply = 'Error: {}'.format(data['reply']) + else: + reply = data['reply'] + + return self._ctcp(name, nick, reply) + + # noinspection PyMethodMayBeStatic + def _ctcp(self, name: str, nick: str, reply: str): + return '\x02[{}]\x02 {}: {}'.format(name.upper(), nick, reply) diff --git a/bot/plugins/isup.py b/bot/isup.py similarity index 84% rename from bot/plugins/isup.py rename to bot/isup.py index da30685..d317b65 100644 --- a/bot/plugins/isup.py +++ b/bot/isup.py @@ -6,13 +6,10 @@ from docopt import Dict from irc3.plugins.command import command from irc3.utils import IrcString -from . import DatabasePlugin +from . import Plugin -class Useless(DatabasePlugin): - requires = ['irc3.plugins.command', - 'bot.plugins.storage'] - +class Useless(Plugin): @command def isup(self, mask: IrcString, target: IrcString, args: Dict): """Checks if a address is up or down diff --git a/bot/plugins/linux.py b/bot/linux.py similarity index 98% rename from bot/plugins/linux.py rename to bot/linux.py index 16de803..d3a573e 100644 --- a/bot/plugins/linux.py +++ b/bot/linux.py @@ -8,7 +8,7 @@ from irc3.plugins.command import command from irc3.utils import IrcString from . import Plugin -from ..utils import re_generator +from .utils import re_generator GNU_LINUX = """I'd Just Like To Interject For A Moment. What you're referring to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, diff --git a/bot/plugins/mcmaniac.py b/bot/mcmaniac.py similarity index 94% rename from bot/plugins/mcmaniac.py rename to bot/mcmaniac.py index 3dae18c..d68a55a 100644 --- a/bot/plugins/mcmaniac.py +++ b/bot/mcmaniac.py @@ -6,13 +6,10 @@ from irc3.utils import IrcString from psycopg2 import Error from . import DatabasePlugin -from ..utils import parse_int +from .utils import parse_int class McManiac(DatabasePlugin): - requires = ['irc3.plugins.command', - 'bot.plugins.storage'] - @command(options_first=True) def mcmaniac(self, mask: IrcString, target: IrcString, args: Dict): """Get a random McManiaC or by index. diff --git a/bot/plugins/__init__.py b/bot/plugins/__init__.py deleted file mode 100644 index e29b967..0000000 --- a/bot/plugins/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -import irc3 -from irc3.plugins.command import Commands - -MODULE = __name__ - - -@irc3.plugin -class BasePlugin(object): - def __init__(self, bot: irc3.IrcBot): - self.bot = bot - self.log = bot.log - self.guard = bot.get_plugin(Commands).guard - - -class Plugin(BasePlugin): - @classmethod - def reload(cls, old: BasePlugin): - return cls(old.bot) - - -# Import the PgSQL storage plugin -from .storage import Storage # noqa - - -class DatabasePlugin(Plugin): - def __init__(self, bot: irc3.IrcBot): - super().__init__(bot) - # Get PgSQL storage instance and connection + cursor - self.db = bot.get_plugin(Storage) - self.con = self.db.con - self.cur = self.db.cur diff --git a/bot/plugins/quotes.py b/bot/quotes.py similarity index 93% rename from bot/plugins/quotes.py rename to bot/quotes.py index b12f318..214d6f7 100644 --- a/bot/plugins/quotes.py +++ b/bot/quotes.py @@ -2,17 +2,18 @@ import re from docopt import Dict -from irc3.plugins.command import command +from irc3.plugins.command import Commands, command from irc3.utils import IrcString from psycopg2 import Error -from . import DatabasePlugin -from ..utils import parse_int +from . import DatabasePlugin, Bot +from .utils import parse_int class Quotes(DatabasePlugin): - requires = ['irc3.plugins.command', - 'bot.plugins.storage'] + def __init__(self, bot: Bot): + super().__init__(bot) + self.guard = bot.get_plugin(Commands).guard def add_quote(self, mask: IrcString, nick: str, quote: str, channel: IrcString): # Parse nick from "<@foobar>" like strings @@ -51,7 +52,7 @@ class Quotes(DatabasePlugin): quotes WHERE id = ( - SELECT + SELECT id FROM ranked_quotes diff --git a/bot/plugins/rape.py b/bot/rape.py similarity index 96% rename from bot/plugins/rape.py rename to bot/rape.py index 78a133c..6cb25b2 100644 --- a/bot/plugins/rape.py +++ b/bot/rape.py @@ -9,9 +9,6 @@ from . import DatabasePlugin class Rape(DatabasePlugin): - requires = ['irc3.plugins.command', - 'bot.plugins.storage'] - @command def owe(self, mask: IrcString, target: IrcString, args: Dict): """Shows how much a nick owes diff --git a/bot/plugins/regex.py b/bot/regex.py similarity index 95% rename from bot/plugins/regex.py rename to bot/regex.py index ab74c2a..b9f86ff 100644 --- a/bot/plugins/regex.py +++ b/bot/regex.py @@ -8,8 +8,7 @@ from . import DatabasePlugin class Useless(DatabasePlugin): - requires = ['irc3.plugins.command', - 'bot.plugins.storage'] + requires = [] @irc3.event(r'^:(?P\S+) PRIVMSG (?P#\S+) :s/' r'(?P(?:[^/\\]|\\.)*)/' diff --git a/bot/plugins/seen.py b/bot/seen.py similarity index 95% rename from bot/plugins/seen.py rename to bot/seen.py index 5daf837..7ca66ff 100644 --- a/bot/plugins/seen.py +++ b/bot/seen.py @@ -10,9 +10,6 @@ from . import DatabasePlugin class Seen(DatabasePlugin): - requires = ['irc3.plugins.command', - 'bot.plugins.storage'] - @command def seen(self, mask: IrcString, target: IrcString, args: Dict): """Get last seen date and message for a nick diff --git a/bot/plugins/storage.py b/bot/storage.py similarity index 59% rename from bot/plugins/storage.py rename to bot/storage.py index 871dc08..9c12eeb 100644 --- a/bot/plugins/storage.py +++ b/bot/storage.py @@ -1,17 +1,15 @@ # -*- coding: utf-8 -*- import os -import irc3 import psycopg2 from psycopg2.extras import DictCursor -from . import Plugin +from . import Plugin, Bot class Storage(Plugin): - def __init__(self, bot: irc3.IrcBot): + def __init__(self, bot: Bot): super().__init__(bot) - # Create database connection + self.bot.sql = self self.con = psycopg2.connect(os.environ['DATABASE_URI']) - # Create database cursor (with dict factory to access rows by name) self.cur = self.con.cursor(cursor_factory=DictCursor) diff --git a/bot/plugins/tell.py b/bot/tell.py similarity index 93% rename from bot/plugins/tell.py rename to bot/tell.py index 22e1d39..bdb52b7 100644 --- a/bot/plugins/tell.py +++ b/bot/tell.py @@ -7,14 +7,11 @@ from irc3.plugins.command import command from irc3.utils import IrcString from psycopg2 import Error -from . import DatabasePlugin +from . import DatabasePlugin, Bot class Tell(DatabasePlugin): - requires = ['irc3.plugins.command', - 'bot.plugins.storage'] - - def __init__(self, bot: irc3.IrcBot): + def __init__(self, bot: Bot): super().__init__(bot) self.tell_queue = {} diff --git a/bot/plugins/timer.py b/bot/timer.py similarity index 94% rename from bot/plugins/timer.py rename to bot/timer.py index b818e17..35d3bbd 100644 --- a/bot/plugins/timer.py +++ b/bot/timer.py @@ -3,7 +3,6 @@ import re import asyncio from datetime import datetime -import irc3 from aiocron import crontab from docopt import Dict from irc3.plugins.command import command @@ -11,14 +10,11 @@ from irc3.utils import IrcString from psycopg2 import Error from psycopg2.extras import DictRow -from . import DatabasePlugin +from . import DatabasePlugin, Bot class Timer(DatabasePlugin): - requires = ['irc3.plugins.command', - 'bot.plugins.storage'] - - def __init__(self, bot: irc3.IrcBot): + def __init__(self, bot: Bot): super().__init__(bot) self.timers = set() self.set_timers() diff --git a/bot/plugins/urban.py b/bot/urban.py similarity index 97% rename from bot/plugins/urban.py rename to bot/urban.py index 1c18bad..551444e 100644 --- a/bot/plugins/urban.py +++ b/bot/urban.py @@ -8,8 +8,6 @@ from . import Plugin class Urban(Plugin): - requires = ['irc3.plugins.command'] - URL = 'https://api.urbandictionary.com/v0/define' @command diff --git a/bot/plugins/useless.py b/bot/useless.py similarity index 98% rename from bot/plugins/useless.py rename to bot/useless.py index 23c2520..042785c 100644 --- a/bot/plugins/useless.py +++ b/bot/useless.py @@ -9,22 +9,17 @@ from irc3.utils import IrcString from psycopg2 import Error from . import DatabasePlugin -from ..utils import re_generator +from .utils import re_generator class Useless(DatabasePlugin): - requires = ['irc3.plugins.command', - 'bot.plugins.storage'] - RAINBOW = (5, 7, 8, 9, 3, 10, 12, 2, 6, 13) - WOAH = ( 'woah', 'woah indeed', 'woah woah woah!', 'keep your woahs to yourself', ) - ISPS = ( 't-ipconnect', 'telefonica', @@ -32,7 +27,6 @@ class Useless(DatabasePlugin): 'kabel', 'unity-media', ) - GENDERS = ( 'male', 'female', diff --git a/bot/plugins/weather.py b/bot/weather.py similarity index 97% rename from bot/plugins/weather.py rename to bot/weather.py index 506685a..e01ac80 100644 --- a/bot/plugins/weather.py +++ b/bot/weather.py @@ -8,8 +8,6 @@ from . import Plugin class Weather(Plugin): - requires = ['irc3.plugins.command'] - URL = 'https://query.yahooapis.com/v1/public/yql?format=json&q=' \ 'select * from weather.forecast where u="c" and woeid in ' \ '(select woeid from geo.places(1) where text="{}")' diff --git a/bot/plugins/youtube.py b/bot/youtube.py similarity index 97% rename from bot/plugins/youtube.py rename to bot/youtube.py index 5e10def..0650bd5 100644 --- a/bot/plugins/youtube.py +++ b/bot/youtube.py @@ -9,12 +9,10 @@ from irc3.plugins.command import command from irc3.utils import IrcString from . import Plugin -from ..utils import date_from_iso +from .utils import date_from_iso class YouTube(Plugin): - requires = ['irc3.plugins.command'] - URL = 'https://www.googleapis.com/youtube/v3' API = '{}/videos?part=snippet,statistics,contentDetails'.format(URL) SEARCH = '{}/search?part=id'.format(URL) diff --git a/files/config.json b/files/config.json index 4507a33..4232b6b 100644 --- a/files/config.json +++ b/files/config.json @@ -11,22 +11,22 @@ "flood_rate_delay": 1, "includes": [ "irc3.plugins.uptime", - "bot.plugins.admin", - "bot.plugins.coins", - "bot.plugins.ctcp", - "bot.plugins.isup", - "bot.plugins.linux", - "bot.plugins.mcmaniac", - "bot.plugins.quotes", - "bot.plugins.rape", - "bot.plugins.regex", - "bot.plugins.seen", - "bot.plugins.tell", - "bot.plugins.timer", - "bot.plugins.urban", - "bot.plugins.useless", - "bot.plugins.weather", - "bot.plugins.youtube" + "bot.admin", + "bot.coins", + "bot.ctcp", + "bot.isup", + "bot.linux", + "bot.mcmaniac", + "bot.quotes", + "bot.rape", + "bot.regex", + "bot.seen", + "bot.tell", + "bot.timer", + "bot.urban", + "bot.useless", + "bot.weather", + "bot.youtube" ], "irc3.plugins.command": { "guard": "irc3.plugins.command.mask_based_policy",