diff --git a/.env-example b/.env-example index 7d0c1ef..7def0a2 100644 --- a/.env-example +++ b/.env-example @@ -1 +1,3 @@ -PASSWORD=password +DATABASE_URI=postgresql://:@:/ +GOOGLE_API_KEY= +PASSWORD= diff --git a/.gitignore b/.gitignore index d81fc0c..14b269f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -*.sqlite .env .idea/ __pycache__/ diff --git a/README.md b/README.md index 13f8cc9..295f121 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,16 @@ -# Todo -* [ ] joke Trigger – Erzählt einen schlechten Witz -* [ ] .seen trigger – returns the last message and date the user was seen in the channel - -# Done -* [x] .help - lists all available commands (builtin) -* [x] .uptime - prints bot uptime (builtin) - -## plugins/admin -* [x] .reload - reloads a plugin or the whole bot - -## plugins/coins -* [x] .btc - prints current btc values -* [x] .eth - prints current eth values - -## plugins/ctcp -* [x] .finger - does a FINGER ctcp -* [x] .ping - does a PING ctcp -* [x] .time - does a TIME ctcp -* [x] .ver - does a VERSION ctcp - -## plugins/database - -## plugins/linux -* [x] useless gnu/linux event -* [x] .kernel - displays current linux kernel info - -## plugins/mcmaniac -* [x] regex event for Mc(.*)iaC which adds a McManiaC -* [x] .mcmaniac - shows a McManiaC (random or by index) - -## plugins/quotes -* [x] .q - prints, adds or removes a quote - -## plugins/rape -* [x] .owe command -* [x] .rape command - -## plugins/seen - -## plugins/tell -* [x] .tell - stores a message for a user and sends it to him when he writes a message in any channel (shows activity) - -## plugins/timer -* [x] .timer - sets a timer for a specified amount of time and a name - -## plugins/urban -* [x] .ud - gets a definition from urban dictionary - -## plugins/useless -* [x] .gay - colors text in rainbow colors -* [x] .hack - prints "hacking..." -* [x] .jn - returns yes or no for a given question -* [x] .kiss - kisses a user -* [x] .rainbow - same as .gay -* [x] .storyofpomfface - .... -* [x] Bier Trigger – nxy schenkt ein kühles blondes an $nick aus -* [x] REEEEEE Trigger – REEEEEEEEEEEEEEEEEEEEEEEEEEEE -* [x] Fucken Trigger – nxy fuckt $nick und tötet $nick anschließend. -* [x] .choose - picks a random entry from a list seperated with commas -* [x] .sudoku/.anhero - kicks the user that executes that command - -## plugins/weather -* [x] .weather - prints the weather for a given location - -## plugins/youtube -* [x] .yt - search for a video on youtube -* [x] youtube url parser - returns video information if a youtube link is posted +# Installation +* Open shell as user nxy will run +* Install virtualenvwrapper and source it + - ```source $(which virtualenvwrapper.sh)``` +* Clone repo + - ```git clone https://gitfap.de/mrhanky/nxy.git``` +* Create virtualenv and install dependencies + - ```mkvirtualenv -a $PWD/nxy -r $PWD/nxy/requirements.txt nxy``` +* Copy ```.env-example``` and insert values in ```.env``` (replace everything wrapped in < and >) + - ```cp .env-example .env``` + - ```vim .env``` +* Leave (auto) activated virtualenv and + - ```deactivate``` +* Copy systemd unit (would recommend the ```/usr/local``` prefix) + - ```sudo mkdir -p /usr/local/lib/systemd/system``` + - ```sudo cp $PWD/nxy/nxy-bot.service /usr/local/lib/systemd/system``` diff --git a/config.json b/config.json index 539716a..5031da7 100644 --- a/config.json +++ b/config.json @@ -13,10 +13,12 @@ "nxy.plugins.admin", "nxy.plugins.coins", "nxy.plugins.ctcp", + "nxy.plugins.isup", "nxy.plugins.linux", "nxy.plugins.mcmaniac", "nxy.plugins.quotes", "nxy.plugins.rape", + "nxy.plugins.regex", "nxy.plugins.seen", "nxy.plugins.tell", "nxy.plugins.timer", diff --git a/nxy-bot.service b/nxy-bot.service new file mode 100644 index 0000000..ea85165 --- /dev/null +++ b/nxy-bot.service @@ -0,0 +1,12 @@ +[Unit] +Description=nxy python irc bot +After=network.target znc.service + +[Service] +Type=simple +User=nxy +WorkingDirectory=$BOTDIR +ExecStart=$VIRTUALENV/bin/python $BOTDIR/nxy/bot.py + +[Install] +WantedBy=multi-user.target diff --git a/nxy/bot.py b/nxy/bot.py index 8d2b23b..8a231fe 100644 --- a/nxy/bot.py +++ b/nxy/bot.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import os -import json import sys +import json from dotenv import load_dotenv from irc3 import IrcBot diff --git a/nxy/plugins/__init__.py b/nxy/plugins/__init__.py index 9d66139..d63575b 100644 --- a/nxy/plugins/__init__.py +++ b/nxy/plugins/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + from irc3 import IrcBot from irc3.plugins.command import Commands diff --git a/nxy/plugins/admin.py b/nxy/plugins/admin.py index 8a15be2..79c2f72 100644 --- a/nxy/plugins/admin.py +++ b/nxy/plugins/admin.py @@ -1,15 +1,19 @@ # -*- coding: utf-8 -*- +import logging + import irc3 -from docopt import Dict as DocoptDict +from docopt import Dict as DocOptDict from irc3.plugins.command import command from irc3.utils import IrcString -from . import MODULE +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): + args: DocOptDict): """Reloads a plugin or the whole bot. %%reload [] @@ -21,3 +25,8 @@ def reload(bot: irc3.IrcBot, mask: IrcString, target: IrcString, else: bot.reload() bot.privmsg(target, 'Reloaded the bot') + + +@irc3.plugin +class Admin(Plugin): + pass diff --git a/nxy/plugins/coins.py b/nxy/plugins/coins.py index f6f1e93..96a3b7b 100644 --- a/nxy/plugins/coins.py +++ b/nxy/plugins/coins.py @@ -62,8 +62,3 @@ class Coins(Plugin): low=result['price']['low'], change=result['price']['change']['percentage'] * 100, volume=result['volume']) - - @staticmethod - def _etherscan(action: str): - return requests.get('https://api.etherscan.io/api?module=stats&action=' - '{action}'.format(action=action)).json()['result'] diff --git a/nxy/plugins/isup.py b/nxy/plugins/isup.py new file mode 100644 index 0000000..448298e --- /dev/null +++ b/nxy/plugins/isup.py @@ -0,0 +1,36 @@ +# -*- 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', + 'nxy.plugins.storage'] + + @command + def isup(self, mask: IrcString, target: IrcString, args: DocOptDict): + """Checks if a address is up. + + %%isup
+ """ + address = args['
'] + if not address.startswith('http://'): + address = 'https://{}'.format(address) + parsed = urlparse(address) + + try: + requests.head(address) + state = 'up' + except requests.ConnectionError: + state = 'down' + return '{scheme}://{address} seems to be {state}'.format(scheme=parsed.scheme, + address=parsed.netloc, + state=state) diff --git a/nxy/plugins/linux.py b/nxy/plugins/linux.py index 58475da..146caa2 100644 --- a/nxy/plugins/linux.py +++ b/nxy/plugins/linux.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import random - import irc3 import feedparser from docopt import Dict as DocOptDict @@ -10,6 +9,7 @@ from irc3.utils import IrcString from . import Plugin + 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 @@ -21,14 +21,18 @@ GNU_LINUX = """I'd Just Like To Interject For A Moment. What you're referring class Linux(Plugin): KERNEL_FEED = 'https://www.kernel.org/feeds/kdist.xml' - @irc3.event(r'(?i)^:\S+ PRIVMSG (?P\S+) :.*(debian|apt|dpkg).*') + @irc3.event(r'(?i)^:\S+ PRIVMSG (?P\S+) :' + r'.*(debian|ubuntu|apt|dpkg).*') def debillian(self, target: str): - if random.randint(0, 12) is 0: - self.bot.privmsg(target, 'REEEEEEEEEEEEEEEEEEEEEEEEEEEEE') + """Annoying RE trigger for debian with variable count of E.""" + if random.randint(0, 3) is 0: + self.bot.privmsg(target, 'R{}'.format(''.join( + 'E' for _ in range(random.randint(5, 20))))) @irc3.event(r'(?i)^:\S+ PRIVMSG (?P\S+) :' r'.*(?\S+) PRIVMSG (?P#\S+) :s/' + r'(?P(?:[^/\\]|\\.)*)/' + r'(?P(?:.*?))' + r'(?:/ ?(?P.*))?$') + 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 = re.sub(search, '\x02{}\x0F'.format(replace), old) + print(msg) + if old != msg: + self.bot.privmsg(target, '<{nick}> {msg}'.format(nick=nick, + msg=msg)) + + @irc3.event(r'(?i)^:(?P\S+) PRIVMSG (?P#\S+) :(?P.*)$') + 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() diff --git a/nxy/plugins/storage.py b/nxy/plugins/storage.py index 17a958b..5b47da7 100644 --- a/nxy/plugins/storage.py +++ b/nxy/plugins/storage.py @@ -14,5 +14,5 @@ class Storage(Plugin): super().__init__(bot) # Create database connection self.con = psycopg2.connect(os.environ['DATABASE_URI']) - # Create database cursor (with dict favtory to access rows by name) + # Create database cursor (with dict factory to access rows by name) self.cur = self.con.cursor(cursor_factory=DictCursor) diff --git a/nxy/plugins/useless.py b/nxy/plugins/useless.py index cd4ed8c..9dd0f04 100644 --- a/nxy/plugins/useless.py +++ b/nxy/plugins/useless.py @@ -8,7 +8,7 @@ from irc3.utils import IrcString from . import DatabasePlugin -RAINBOW = (5, 3, 7, 8, 9, 3, 10, 12, 2, 6, 13) +RAINBOW = (5, 7, 8, 9, 3, 10, 12, 2, 6, 13) RAINBOW_LEN = len(RAINBOW) @@ -16,6 +16,18 @@ RAINBOW_LEN = len(RAINBOW) class Useless(DatabasePlugin): requires = ['irc3.plugins.command', 'nxy.plugins.storage'] + WOAH = ( + 'woah', + 'woah indeed', + 'woah woah woah!', + 'keep your woahs to yourself', + ) + + @irc3.event(r'(?i)^:\S+ PRIVMSG (?P\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\S+) :(?Phuehuehue)$') def huehuehue(self, target: str, msg: str): @@ -44,7 +56,6 @@ class Useless(DatabasePlugin): %%kill [] """ - nick = args.get('') or mask.nick self.cur.execute(''' select item @@ -55,7 +66,8 @@ class Useless(DatabasePlugin): limit 1 ''') - return self.cur.fetchone()['item'].format(nick=nick) + self.bot.action(target, self.cur.fetchone()['item'] + .format(nick=args.get('') or mask.nick)) @command def yiff(self, mask: IrcString, target: IrcString, args: DocOptDict): @@ -63,7 +75,6 @@ class Useless(DatabasePlugin): %%yiff [] """ - nick = args.get('') or mask.nick self.cur.execute(''' select item @@ -74,15 +85,16 @@ class Useless(DatabasePlugin): limit 1 ''') - return self.cur.fetchone()['item'].format(nick=nick) + self.bot.action(target, self.cur.fetchone()['item'] + .format(nick=args.get('') or mask.nick)) @command def waifu(self, mask: IrcString, target: IrcString, args: DocOptDict): """Get waifu for a user. - %%waifu [] + %%waifu [...] """ - nick = args.get('') or mask.nick + nick = ' '.join(args.get('')) or mask.nick if nick.startswith('='): waifu = nick[1:] @@ -118,12 +130,12 @@ class Useless(DatabasePlugin): def husbando(self, mask: IrcString, target: IrcString, args: DocOptDict): """Get husbando for a user. - %%husbando [] + %%husbando [...] """ - nick = args.get('') or mask.nick + nick = ' '.join(args.get('')) or mask.nick if nick.startswith('='): - husbando = nick[1:] + nick = nick[1:] self.cur.execute(''' insert into @@ -132,10 +144,10 @@ class Useless(DatabasePlugin): (%s, %s) on conflict (nick) do update set husbando = excluded.husbando - ''', [mask.nick, husbando]) + ''', [mask.nick, nick]) self.bot.notice(mask.nick, 'Husbando set to: {husbando}' - .format(husbando=husbando)) + .format(husbando=nick)) else: self.cur.execute(''' select @@ -206,7 +218,7 @@ class Useless(DatabasePlugin): %%bier [] """ - self.bot.action(target, 'schenkt ein kühles blondes an {nick} aus.' + self.bot.action(target, 'schenkt ein kühles Blondes an {nick} aus.' .format(nick=args.get('') or mask.nick)) @command @@ -276,7 +288,6 @@ class Useless(DatabasePlugin): return ' '.join([self._rainbow(i, word) for i, word in enumerate(args[''])]) - @staticmethod - def _rainbow(i, char): + def _rainbow(self, i, char): return '\x03{color}{char}'.format(color=RAINBOW[i % RAINBOW_LEN], char=char) diff --git a/schema.sql b/schema.sql index 3ee9028..4814f73 100644 --- a/schema.sql +++ b/schema.sql @@ -63,3 +63,20 @@ create table if not exists yiffs ( item text not null, unique (item) ); + +create table if not exists last_messages ( + id serial primary key, + nick varchar(30) not null, + host varchar(255) not null, + channel varchar(32) not null, + item text not null, + unique (nick, channel) +); + +select + item + from + last_messages + where + nick = lower('m') + and channel = lower('nxy-dev');