Massive refactoring/-structuring
This commit is contained in:
53
bot/__main__.py
Normal file
53
bot/__main__.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
||||
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: regex
|
||||
# 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()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
config = sys.argv[1]
|
||||
else:
|
||||
config = 'config.json'
|
||||
main(config)
|
32
bot/plugins/__init__.py
Normal file
32
bot/plugins/__init__.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from irc3 import IrcBot
|
||||
from irc3.plugins.command import Commands
|
||||
|
||||
MODULE = __name__
|
||||
|
||||
|
||||
class BasePlugin(object):
|
||||
def __init__(self, bot: 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: E402
|
||||
|
||||
|
||||
class DatabasePlugin(Plugin):
|
||||
def __init__(self, bot: 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
|
32
bot/plugins/admin.py
Normal file
32
bot/plugins/admin.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
import irc3
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
|
||||
from . import MODULE, Plugin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@command(permission='admin', show_in_help_list=False)
|
||||
def reload(bot: irc3.IrcBot, mask: IrcString, target: IrcString,
|
||||
args: DocOptDict):
|
||||
"""Reloads a plugin or the whole bot.
|
||||
|
||||
%%reload [<plugin>]
|
||||
"""
|
||||
plugin = args.get('<plugin>')
|
||||
if plugin:
|
||||
bot.reload('{module}.{plugin}'.format(plugin=plugin, module=MODULE))
|
||||
bot.privmsg(target, 'Reloaded plugin "{plugin}"'.format(plugin=plugin))
|
||||
else:
|
||||
bot.reload()
|
||||
bot.privmsg(target, 'Reloaded the bot')
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Admin(Plugin):
|
||||
pass
|
60
bot/plugins/coins.py
Normal file
60
bot/plugins/coins.py
Normal file
@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import irc3
|
||||
import requests
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
|
||||
from . import Plugin
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Coins(Plugin):
|
||||
requires = ['irc3.plugins.command']
|
||||
|
||||
API_URL = 'https://api.cryptowat.ch/markets/coinbase/{crypto}{currency}/summary'
|
||||
CURRENCIES = {
|
||||
'usd': '$',
|
||||
'eur': '€',
|
||||
'eth': 'Ξ',
|
||||
'btc': '฿',
|
||||
}
|
||||
|
||||
@command
|
||||
def btc(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Gets the Bitcoin values from cryptowatch.
|
||||
|
||||
%%btc [<currency>]
|
||||
"""
|
||||
return self._cryptowat_summary('btc', args.get('<currency>') or 'usd')
|
||||
|
||||
@command
|
||||
def eth(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Gets the Ethereum values from cryptowatch.
|
||||
|
||||
%%eth [<currency>]
|
||||
"""
|
||||
return self._cryptowat_summary('eth', args.get('<currency>') or 'usd')
|
||||
|
||||
def _cryptowat_summary(self, crypto: str, currency: str = 'usd'):
|
||||
# Check if valid currency + crypto2currency
|
||||
if currency not in self.CURRENCIES or crypto == currency:
|
||||
return
|
||||
|
||||
# Send request to api
|
||||
data = requests.get(self.API_URL.format(crypto=crypto, currency=currency))
|
||||
if data:
|
||||
result = data.json()['result']
|
||||
return '\x02[{crypto}]\x0F ' \
|
||||
'Current: \x02\x0307{currency}{last:,.2f}\x0F - ' \
|
||||
'High: \x02\x0303{currency}{high:,.2f}\x0F - ' \
|
||||
'Low: \x02\x0304{currency}{low:,.2f}\x0F - ' \
|
||||
'Change: \x02\x0307{change:,.2f}%\x0F - ' \
|
||||
'Volume: \x02\x0307{volume}\x0F' \
|
||||
.format(crypto=crypto.upper(),
|
||||
currency=self.CURRENCIES[currency],
|
||||
last=result['price']['last'],
|
||||
high=result['price']['high'],
|
||||
low=result['price']['low'],
|
||||
change=result['price']['change']['percentage'] * 100,
|
||||
volume=result['volume'])
|
84
bot/plugins/ctcp.py
Normal file
84
bot/plugins/ctcp.py
Normal file
@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
|
||||
import irc3
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
|
||||
from . import Plugin
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class CTCP(Plugin):
|
||||
requires = ['irc3.plugins.async',
|
||||
'irc3.plugins.command']
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def _ctcp(self, name: str, nick: str, reply: str):
|
||||
return '\x02[{}]\x0F {}: {}'.format(name.upper(), nick, reply)
|
||||
|
||||
async def ctcp(self, name: str, mask: IrcString, args: DocOptDict):
|
||||
nick = args.get('<nick>') or mask.nick
|
||||
name = name.upper()
|
||||
data = await self.bot.ctcp_async(nick, name)
|
||||
|
||||
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: DocOptDict):
|
||||
"""Sends ping via CTCP to user and sends the time needed
|
||||
|
||||
%%ping [<nick>]
|
||||
"""
|
||||
nick = args.get('<nick>') or mask.nick
|
||||
data = await self.bot.ctcp_async(nick, 'PING {}'.format(time.time()))
|
||||
|
||||
if not data or data['timeout']:
|
||||
reply = 'timeout'
|
||||
elif not data['success']:
|
||||
reply = 'Error: {}'.format(data['reply'])
|
||||
else:
|
||||
delta = time.time() - float(data['reply'])
|
||||
if delta < 1.0:
|
||||
delta *= 1000
|
||||
unit = 'ms'
|
||||
else:
|
||||
unit = 's'
|
||||
reply = '{0:.3f} {1}'.format(delta, unit)
|
||||
|
||||
return self._ctcp('PING', nick, reply)
|
||||
|
||||
@command
|
||||
async def finger(self, mask: IrcString, target: IrcString,
|
||||
args: DocOptDict):
|
||||
"""Gets the client response for finger nick user via CTCP
|
||||
|
||||
%%finger [<nick>]
|
||||
"""
|
||||
return await self.ctcp('FINGER', mask, args)
|
||||
|
||||
@command
|
||||
async def time(self, mask: IrcString, target: IrcString,
|
||||
args: DocOptDict):
|
||||
"""Gets the client time from nick via CTCP
|
||||
|
||||
%%time [<nick>]
|
||||
"""
|
||||
return await self.ctcp('TIME', mask, args)
|
||||
|
||||
@command
|
||||
async def ver(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Gets the client version from nick via CTCP
|
||||
|
||||
%%ver [<nick>]
|
||||
"""
|
||||
return await self.ctcp('VERSION', mask, args)
|
34
bot/plugins/isup.py
Normal file
34
bot/plugins/isup.py
Normal file
@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import irc3
|
||||
import requests
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
|
||||
from . import DatabasePlugin
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Useless(DatabasePlugin):
|
||||
requires = ['irc3.plugins.command',
|
||||
'bot.plugins.storage']
|
||||
|
||||
@command
|
||||
def isup(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Checks if a address is up.
|
||||
|
||||
%%isup <address>
|
||||
"""
|
||||
address = args['<address>']
|
||||
if not address.startswith('http://'):
|
||||
address = 'https://{}'.format(address)
|
||||
parsed = urlparse(address)
|
||||
|
||||
try:
|
||||
requests.head(address)
|
||||
state = 'up'
|
||||
except requests.ConnectionError:
|
||||
state = 'down'
|
||||
return '{}://{} seems to be {}'.format(parsed.scheme, parsed.netloc, state)
|
60
bot/plugins/linux.py
Normal file
60
bot/plugins/linux.py
Normal file
@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import random
|
||||
|
||||
import irc3
|
||||
import feedparser
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
|
||||
from . import Plugin
|
||||
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,
|
||||
GNU plus Linux. Linux is not an operating system unto itself, but rather
|
||||
another free component of a fully functioning GNU system made useful by the
|
||||
GNU corelibs, shell utilities and vital system components comprising a full
|
||||
OS as defined by POSIX."""
|
||||
|
||||
|
||||
class Linux(Plugin):
|
||||
KERNEL_FEED = 'https://www.kernel.org/feeds/kdist.xml'
|
||||
|
||||
@irc3.event(r'(?i)^:\S+ PRIVMSG (?P<target>\S+) :.*(debian|ubuntu|apt|dpkg).*')
|
||||
def debillian(self, target: str):
|
||||
"""Annoying RE trigger for debian with variable count of E."""
|
||||
if random.randint(0, 3) is 0:
|
||||
self.bot.privmsg(target, re_generator())
|
||||
|
||||
@irc3.event(r'(?i)^:\S+ PRIVMSG (?P<target>\S+) :.*(?<!gnu[/+])linux(?! kernel).*')
|
||||
def linux(self, target: str):
|
||||
"""Super annoying, useless 'Stallman is mad' trigger."""
|
||||
if random.randint(0, 12) is 0:
|
||||
self.bot.privmsg(target, GNU_LINUX)
|
||||
|
||||
@command
|
||||
def kernel(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Get Linux kernel releases.
|
||||
|
||||
%%kernel
|
||||
"""
|
||||
feed = feedparser.parse(self.KERNEL_FEED)
|
||||
|
||||
# Cancel if no feed or entries
|
||||
if not feed or not feed.get('entries'):
|
||||
self.log.error('Error fetching kernel.org releases feed')
|
||||
return
|
||||
|
||||
# Make list of releases
|
||||
releases = []
|
||||
for e in feed['entries']:
|
||||
version, branch = e['title'].split(': ')
|
||||
|
||||
if '(EOL)' in e['description']:
|
||||
branch = '{}, \x1DEOL\x0F'.format(branch)
|
||||
|
||||
releases.append('\x02{}\x0F ({})'.format(version, branch))
|
||||
|
||||
return '\x02[Kernel]\x0F {}'.format(', '.join(releases))
|
72
bot/plugins/mcmaniac.py
Normal file
72
bot/plugins/mcmaniac.py
Normal file
@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import irc3
|
||||
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
from psycopg2 import Error
|
||||
|
||||
from . import DatabasePlugin
|
||||
from ..utils import parse_int
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class McManiac(DatabasePlugin):
|
||||
requires = ['irc3.plugins.command',
|
||||
'bot.plugins.storage']
|
||||
|
||||
@command(options_first=True)
|
||||
def mcmaniac(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Get a random McManiaC or by index.
|
||||
|
||||
%%mcmaniac [<index>]
|
||||
"""
|
||||
index = args.get('<index>')
|
||||
|
||||
if index:
|
||||
index = parse_int(index)
|
||||
if not index:
|
||||
return
|
||||
index, order = index
|
||||
order = 'id {order}'.format(order=order)
|
||||
offset = 'offset %s'
|
||||
else:
|
||||
order = 'random()'
|
||||
offset = ''
|
||||
|
||||
# Fetch result from database
|
||||
self.cur.execute('''
|
||||
select
|
||||
item,
|
||||
rank() over (order by id),
|
||||
count(*) over (rows between unbounded preceding
|
||||
and unbounded following) as total
|
||||
from
|
||||
mcmaniacs
|
||||
order by
|
||||
{order}
|
||||
limit
|
||||
1
|
||||
{offset}
|
||||
'''.format(order=order, offset=offset), [index])
|
||||
result = self.cur.fetchone()
|
||||
|
||||
if result:
|
||||
return '[{rank}/{total}] {item}'.format(**result)
|
||||
|
||||
# TODO: fix regex ("McFooiaC McBariaC" adds "Mc\S+iaC")
|
||||
@irc3.event(r'^:(?P<mask>\S+) PRIVMSG \S+ :.*(?P<item>Mc\S+iaC).*')
|
||||
def save(self, mask: str, item: str):
|
||||
if IrcString(mask).nick != self.bot.nick:
|
||||
try:
|
||||
# Insert into database
|
||||
self.cur.execute('''
|
||||
insert into
|
||||
mcmaniacs (item)
|
||||
values
|
||||
(%s)
|
||||
''', [item])
|
||||
self.con.commit()
|
||||
except Error:
|
||||
# Rollback transaction on error
|
||||
self.con.rollback()
|
130
bot/plugins/quotes.py
Normal file
130
bot/plugins/quotes.py
Normal file
@ -0,0 +1,130 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
|
||||
import irc3
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
from psycopg2 import Error
|
||||
|
||||
from . import DatabasePlugin
|
||||
from ..utils import parse_int
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Quotes(DatabasePlugin):
|
||||
requires = ['irc3.plugins.command',
|
||||
'bot.plugins.storage']
|
||||
|
||||
def add_quote(self, mask, nick, quote, channel):
|
||||
# Parse nick from "<@foobar>" like strings
|
||||
nick = re.match(r'<?[~&@%+]?([a-zA-Z0-9_\-^`|\\\[\]{}]+)>?', nick).group(1)
|
||||
|
||||
if not nick:
|
||||
self.bot.notice(mask.nick, '[Quotes] Error parsing nick')
|
||||
else:
|
||||
# Insert quote into database
|
||||
self.cur.execute('''
|
||||
insert into
|
||||
quotes (nick, item, channel, created_by)
|
||||
values
|
||||
(%s, %s, %s, %s)
|
||||
''', [nick, quote, channel, mask.nick])
|
||||
|
||||
def delete_quote(self, nick, quote):
|
||||
index, order = parse_int(quote, select=False)
|
||||
|
||||
if index:
|
||||
# Delete from database
|
||||
self.cur.execute('''
|
||||
with ranked_quotes as (
|
||||
select
|
||||
id,
|
||||
rank() over (partition by nick order by id {order})
|
||||
from
|
||||
quotes
|
||||
where
|
||||
nick = %s
|
||||
)
|
||||
|
||||
delete from
|
||||
quotes
|
||||
where
|
||||
id = (
|
||||
select
|
||||
id
|
||||
from
|
||||
ranked_quotes
|
||||
where
|
||||
rank = %s
|
||||
)
|
||||
'''.format(order=order), [nick, index])
|
||||
|
||||
@command(options_first=True)
|
||||
def q(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Get, add or delete quotes for an user.
|
||||
|
||||
%%q <cmd> <nick> <quote>...
|
||||
%%q <nick> [<index>]
|
||||
"""
|
||||
cmd = args.get('<cmd>')
|
||||
nick = args['<nick>']
|
||||
quote = ' '.join(args.get('<quote>'))
|
||||
|
||||
if cmd and quote:
|
||||
try:
|
||||
# Anybody can add
|
||||
if cmd == 'add':
|
||||
self.add_quote(mask, nick, quote, target)
|
||||
# But only admins can delete
|
||||
elif cmd == 'del' and self.guard.has_permission(mask, 'admin'):
|
||||
self.delete_quote(nick, quote)
|
||||
|
||||
self.con.commit()
|
||||
except Error as ex:
|
||||
# Rollback transaction on error
|
||||
self.con.rollback()
|
||||
else:
|
||||
index = args.get('<index>')
|
||||
values = [nick]
|
||||
|
||||
if index:
|
||||
index = parse_int(index)
|
||||
if not index:
|
||||
return
|
||||
index, order = index
|
||||
order = 'rank {order}'.format(order=order)
|
||||
offset = 'offset %s'
|
||||
values.append(index)
|
||||
else:
|
||||
order = 'random()'
|
||||
offset = ''
|
||||
|
||||
# Fetch quote from database
|
||||
self.cur.execute("""
|
||||
with ranked_quotes as (
|
||||
select
|
||||
nick,
|
||||
item,
|
||||
rank() over (partition by nick order by id),
|
||||
count(*) over (partition by nick) as total
|
||||
from
|
||||
quotes
|
||||
)
|
||||
|
||||
select
|
||||
*
|
||||
from
|
||||
ranked_quotes
|
||||
where
|
||||
lower(nick) like lower(%s)
|
||||
order by
|
||||
{order}
|
||||
limit
|
||||
1
|
||||
{offset}
|
||||
""".format(order=order, offset=offset), values)
|
||||
result = self.cur.fetchone()
|
||||
|
||||
if result:
|
||||
return '[{rank}/{total}] <{nick}> {item}'.format(**result)
|
87
bot/plugins/rape.py
Normal file
87
bot/plugins/rape.py
Normal file
@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import random
|
||||
|
||||
import irc3
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
from psycopg2 import Error
|
||||
|
||||
from . import DatabasePlugin
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Rape(DatabasePlugin):
|
||||
requires = ['irc3.plugins.command',
|
||||
'bot.plugins.storage']
|
||||
|
||||
@command
|
||||
def owe(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Shows how much a nick owes.
|
||||
|
||||
%%owe [<nick>]
|
||||
"""
|
||||
nick = args.get('<nick>') or mask.nick
|
||||
|
||||
# Fetch result from database
|
||||
self.cur.execute('''
|
||||
select
|
||||
fines
|
||||
from
|
||||
users
|
||||
where
|
||||
lower(nick) = lower(%s)
|
||||
''', [nick])
|
||||
owes = self.cur.fetchone()
|
||||
|
||||
# Colorize owe amount and return string
|
||||
if owes:
|
||||
fines = '4${fines}'.format(fines=owes['fines'])
|
||||
else:
|
||||
fines = '3$0'
|
||||
|
||||
# Return total owes
|
||||
return '{nick} owes: \x03{fines}\x03'.format(nick=nick, fines=fines)
|
||||
|
||||
@command
|
||||
def rape(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Rapes a nick and eventually charge for it.
|
||||
|
||||
%%rape <nick>
|
||||
"""
|
||||
nick = args.get('<nick>') or mask.nick
|
||||
rand = random.randint(0, 3)
|
||||
|
||||
if rand not in (0, 1):
|
||||
self.bot.action(target, 'rapes {nick}'.format(nick=nick))
|
||||
else:
|
||||
fine = random.randint(1, 500)
|
||||
|
||||
try:
|
||||
# Insert or add fine to database and return total owe
|
||||
self.cur.execute('''
|
||||
insert into
|
||||
users (nick, fines)
|
||||
values
|
||||
(lower(%s), %s)
|
||||
on conflict (nick) do update set
|
||||
fines = users.fines + excluded.fines
|
||||
returning
|
||||
fines
|
||||
''', [mask.nick, fine])
|
||||
self.con.commit()
|
||||
|
||||
# Get reason based on rand value
|
||||
reason = ('raping', 'being too lewd and getting raped')[rand]
|
||||
|
||||
# Print fine and total owe
|
||||
self.bot.action(target,
|
||||
'fines {nick} \x02${fine}\x02 for {reason}. '
|
||||
'You owe: \x0304${total}\x03'
|
||||
.format(nick=mask.nick,
|
||||
fine=fine,
|
||||
reason=reason,
|
||||
total=self.cur.fetchone()['fines']))
|
||||
except Error:
|
||||
# Rollback transaction on error
|
||||
self.con.rollback()
|
59
bot/plugins/regex.py
Normal file
59
bot/plugins/regex.py
Normal file
@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
|
||||
import irc3
|
||||
from irc3.utils import IrcString
|
||||
|
||||
from . import DatabasePlugin
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Useless(DatabasePlugin):
|
||||
requires = ['irc3.plugins.command',
|
||||
'bot.plugins.storage']
|
||||
|
||||
@irc3.event(r'^:(?P<mask>\S+) PRIVMSG (?P<target>#\S+) :s/'
|
||||
r'(?P<search>(?:[^/\\]|\\.)*)/'
|
||||
r'(?P<replace>(?:.*?))'
|
||||
r'(?:/ ?(?P<nick>.*))?$')
|
||||
def regex(self, mask: str, target: str, search: str, replace: str,
|
||||
nick: str = None):
|
||||
nick = (nick or IrcString(mask).nick).strip()
|
||||
|
||||
if nick == self.bot.nick:
|
||||
return
|
||||
|
||||
self.cur.execute('''
|
||||
select
|
||||
item
|
||||
from
|
||||
last_messages
|
||||
where
|
||||
nick = lower(%s)
|
||||
and channel = lower(%s)
|
||||
''', [nick, target])
|
||||
result = self.cur.fetchone()
|
||||
|
||||
if result:
|
||||
old = result['item']
|
||||
msg = old.replace(search, '\x02{}\x0F'.format(replace), 1)
|
||||
msg = re.sub(r'\x01ACTION (.*)\x01', r'/me \1', msg)
|
||||
if old != msg:
|
||||
self.bot.privmsg(target, '<{nick}> {msg}'.format(nick=nick,
|
||||
msg=msg))
|
||||
|
||||
@irc3.event(r'(?i)^:(?P<mask>\S+) PRIVMSG (?P<target>#\S+) :(?P<msg>.*)$')
|
||||
def last_message(self, mask: str, target: str, msg: str):
|
||||
"""Saves the last message of a user for each channel (for regex)."""
|
||||
mask = IrcString(mask)
|
||||
|
||||
self.cur.execute('''
|
||||
insert into
|
||||
last_messages (nick, host, channel, item)
|
||||
values
|
||||
(lower(%s), %s, lower(%s), %s)
|
||||
on conflict (nick, channel) do update set
|
||||
host = excluded.host,
|
||||
item = excluded.item
|
||||
''', [mask.nick, mask.host, target, msg])
|
||||
self.con.commit()
|
74
bot/plugins/seen.py
Normal file
74
bot/plugins/seen.py
Normal file
@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
import irc3
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
from psycopg2 import Error
|
||||
|
||||
from . import DatabasePlugin
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Seen(DatabasePlugin):
|
||||
requires = ['irc3.plugins.command',
|
||||
'bot.plugins.storage']
|
||||
|
||||
@command
|
||||
def seen(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Get last seen date and message for a nick.
|
||||
|
||||
%%seen [<nick>]
|
||||
"""
|
||||
nick = args.get('<nick>') or mask.nick
|
||||
|
||||
# Don't be stupid
|
||||
if nick == mask.nick:
|
||||
return '{nick}, have you seen in the mirror?'.format(nick=nick)
|
||||
|
||||
# Fetch seen from database
|
||||
self.cur.execute('''
|
||||
select
|
||||
*
|
||||
from
|
||||
seens
|
||||
where
|
||||
lower(nick) = lower(%s)
|
||||
''', [nick])
|
||||
seen = self.cur.fetchone()
|
||||
|
||||
# No result
|
||||
if not seen:
|
||||
return 'I\'ve never seen {nick}'.format(nick=nick)
|
||||
|
||||
# Return result
|
||||
return '{nick} was last seen {delta} saying: {message}'.format(
|
||||
nick=seen['nick'],
|
||||
# TODO: relative string delta?
|
||||
delta=seen['seen_at'],
|
||||
message=re.sub(r'\x01ACTION (.*)\x01', r'/me \1', seen['message']),
|
||||
)
|
||||
|
||||
@irc3.event(r'(?i)^:(?P<mask>\S+) PRIVMSG (?P<target>\S+) :(?P<msg>.*)')
|
||||
def save(self, mask: str, target: str, msg: str):
|
||||
mask = IrcString(mask)
|
||||
|
||||
try:
|
||||
# Insert or update if user writes a message
|
||||
self.cur.execute('''
|
||||
insert into
|
||||
seens (nick, host, channel, message, seen_at)
|
||||
values
|
||||
(%s, %s, %s, %s, %s)
|
||||
on conflict (nick) do update set
|
||||
host = excluded.host,
|
||||
channel = excluded.channel,
|
||||
seen_at = excluded.seen_at,
|
||||
message = excluded.message
|
||||
''', [mask.nick, mask.host, target, msg, datetime.now()])
|
||||
self.con.commit()
|
||||
except Error:
|
||||
# Rollback transaction on error
|
||||
self.con.rollback()
|
18
bot/plugins/storage.py
Normal file
18
bot/plugins/storage.py
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
import irc3
|
||||
import psycopg2
|
||||
from psycopg2.extras import DictCursor
|
||||
|
||||
from . import Plugin
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Storage(Plugin):
|
||||
def __init__(self, bot: irc3.IrcBot):
|
||||
super().__init__(bot)
|
||||
# Create database connection
|
||||
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)
|
98
bot/plugins/tell.py
Normal file
98
bot/plugins/tell.py
Normal file
@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
import irc3
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
from psycopg2 import Error
|
||||
|
||||
from . import DatabasePlugin
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Tell(DatabasePlugin):
|
||||
requires = ['irc3.plugins.command',
|
||||
'bot.plugins.storage']
|
||||
|
||||
def __init__(self, bot: irc3.IrcBot):
|
||||
super().__init__(bot)
|
||||
self.tell_queue = {}
|
||||
|
||||
# Fetch tells from database
|
||||
self.cur.execute('''
|
||||
select
|
||||
to_nick, from_nick, message, created_at
|
||||
from
|
||||
tells
|
||||
''')
|
||||
|
||||
# Add tells to queue
|
||||
for res in self.cur.fetchall():
|
||||
nick = res['to_nick'].lower()
|
||||
|
||||
# Create list in queue and add tell
|
||||
if nick not in self.tell_queue:
|
||||
self.tell_queue[nick] = []
|
||||
self.tell_queue[nick].append(res[1:])
|
||||
|
||||
@command
|
||||
def tell(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Saves a message for nick to forward on activity
|
||||
|
||||
%%tell <nick> <message>...
|
||||
"""
|
||||
nick = args['<nick>']
|
||||
nick_lower = nick.lower()
|
||||
tell = [nick, mask.nick, ' '.join(args['<message>']).strip(),
|
||||
datetime.now()]
|
||||
|
||||
# Create list in queue and add tell
|
||||
if nick_lower not in self.tell_queue:
|
||||
self.tell_queue[nick_lower] = []
|
||||
self.tell_queue[nick_lower].append(tell[1:])
|
||||
|
||||
try:
|
||||
# Insert tell into database
|
||||
self.cur.execute('''
|
||||
insert into
|
||||
tells (to_nick, from_nick, message, created_at)
|
||||
values
|
||||
(%s, %s, %s, %s)
|
||||
''', tell)
|
||||
self.con.commit()
|
||||
except Error:
|
||||
# Rollback transaction on error
|
||||
self.con.rollback()
|
||||
|
||||
@irc3.event(r'(?i)^:(?P<mask>.*) PRIVMSG .* :.*')
|
||||
def check(self, mask: str):
|
||||
"""If activ nick has tells, forward and delete them."""
|
||||
nick = IrcString(mask).nick
|
||||
nick_lower = nick.lower()
|
||||
|
||||
if nick_lower in self.tell_queue:
|
||||
# Forward all tells for nick
|
||||
for tell in self.tell_queue[nick_lower]:
|
||||
# TODO: format time
|
||||
self.bot.privmsg(nick, '[Tell] Message from {nick} at {time}: '
|
||||
'{message}'.format(nick=tell[0],
|
||||
time=tell[2],
|
||||
message=tell[1]))
|
||||
|
||||
# Remove nick from queue
|
||||
del self.tell_queue[nick_lower]
|
||||
|
||||
try:
|
||||
# Remove tells from database
|
||||
self.cur.execute('''
|
||||
delete from
|
||||
tells
|
||||
where
|
||||
lower(to_nick) = lower(%s)
|
||||
''', [nick])
|
||||
self.con.commit()
|
||||
except Error as ex:
|
||||
print(ex)
|
||||
# Rollback transaction on error
|
||||
self.con.rollback()
|
106
bot/plugins/timer.py
Normal file
106
bot/plugins/timer.py
Normal file
@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import irc3
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
from psycopg2 import Error
|
||||
|
||||
from . import DatabasePlugin
|
||||
from ..utils import time_delta
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Timer(DatabasePlugin):
|
||||
requires = ['irc3.plugins.command',
|
||||
'bot.plugins.storage']
|
||||
|
||||
def __init__(self, bot: irc3.IrcBot):
|
||||
super().__init__(bot)
|
||||
|
||||
# Fetch timers from database
|
||||
self.cur.execute('''
|
||||
select
|
||||
id, mask, target, message, delay, ends_at
|
||||
from
|
||||
timers
|
||||
''')
|
||||
|
||||
# Recreate timers
|
||||
for res in self.cur.fetchall():
|
||||
self.start_timer(IrcString(res['mask']),
|
||||
res['target'],
|
||||
res['message'],
|
||||
res['delay'],
|
||||
res['ends_at'] - datetime.now(),
|
||||
res['id'])
|
||||
|
||||
@command
|
||||
def timer(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Sets a timer, delay can be: s, m, h, w, mon, y(
|
||||
|
||||
%%timer <delay> <message>...
|
||||
"""
|
||||
delay = args['<delay>']
|
||||
delta = time_delta(delay)
|
||||
|
||||
if not delta:
|
||||
self.bot.privmsg(target, 'Invalid timer delay')
|
||||
else:
|
||||
message = ' '.join(args['<message>'])
|
||||
values = [mask, target, message, delay]
|
||||
|
||||
try:
|
||||
# Insert into database (add now + delta)
|
||||
self.cur.execute('''
|
||||
insert into
|
||||
timers (mask, target, message, delay, ends_at)
|
||||
values
|
||||
(%s, %s, %s, %s, %s)
|
||||
returning id
|
||||
''', values + [datetime.now() + delta])
|
||||
self.con.commit()
|
||||
|
||||
# Add delta and id from inserted and start timer
|
||||
values.extend([delta, self.cur.fetchone()['id']])
|
||||
self.start_timer(*values)
|
||||
|
||||
# Send notice to user that timer has been set
|
||||
self.bot.notice(mask.nick, 'Timer in {delay} set: {message}'
|
||||
.format(delay=delay, message=message))
|
||||
except Error:
|
||||
# Rollback transaction on error
|
||||
self.con.rollback()
|
||||
|
||||
def start_timer(self, mask: IrcString, target: IrcString, message: str,
|
||||
delay: str, delta: timedelta, row_id: int):
|
||||
"""Async function, sleeps for `delay` seconds and sends notification"""
|
||||
|
||||
async def callback():
|
||||
# Sleep if necessary until timed
|
||||
seconds = delta.total_seconds()
|
||||
if seconds > 0:
|
||||
await asyncio.sleep(seconds)
|
||||
|
||||
# Send reminder
|
||||
self.bot.privmsg(target, '\x02[Timer]\x0F {nick}: {message} '
|
||||
'({delay})'.format(message=message,
|
||||
nick=mask.nick,
|
||||
delay=delay))
|
||||
|
||||
try:
|
||||
# Delete timer from database
|
||||
self.cur.execute('''
|
||||
delete from
|
||||
timers
|
||||
where
|
||||
id = %s
|
||||
''', [row_id])
|
||||
self.con.commit()
|
||||
except Error:
|
||||
# Rollback transaction on error
|
||||
self.con.rollback()
|
||||
|
||||
asyncio.ensure_future(callback())
|
51
bot/plugins/urban.py
Normal file
51
bot/plugins/urban.py
Normal file
@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import irc3
|
||||
import requests
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
|
||||
from . import Plugin
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Urban(Plugin):
|
||||
requires = ['irc3.plugins.command']
|
||||
|
||||
URL = 'https://api.urbandictionary.com/v0/define'
|
||||
|
||||
@command
|
||||
def ud(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Searches for a term on YouTube and returns first result.
|
||||
|
||||
%%ud <term>...
|
||||
"""
|
||||
# Clean, strip and split term by whitespace
|
||||
term = ' '.join(args.get('<term>')).lower().strip().split()
|
||||
|
||||
if term[-1].isdigit():
|
||||
# Parse number at end as index - 1 and slice term
|
||||
index = int(term[-1]) - 1
|
||||
term = ' '.join(term[:-1])
|
||||
else:
|
||||
# Set index to 0
|
||||
index = 0
|
||||
|
||||
# Fetch data for term from urban dictionary as json
|
||||
data = requests.get(self.URL, params=dict(term=term)).json()
|
||||
|
||||
# No results
|
||||
if data['result_type'] == 'no_results':
|
||||
return 'Term not found'
|
||||
|
||||
# Get result by index
|
||||
res = data['list'][index]
|
||||
|
||||
# Format and return result
|
||||
return '[{idx}/{len}] \x02{word}\x02: {definition} - {example}'.format(
|
||||
idx=index + 1,
|
||||
len=len(data['list']),
|
||||
word=res['word'],
|
||||
definition=res['definition'].replace('\r\n', ' '),
|
||||
example=res['example'].replace('\r\n', ' '),
|
||||
)
|
298
bot/plugins/useless.py
Normal file
298
bot/plugins/useless.py
Normal file
@ -0,0 +1,298 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import random
|
||||
import re
|
||||
|
||||
import irc3
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
from psycopg2 import Error
|
||||
|
||||
from . import DatabasePlugin
|
||||
from ..utils import re_generator
|
||||
|
||||
RAINBOW = (5, 7, 8, 9, 3, 10, 12, 2, 6, 13)
|
||||
RAINBOW_LEN = len(RAINBOW)
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
class Useless(DatabasePlugin):
|
||||
requires = ['irc3.plugins.command',
|
||||
'bot.plugins.storage']
|
||||
WOAH = (
|
||||
'woah',
|
||||
'woah indeed',
|
||||
'woah woah woah!',
|
||||
'keep your woahs to yourself',
|
||||
)
|
||||
|
||||
@irc3.event(r'(?i)^:(?P<mask>\S+) JOIN :(?P<target>#\S+)$')
|
||||
def tehkuh(self, mask, target):
|
||||
if re.search(r'(?i).*(tehkuh).*@.*(telefonica|vodafone|kabel|unity-media).*', mask):
|
||||
self.bot.privmsg(target, '{}: Bouncer'.format(IrcString(mask).nick))
|
||||
|
||||
@irc3.event(r'(?i)^:\S+ PRIVMSG (?P<target>\S+) :.*(woah|whoa).*$')
|
||||
def woah(self, target: str):
|
||||
"""Colorize words in a sentence with rainbow colors."""
|
||||
if random.randint(0, 4) is 0:
|
||||
self.bot.privmsg(target, random.choice(self.WOAH))
|
||||
|
||||
@irc3.event(r'(?i)^:\S+ PRIVMSG (?P<target>\S+) :(?P<msg>huehuehue)$')
|
||||
def huehuehue(self, target: str, msg: str):
|
||||
"""Returns a huehuehue when someone writes it."""
|
||||
self.bot.privmsg(target, msg)
|
||||
|
||||
@irc3.event(r'(?i)^:\S+ PRIVMSG (?P<target>\S+) :re+$')
|
||||
def reeeeee(self, target: str):
|
||||
"""Returns a REEEE."""
|
||||
self.bot.privmsg(target, re_generator())
|
||||
|
||||
@irc3.event(r'(?i)^:\S+ PRIVMSG (?P<target>\S+) :same$')
|
||||
def same(self, target: str):
|
||||
"""Returns a plain same when a user writes same."""
|
||||
self.bot.privmsg(target, 'same')
|
||||
|
||||
@irc3.event(r'(?i)^:\S+ PRIVMSG (?P<target>\S+) :\[(?P<msg>.*)\]$')
|
||||
def intensifies(self, target: str, msg: str):
|
||||
"""String with brackets around will be returned with INTENSIFIES."""
|
||||
self.bot.privmsg(target, '\x02[{} INTENSIFIES]'.format(msg.upper()))
|
||||
|
||||
@command
|
||||
def kill(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Kills a user with a random message.
|
||||
|
||||
%%kill [<nick>]
|
||||
"""
|
||||
self.cur.execute('''
|
||||
select
|
||||
item
|
||||
from
|
||||
kills
|
||||
order by
|
||||
random()
|
||||
limit
|
||||
1
|
||||
''')
|
||||
nick = args.get('<nick>') or mask.nick
|
||||
self.bot.action(target, self.cur.fetchone()['item'].format(nick))
|
||||
|
||||
@command
|
||||
def yiff(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Yiffs a user with a random message.
|
||||
|
||||
%%yiff [<nick>]
|
||||
"""
|
||||
self.cur.execute('''
|
||||
select
|
||||
item
|
||||
from
|
||||
yiffs
|
||||
order by
|
||||
random()
|
||||
limit
|
||||
1
|
||||
''')
|
||||
nick = args.get('<nick>') or mask.nick
|
||||
self.bot.action(target, self.cur.fetchone()['item'].format(nick))
|
||||
|
||||
@command
|
||||
def waifu(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Get waifu for a user.
|
||||
|
||||
%%waifu [<nick>...]
|
||||
"""
|
||||
nick = ' '.join(args.get('<nick>')) or mask.nick
|
||||
|
||||
if nick.startswith('='):
|
||||
waifu = nick[1:]
|
||||
|
||||
try:
|
||||
self.cur.execute('''
|
||||
insert into
|
||||
users (nick, waifu)
|
||||
values
|
||||
(lower(%s), %s)
|
||||
on conflict (nick) do update set
|
||||
waifu = excluded.waifu
|
||||
''', [mask.nick, waifu])
|
||||
|
||||
self.bot.notice(mask.nick, 'Waifu set to: {}'.format(waifu))
|
||||
except Error as ex:
|
||||
print(ex)
|
||||
self.con.rollback()
|
||||
else:
|
||||
self.cur.execute('''
|
||||
select
|
||||
waifu
|
||||
from
|
||||
users
|
||||
where
|
||||
lower(nick) = lower(%s)
|
||||
''', [nick])
|
||||
result = self.cur.fetchone()
|
||||
|
||||
if result and result['waifu']:
|
||||
return '\x02[Waifu]\x0F {}: {}'.format(nick, result['waifu'])
|
||||
|
||||
@command
|
||||
def husbando(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Get husbando for a user.
|
||||
|
||||
%%husbando [<nick>...]
|
||||
"""
|
||||
nick = ' '.join(args.get('<nick>')) or mask.nick
|
||||
|
||||
if nick.startswith('='):
|
||||
nick = nick[1:]
|
||||
|
||||
try:
|
||||
self.cur.execute('''
|
||||
insert into
|
||||
users (nick, husbando)
|
||||
values
|
||||
(lower(%s), %s)
|
||||
on conflict (nick) do update set
|
||||
husbando = excluded.husbando
|
||||
''', [mask.nick, nick])
|
||||
|
||||
self.bot.notice(mask.nick, 'Husbando set to: {}'.format(nick))
|
||||
except Error as ex:
|
||||
print(ex)
|
||||
self.con.rollback()
|
||||
else:
|
||||
self.cur.execute('''
|
||||
select
|
||||
husbando
|
||||
from
|
||||
users
|
||||
where
|
||||
lower(nick) = lower(%s)
|
||||
''', [nick])
|
||||
result = self.cur.fetchone()
|
||||
|
||||
if result and result['husbando']:
|
||||
return '\x02[Husbando]\x0F {}: {}'.format(nick, result['husbando'])
|
||||
|
||||
@command
|
||||
def storyofpomfface(self, mask: IrcString, target: IrcString,
|
||||
args: DocOptDict):
|
||||
"""Story of pomf face.
|
||||
|
||||
%%storyofpomfface
|
||||
"""
|
||||
for face in (':O C==3', ':OC==3', ':C==3', ':C=3', ':C3', ':3'):
|
||||
self.bot.privmsg(target, face)
|
||||
|
||||
@command
|
||||
def choose(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Decides between items separated by comma.
|
||||
|
||||
%%choose <items>...
|
||||
"""
|
||||
choice = random.choice(' '.join(args['<items>']).split(','))
|
||||
return '{}: {}'.format(mask.nick, choice.strip())
|
||||
|
||||
@command
|
||||
def jn(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Decides between yes and no (for a question).
|
||||
|
||||
%%jn <question>...
|
||||
%%jn
|
||||
"""
|
||||
choice = random.choice(['3Ja', '4Nein'])
|
||||
return '{}: \x02\x030{}'.format(mask.nick, choice)
|
||||
|
||||
@command
|
||||
def kiss(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Kisses a user.
|
||||
|
||||
%%kiss <nick>
|
||||
"""
|
||||
return '(づ。◕‿‿◕。)\x0304。。・゜゜・。。・゜❤\x0F {} \x0304❤'.format(args['<nick>'])
|
||||
|
||||
@command
|
||||
def hug(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Hugs a user.
|
||||
|
||||
%%hug <nick>
|
||||
"""
|
||||
return '\x0304♥♡❤♡♥\x0F {} \x0304♥♡❤♡♥'.format(args['<nick>'])
|
||||
|
||||
@command
|
||||
def bier(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Gives nick a beer.
|
||||
|
||||
%%bier [<nick>]
|
||||
"""
|
||||
nick = args.get('<nick>') or mask.nick
|
||||
self.bot.action(target, 'schenkt ein kühles Blondes an {} aus.'.format(nick))
|
||||
|
||||
@command
|
||||
def fucken(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Kills and fucks a nick.
|
||||
|
||||
%%fucken [<nick>]
|
||||
"""
|
||||
nick = args.get('<nick>') or mask.nick
|
||||
self.bot.action(target, 'fuckt {0} und tötet {0} anschließend.'.format(nick, nick))
|
||||
|
||||
@command
|
||||
def anhero(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Kicks a nick.
|
||||
|
||||
%%anhero
|
||||
"""
|
||||
self.bot.privmsg(target, 'Sayonara bonzai-chan...')
|
||||
self.bot.kick(target, mask.nick)
|
||||
|
||||
@command
|
||||
def sudoku(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Kicks a nick.
|
||||
|
||||
%%sudoku
|
||||
"""
|
||||
self.anhero(mask, target, args)
|
||||
|
||||
@command
|
||||
def hack(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Hacks (a user).
|
||||
|
||||
%%hack [<nick>]
|
||||
"""
|
||||
nick = args.get('<nick>') or ''
|
||||
return 'hacking{}...'.format(' %s' % nick if nick else '')
|
||||
|
||||
@command
|
||||
def gay(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Make someone gay (alias to rainbow)
|
||||
|
||||
%%gay <word>...
|
||||
"""
|
||||
return self.rainbow(mask, target, args)
|
||||
|
||||
@command
|
||||
def rainbow(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Colorize a word in rainbow colors.
|
||||
|
||||
%%rainbow <word>...
|
||||
"""
|
||||
last = 0
|
||||
word = []
|
||||
for char in ' '.join(args.get('<word>')):
|
||||
if char != ' ':
|
||||
char = self._rainbow(last, char)
|
||||
last += 1
|
||||
word.append(char)
|
||||
return ''.join(word)
|
||||
|
||||
@command
|
||||
def wrainbow(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Colorize words in a sentence with rainbow colors.
|
||||
|
||||
%%wrainbow <words>...
|
||||
"""
|
||||
return ' '.join([self._rainbow(i, word) for i, word in enumerate(args['<words>'])])
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def _rainbow(self, i, char):
|
||||
return '\x030{}{}'.format(RAINBOW[i % RAINBOW_LEN], char)
|
44
bot/plugins/weather.py
Normal file
44
bot/plugins/weather.py
Normal file
@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import irc3
|
||||
import requests
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
|
||||
from . import Plugin
|
||||
|
||||
|
||||
@irc3.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="{}")'
|
||||
|
||||
@command
|
||||
def weather(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Gets the weather from Yahoo weather API.
|
||||
|
||||
%%weather <location>...
|
||||
"""
|
||||
req = requests.get(self.URL.format(' '.join(args['<location>'])))
|
||||
data = req.json()
|
||||
if 'error' in data:
|
||||
return '\x02[Weather]\x0F Error'
|
||||
elif not data['query']['results']:
|
||||
return '\x02[Weather]\x0F Location not found'
|
||||
else:
|
||||
res = data['query']['results']['channel']
|
||||
return '\x02[Weather]\x0F {city}, {region}, {country}: ' \
|
||||
'\x02{temp}°{unit_temp} {text}\x0F, ' \
|
||||
'\x02{direction} {speed} {unit_speed}\x0F' \
|
||||
.format(city=res['location']['city'],
|
||||
region=res['location']['region'].strip(),
|
||||
country=res['location']['country'],
|
||||
temp=res['item']['condition']['temp'],
|
||||
text=res['item']['condition']['text'],
|
||||
direction='↑↗→↘↓↙←↖'[round(int(res['wind']['direction']) / 45) % 8],
|
||||
speed=res['wind']['speed'],
|
||||
unit_temp=res['units']['temperature'],
|
||||
unit_speed=res['units']['speed'])
|
83
bot/plugins/youtube.py
Normal file
83
bot/plugins/youtube.py
Normal file
@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
|
||||
import irc3
|
||||
import requests
|
||||
from docopt import Dict as DocOptDict
|
||||
from irc3.plugins.command import command
|
||||
from irc3.utils import IrcString
|
||||
|
||||
from . import Plugin
|
||||
from ..utils import date_from_iso
|
||||
|
||||
|
||||
@irc3.plugin
|
||||
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)
|
||||
|
||||
def get_video_data(self, video_id: str):
|
||||
"""Requests the infos for a video id and formats them."""
|
||||
data = self._api(self.API, id=video_id)
|
||||
|
||||
if not data['items']:
|
||||
return
|
||||
|
||||
item = data['items'][0]
|
||||
date = date_from_iso(item['snippet']['publishedAt'])
|
||||
length = re.findall('(\d+[DHMS])', item['contentDetails']['duration'])
|
||||
|
||||
views = int(item['statistics'].get('viewCount', 0))
|
||||
likes = int(item['statistics'].get('likeCount', 0))
|
||||
dislikes = int(item['statistics'].get('dislikeCount', 0))
|
||||
|
||||
try:
|
||||
score = 100 * float(likes) / (likes + dislikes)
|
||||
except ZeroDivisionError:
|
||||
score = 0
|
||||
|
||||
return '{title} - length {length} -\x033 {likes:,}\x03 /' \
|
||||
'\x034 {dislikes:,}\x03 ({score:,.1f}%) - {views:,} ' \
|
||||
'views - {channel} on {date}' \
|
||||
.format(title=item['snippet']['title'],
|
||||
channel=item['snippet']['channelTitle'],
|
||||
length=' '.join([l.lower() for l in length]),
|
||||
likes=likes,
|
||||
dislikes=dislikes,
|
||||
score=score,
|
||||
views=views,
|
||||
date=date.strftime('%Y.%m.%d'))
|
||||
|
||||
@irc3.event(r'(?i)^:.* PRIVMSG (?P<target>.*) :.*(?:youtube.*?(?:v=|/v/)'
|
||||
r'|youtu\.be/)(?P<video_id>[-_a-zA-Z0-9]+).*')
|
||||
def youtube_parser(self, target: str, video_id: str):
|
||||
data = self.get_video_data(video_id)
|
||||
if data:
|
||||
self.bot.privmsg(target, data)
|
||||
|
||||
@command
|
||||
def yt(self, mask: IrcString, target: IrcString, args: DocOptDict):
|
||||
"""Searches for query on YouTube and returns first result.
|
||||
|
||||
%%yt <query>...
|
||||
"""
|
||||
data = self._api(self.SEARCH, q=' '.join(args['<query>']))
|
||||
|
||||
if 'error' in data:
|
||||
return '\x02[YouTube]\x0F Error performing search'
|
||||
elif data['pageInfo']['totalResults'] is 0:
|
||||
return '\x02[YouTube]\x0F No results found'
|
||||
else:
|
||||
video_id = data['items'][0]['id']['videoId']
|
||||
data = self.get_video_data(video_id)
|
||||
return '{} - https://youtu.be/{}'.format(data, video_id)
|
||||
|
||||
@staticmethod
|
||||
def _api(url: str, **kwargs):
|
||||
"""Wrapper around requests.get which adds the Google API key."""
|
||||
kwargs['key'] = os.environ['GOOGLE_API_KEY']
|
||||
return requests.get(url, params=kwargs).json()
|
5
bot/server.py
Normal file
5
bot/server.py
Normal file
@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import irc3d
|
||||
|
||||
if __name__ == '__main__':
|
||||
irc3d.run()
|
54
bot/utils.py
Normal file
54
bot/utils.py
Normal file
@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import random
|
||||
from pprint import pprint
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
TIME_UNITS = {
|
||||
's': 'seconds',
|
||||
'm': 'minutes',
|
||||
'h': 'hours',
|
||||
'd': 'days',
|
||||
'w': 'weeks',
|
||||
'mon': 'months',
|
||||
'y': 'years',
|
||||
}
|
||||
|
||||
pp = pprint
|
||||
|
||||
|
||||
def date_from_iso(date: str) -> datetime:
|
||||
return datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
|
||||
|
||||
def time_delta(text: str) -> timedelta:
|
||||
match = re.match(r'(\d+)(s|m|h|mon|w|y)', text)
|
||||
if match:
|
||||
num, unit = match.groups()
|
||||
num = int(num)
|
||||
unit = TIME_UNITS[unit]
|
||||
if unit == 'mon':
|
||||
num *= 4
|
||||
elif unit == 'y':
|
||||
num *= 52
|
||||
return timedelta(**{unit: num})
|
||||
|
||||
|
||||
def parse_int(val: str, select: bool = True) -> tuple:
|
||||
try:
|
||||
val = int(val)
|
||||
if val is not 0:
|
||||
if val < 1:
|
||||
order = 'desc'
|
||||
val *= -1
|
||||
else:
|
||||
order = 'asc'
|
||||
if select:
|
||||
val -= 1
|
||||
return val, order
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def re_generator(low: int = 5, high: int = 20) -> str:
|
||||
return 'R{}'.format(''.join('E' for _ in range(random.randint(low, high))))
|
Reference in New Issue
Block a user