commit 4d2156be1c8568733a5136ee8857ad6ea1f1d610 Author: root Date: Wed May 1 12:30:41 2019 +0000 Watch For New Channels, JOIN, and +a. Basic Nick_Commands for channel Listing diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba74660 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..7d1cd07 --- /dev/null +++ b/COPYING @@ -0,0 +1,23 @@ + + Eiffel Forum License, version 2 + + 1. Permission is hereby granted to use, copy, modify and/or + distribute this package, provided that: + * copyright notices are retained unchanged, + * any distribution of this package, whether modified or not, + includes this license text. + 2. Permission is hereby also granted to distribute binary programs + which depend on this package. If the binary program depends on a + modified version of this package, you are encouraged to publicly + release the modified version of this package. + +*********************** + +THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT WARRANTY. ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE TO ANY PARTY FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THIS PACKAGE. + +*********************** diff --git a/Installation Files/requirements-os.sh b/Installation Files/requirements-os.sh new file mode 100644 index 0000000..47e988a --- /dev/null +++ b/Installation Files/requirements-os.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# This install was run on Proxmox Ubuntu 18.10 + +apt update +apt upgrade -y + +dpkg-reconfigure locales + +apt install -y git +apt install -y python3-dev python3-pip +apt install -y python3-enchant libaspell-dev + +wget https://raw.githubusercontent.com/sopel-irc/sopel/master/requirements.txt +pip3 install -r requirements.txt +rm requirements.txt +wget https://raw.githubusercontent.com/sopel-irc/sopel/master/dev-requirements.txt +pip3 install -r dev-requirements.txt +rm dev-requirements.txt +wget https://raw.githubusercontent.com/SpiceBot/SpiceBot/development/Installation%20Files/requirements.txt +pip3 install -r requirements.txt +rm requirements.txt +pip3 install sopel + +mkdir /usr/local/lib/python3.6/dist-packages/sopel/modules/stock +mv /usr/local/lib/python3.6/dist-packages/sopel/modules/* /usr/local/lib/python3.6/dist-packages/sopel/modules/stock/ + +useradd sopel +passwd sopel +mkdir /home/sopel +chown sopel:sopel /home/sopel +usermod -aG sudo sopel + +git clone https://github.com/SpiceBot/SpiceBot.git /home/sopel/SpiceBot +cd /home/sopel/SpiceBot +git checkout dev + +cp /home/sopel/SpiceBot/System-Files/Config/SpiceBot.cfg /home/sopel/SpiceBot.cfg +cp /home/sopel/SpiceBot/System-Files/systemd/SpiceBot.service /lib/systemd/system/SpiceBot.service +systemctl enable SpiceBot.service diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..60ca266 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include NEWS +include COPYING +include README.md +include *requirements.txt + +recursive-include tests * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..47140c4 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# SpiceBot + +SpiceBot aims to run SpiceBot as an IRC service + +# This code is bad, and the author doesn't feel bad +Use at your own risk. Problems are not my faultâ„¢. + +# Installation +```` +git clone https://github.com/SpiceBot/SpiceBot.git +cd SpiceBot +pip3 install . +```` diff --git a/System-Files/Config/SpiceBotSERV.cfg b/System-Files/Config/SpiceBotSERV.cfg new file mode 100644 index 0000000..c6651a5 --- /dev/null +++ b/System-Files/Config/SpiceBotSERV.cfg @@ -0,0 +1,16 @@ +[core] +name = SpiceBot +nick = SpiceBot +user = SpiceBot/SpiceBot +auth_method = server +auth_password = ********** +admins = deathbybandaid,dysonparkes +host = 192.168.5.10 +use_ssl = false +port = 5000 +owner = deathbybandaid +channels = #spicebot +prefix = \.|! +homedir = /home/sopel/.sopel/ +db_filename = /home/sopel/.sopel/SpiceBot.db +extra = diff --git a/System-Files/systemd/SpiceBotSERV.service b/System-Files/systemd/SpiceBotSERV.service new file mode 100644 index 0000000..7a17cb7 --- /dev/null +++ b/System-Files/systemd/SpiceBotSERV.service @@ -0,0 +1,17 @@ +[Unit] +Description=Sopel IRC bot +Documentation=http://sopel.chat/ +After=network.target + +[Service] +Type=simple +User=sopel +PIDFile=/run/sopel/sopel-sopel.pid +ExecStart=/usr/local/bin/sopel -c /home/sopel/.sopel/SpiceBot.cfg +Restart=on-failure +RestartPreventExitStatus=2 +RestartSec=30 +Environment=LC_ALL=en_US.UTF-8 + +[Install] +WantedBy=multi-user.target diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..25327ab --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +sopel>=6.0,<7 +spicemanip>=0.1.6 +gitpython +sopel-modules.osd diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..6477787 --- /dev/null +++ b/setup.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +from __future__ import print_function +import os +import sys +from setuptools import setup, find_packages + + +if __name__ == '__main__': + print('Sopel does not correctly load modules installed with setup.py ' + 'directly. Please use "pip install .", or add {}/sopel_modules to ' + 'core.extra in your config.'.format( + os.path.dirname(os.path.abspath(__file__))), + file=sys.stderr) + +with open('README.md') as readme_file: + readme = readme_file.read() + +with open('NEWS') as history_file: + history = history_file.read() + +with open('requirements.txt') as requirements_file: + requirements = [req for req in requirements_file.readlines()] + +with open('dev-requirements.txt') as dev_requirements_file: + dev_requirements = [req for req in dev_requirements_file.readlines()] + + +setup( + name='sopel_modules.SpiceBot', + version='0.1.0', + description='SpiceBot aims to run SpiceBot as an IRC service', + long_description=readme + '\n\n' + history, + author='Sam Zick', + author_email='sam@deathbybandaid.net', + url='https://github.com/SpiceBot/SpiceBot', + packages=find_packages('.'), + namespace_packages=['sopel_modules'], + include_package_data=True, + install_requires=requirements, + tests_require=dev_requirements, + test_suite='tests', + license='Eiffel Forum License, version 2', +) diff --git a/sopel_modules/SpiceBot_Channels/Channels.py b/sopel_modules/SpiceBot_Channels/Channels.py new file mode 100644 index 0000000..997a2f1 --- /dev/null +++ b/sopel_modules/SpiceBot_Channels/Channels.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python +# coding=utf-8 +from __future__ import unicode_literals, absolute_import, print_function, division + +# sopel imports +import sopel.module +from sopel.tools import stderr +from sopel.config.types import StaticSection, ValidatedAttribute +from sopel.module import OP, ADMIN, VOICE, OWNER, HALFOP +HOP = HALFOP + +import time +import re + +import spicemanip + +import sopel_modules.osd + +from sopel_modules.SpiceBot_SBTools import sopel_triggerargs, inlist + + +class SpiceBot_Channels_MainSection(StaticSection): + announcenew = ValidatedAttribute('announcenew', default=False) + joinall = ValidatedAttribute('joinall', default=False) + operadmin = ValidatedAttribute('operadmin', default=False) + + +def configure(config): + config.define_section("SpiceBot_Channels", SpiceBot_Channels_MainSection, validate=False) + config.SpiceBot_Channels.configure_setting('announcenew', 'SpiceBot_Channels Announce New Channels') + config.SpiceBot_Channels.configure_setting('announcenew', 'SpiceBot_Channels JOIN New Channels') + config.SpiceBot_Channels.configure_setting('announcenew', 'SpiceBot_Channels OPER ADMIN MODE') + + +def setup(bot): + stderr("[SpiceBot_Channels] Initial Setup processing...") + + bot.config.define_section("SpiceBot_Channels", SpiceBot_Channels_MainSection, validate=False) + + bot.memory['SpiceBot_Channels'] = dict() + bot.memory['SpiceBot_Channels']['channels'] = dict() + bot.memory['SpiceBot_Channels']['InitialProcess'] = False + bot.memory['SpiceBot_Channels']['ProcessLock'] = False + + +@sopel.module.event('001') +@sopel.module.rule('.*') +def trigger_channel_list_initial(bot, trigger): + + # Unkickable + bot.write(('SAMODE', bot.nick, '+q')) + + bot.write(['LIST']) + bot.memory['SpiceBot_Channels']['ProcessLock'] = True + + stderr("[SpiceBot_Channels] Initial Channel list populating...") + starttime = time.time() + + while not bot.memory['SpiceBot_Channels']['InitialProcess']: + timesince = time.time() - starttime + if timesince < 60: + pass + else: + stderr("[SpiceBot_Channels] Initial Channel list populating Timed Out.") + bot.memory['SpiceBot_Channels']['InitialProcess'] = True + + channel_list_current(bot) + foundchannelcount = len(bot.memory['SpiceBot_Channels']['channels'].keys()) + stderr("[SpiceBot_Channels] Channel listing finished! " + str(foundchannelcount) + " channel(s) found.") + + # JOIN + if bot.config.SpiceBot_Channels.joinall: + for channel in bot.memory['SpiceBot_Channels']['channels'].keys(): + if channel not in bot.channels.keys(): + bot.write(('JOIN', bot.nick, bot.memory['SpiceBot_Channels']['channels'][channel]['name'])) + if channel not in bot.channels.keys(): + bot.write(('SAJOIN', bot.nick, bot.memory['SpiceBot_Channels']['channels'][channel]['name'])) + + # Chan ADMIN +a + for channel in bot.channels.keys(): + if bot.config.SpiceBot_Channels.operadmin: + if not bot.channels[channel].privileges[bot.nick] < ADMIN: + bot.write(('SAMODE', channel, "+a", bot.nick)) + channel_privs(bot, channel) + + if "*" in bot.memory['SpiceBot_Channels']['channels']: + del bot.memory['SpiceBot_Channels']['channels']["*"] + + while True: + time.sleep(1800) + oldlist = list(bot.memory['SpiceBot_Channels']['channels'].keys()) + bot.write(['LIST']) + bot.memory['SpiceBot_Channels']['ProcessLock'] = True + + while bot.memory['SpiceBot_Channels']['ProcessLock']: + pass + + newlist = [item.lower() for item in oldlist if item.lower() not in bot.memory['SpiceBot_Channels']['channels']] + if "*" in newlist: + newlist.remove("*") + if len(newlist) and bot.config.SpiceBot_Channels.announcenew: + bot.osd(["The Following channel(s) are new:", spicemanip.main(newlist, 'andlist')], bot.channels.keys()) + + # JOIN + if bot.config.SpiceBot_Channels.joinall: + for channel in bot.memory['SpiceBot_Channels']['channels'].keys(): + if channel not in bot.channels.keys(): + bot.write(('JOIN', bot.nick, bot.memory['SpiceBot_Channels']['channels'][channel]['name'])) + if channel not in bot.channels.keys(): + bot.write(('SAJOIN', bot.nick, bot.memory['SpiceBot_Channels']['channels'][channel]['name'])) + + # Chan ADMIN +a + for channel in bot.channels.keys(): + if bot.config.SpiceBot_Channels.operadmin: + if not bot.channels[channel].privileges[bot.nick] < ADMIN: + bot.write(('SAMODE', channel, "+a", bot.nick)) + channel_privs(bot, channel) + + if "*" in bot.memory['SpiceBot_Channels']['channels']: + del bot.memory['SpiceBot_Channels']['channels']["*"] + + +@sopel.module.event('321') +@sopel.module.rule('.*') +def watch_chanlist_start(bot, trigger): + bot.memory['SpiceBot_Channels']['ProcessLock'] = True + + +@sopel.module.event('322') +@sopel.module.rule('.*') +def watch_chanlist_populate(bot, trigger): + channel, _, topic = trigger.args[1:] + if channel.lower() not in bot.memory['SpiceBot_Channels']['channels']: + bot.memory['SpiceBot_Channels']['channels'][channel.lower()] = dict() + bot.memory['SpiceBot_Channels']['channels'][channel.lower()]['name'] = channel + bot.memory['SpiceBot_Channels']['channels'][channel.lower()]['topic'] = topic_compile(topic) + bot.memory['SpiceBot_Channels']['ProcessLock'] = True + + +@sopel.module.event('323') +@sopel.module.rule('.*') +def watch_chanlist_complete(bot, trigger): + bot.memory['SpiceBot_Channels']['ProcessLock'] = False + if not bot.memory['SpiceBot_Channels']['InitialProcess']: + bot.memory['SpiceBot_Channels']['InitialProcess'] = True + + +@sopel.module.nickname_commands('channels', 'channel') +def nickname_comand_chanstats(bot, trigger): + triggerargs = sopel_triggerargs(bot, trigger, 'nickname_command') + + if not len(triggerargs): + commandused = 'list' + else: + commandused = spicemanip.main(triggerargs, 1).lower() + + triggerargs = spicemanip.main(triggerargs, '2+', 'list') + + channel_list_current(bot) + + if commandused == 'list': + chanlist = spicemanip.main(bot.channels.keys(), 'andlist') + bot.osd("You can find me in " + chanlist) + return + + elif commandused == 'total': + botcount = len(bot.channels.keys()) + servercount = len(bot.memory['SpiceBot_Channels']['channels'].keys()) + bot.osd("I am in " + str(botcount) + " of " + str(servercount) + " channel(s) available on this server.") + return + + elif commandused == 'random': + channel = spicemanip.main(bot.memory['SpiceBot_Channels']['channels'], 'random') + topic = bot.memory['SpiceBot_Channels']['channels'][channel]['topic'] + msg = ["Random channel for you: {}.".format(bot.memory['SpiceBot_Channels']['channels'][channel]['name'])] + if topic and not topic.isspace(): + msg.append("The topic is: {}".format(topic)) + else: + msg.append("Its topic is empty.") + bot.osd(msg) + return + + elif commandused == 'update': + if not trigger.admin: + bot.osd("You do not have permission to update the channel listing.") + return + bot.write(['LIST']) + bot.osd(["[SpiceBot_Channels]", "I am now updating the channel listing for this server."]) + bot.memory['SpiceBot_Channels']['ProcessLock'] = True + while bot.memory['SpiceBot_Channels']['ProcessLock']: + pass + foundchannelcount = len(bot.memory['SpiceBot_Channels']['channels'].keys()) + bot.osd("[SpiceBot_Channels]", "Channel listing finished!", str(foundchannelcount) + " channel(s) found.") + return + + elif commandused == 'topic': + if not len(triggerargs): + bot.osd("Channel name input missing.") + return + channel = spicemanip.main(triggerargs, 1) + if not inlist(bot, channel.lower(), bot.memory['SpiceBot_Channels']['channels'].keys()): + bot.osd("Channel name {} not valid.".format(channel)) + return + topic = bot.memory['SpiceBot_Channels']['channels'][channel.lower()]['topic'] + channel = bot.memory['SpiceBot_Channels']['channels'][channel.lower()]['name'] + bot.osd("Topic for {}: {}".format(channel, topic)) + return + + # OP list + if commandused == 'op': + channel = spicemanip.main(triggerargs, 1) + if not inlist(bot, channel.lower(), bot.memory['SpiceBot_Channels']['channels'].keys()): + bot.osd("Channel name {} not valid.".format(channel)) + return + if not inlist(bot, channel.lower(), bot.channels.keys()): + bot.osd("I need to be in {} to see nick privileges.".format(channel)) + return + channel_privs(bot, channel) + dispmsg = [] + if not len(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()]): + dispmsg.append("There are no Channel " + commandused.upper() + "s for " + str(channel)) + else: + oplist = spicemanip.main(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()], 'andlist') + dispmsg.append("Channel " + commandused.upper() + "s for " + str(channel) + " are: " + oplist) + bot.osd(dispmsg, trigger.nick, 'notice') + return + + # HOP list + if commandused == 'hop': + channel = spicemanip.main(triggerargs, 1) + if not inlist(bot, channel.lower(), bot.memory['SpiceBot_Channels']['channels'].keys()): + bot.osd("Channel name {} not valid.".format(channel)) + return + if not inlist(bot, channel.lower(), bot.channels.keys()): + bot.osd("I need to be in {} to see nick privileges.".format(channel)) + return + channel_privs(bot, channel) + dispmsg = [] + if not len(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()]): + dispmsg.append("There are no Channel " + commandused.upper() + "s for " + str(channel)) + else: + oplist = spicemanip.main(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()], 'andlist') + dispmsg.append("Channel " + commandused.upper() + "s for " + str(channel) + " are: " + oplist) + bot.osd(dispmsg, trigger.nick, 'notice') + return + + # Voice List + if commandused == 'voice': + channel = spicemanip.main(triggerargs, 1) + if not inlist(bot, channel.lower(), bot.memory['SpiceBot_Channels']['channels'].keys()): + bot.osd("Channel name {} not valid.".format(channel)) + return + if not inlist(bot, channel.lower(), bot.channels.keys()): + bot.osd("I need to be in {} to see nick privileges.".format(channel)) + return + channel_privs(bot, channel) + dispmsg = [] + if not len(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()]): + dispmsg.append("There are no Channel " + commandused.upper() + "s for " + str(channel)) + else: + oplist = spicemanip.main(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()], 'andlist') + dispmsg.append("Channel " + commandused.upper() + "s for " + str(channel) + " are: " + oplist) + bot.osd(dispmsg, trigger.nick, 'notice') + return + + # owner List + if commandused == 'owner': + channel = spicemanip.main(triggerargs, 1) + if not inlist(bot, channel.lower(), bot.memory['SpiceBot_Channels']['channels'].keys()): + bot.osd("Channel name {} not valid.".format(channel)) + return + if not inlist(bot, channel.lower(), bot.channels.keys()): + bot.osd("I need to be in {} to see nick privileges.".format(channel)) + return + channel_privs(bot, channel) + dispmsg = [] + if not len(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()]): + dispmsg.append("There are no Channel " + commandused.upper() + "s for " + str(channel)) + else: + oplist = spicemanip.main(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()], 'andlist') + dispmsg.append("Channel " + commandused.upper() + "s for " + str(channel) + " are: " + oplist) + bot.osd(dispmsg, trigger.nick, 'notice') + return + + # admin List + if commandused == 'admin': + channel = spicemanip.main(triggerargs, 1) + if not inlist(bot, channel.lower(), bot.memory['SpiceBot_Channels']['channels'].keys()): + bot.osd("Channel name {} not valid.".format(channel)) + return + if not inlist(bot, channel.lower(), bot.channels.keys()): + bot.osd("I need to be in {} to see nick privileges.".format(channel)) + return + channel_privs(bot, channel) + dispmsg = [] + if not len(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()]): + dispmsg.append("There are no Channel " + commandused.upper() + "s for " + str(channel)) + else: + oplist = spicemanip.main(bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][commandused.upper()], 'andlist') + dispmsg.append("Channel " + commandused.upper() + "s for " + str(channel) + " are: " + oplist) + bot.osd(dispmsg, trigger.nick, 'notice') + return + + # Users List + if commandused == 'users': + channel = spicemanip.main(triggerargs, 1) + if not inlist(bot, channel.lower(), bot.memory['SpiceBot_Channels']['channels'].keys()): + bot.osd("Channel name {} not valid.".format(channel)) + return + if not inlist(bot, channel.lower(), bot.channels.keys()): + bot.osd("I need to be in {} to see user list.".format(channel)) + return + dispmsg = [] + if not len(bot.channels[channel].privileges.keys()): + dispmsg.append("There are no Channel users for " + str(channel)) + else: + userslist = spicemanip.main(bot.channels[channel].privileges.keys(), 'andlist') + dispmsg.append("Channel users for " + str(channel) + " are: " + userslist) + bot.osd(dispmsg, trigger.nick, 'notice') + return + + +def channel_list_current(bot): + newlist = [item for item in bot.channels.keys() if item.lower() not in bot.memory['SpiceBot_Channels']['channels']] + for channel in newlist: + topic = bot.channels[channel].topic + bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()] = dict() + bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()]['name'] = str(channel) + bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()]['topic'] = topic_compile(topic) + channel_privs(bot, channel) + + if "*" in bot.memory['SpiceBot_Channels']: + bot.memory['SpiceBot_Channels'].remove("*") + + +def topic_compile(topic): + actual_topic = re.compile(r'^\[\+[a-zA-Z]+\] (.*)') + topic = re.sub(actual_topic, r'\1', topic) + return topic + + +def channel_privs(bot, channel): + + if channel not in bot.memory['SpiceBot_Channels']['channels'].keys(): + topic = bot.channels[channel].topic + bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()] = dict() + bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()]['name'] = str(channel) + bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()]['topic'] = topic_compile(topic) + + for chan in bot.memory['SpiceBot_Channels']['channels'].keys(): + for privtype in ['VOICE', 'HOP', 'OP', 'ADMIN', 'OWNER']: + if privtype not in bot.memory['SpiceBot_Channels']['channels'][str(chan).lower()].keys(): + bot.memory['SpiceBot_Channels']['channels'][str(chan).lower()][privtype] = [] + + for user in bot.channels[channel].privileges.keys(): + try: + privnum = bot.channels[channel].privileges[user] or 0 + except KeyError: + privnum = 0 + + for privtype in ['VOICE', 'HOP', 'OP', 'ADMIN', 'OWNER']: + if privnum == eval(privtype) or (privnum >= eval(privtype) and privtype == 'OWNER'): + if user not in bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][privtype]: + bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][privtype].append(user) + else: + if user in bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][privtype]: + bot.memory['SpiceBot_Channels']['channels'][str(channel).lower()][privtype].remove(user) diff --git a/sopel_modules/SpiceBot_Channels/__init__.py b/sopel_modules/SpiceBot_Channels/__init__.py new file mode 100644 index 0000000..fe6cf28 --- /dev/null +++ b/sopel_modules/SpiceBot_Channels/__init__.py @@ -0,0 +1,6 @@ +# coding=utf-8 +"""Event Parsing from IRC +""" +from __future__ import unicode_literals, absolute_import, print_function, division + +from .Channels import * diff --git a/sopel_modules/SpiceBot_Database_Addons/Databaseaddons.py b/sopel_modules/SpiceBot_Database_Addons/Databaseaddons.py new file mode 100644 index 0000000..f1f135a --- /dev/null +++ b/sopel_modules/SpiceBot_Database_Addons/Databaseaddons.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# coding=utf-8 +from __future__ import unicode_literals, absolute_import, print_function, division + +# sopel imports +import sopel.module +from sopel.tools import stderr, Identifier +from sopel.db import _deserialize, SopelDB + +import json + + +def setup(bot): + # Inject Database Functions + stderr("[SpiceBot_Databaseaddons] Implanting Database functions into bot.") + SopelDB.reset_nick_value = SopelDBCache.reset_nick_value + SopelDB.adjust_nick_value = SopelDBCache.adjust_nick_value + SopelDB.reset_channel_value = SopelDBCache.reset_channel_value + SopelDB.adjust_channel_value = SopelDBCache.adjust_channel_value + SopelDB._create_table = SopelDBCache._create_table + + +class SopelDBCache: + + """Dynamic Table Creation""" + + def _create_table(self, tablename): + self.execute('CREATE TABLE IF NOT EXISTS ? (mainid STRING, key STRING, value STRING, PRIMARY KEY (mainid, key))', tablename) + + """Nicks""" + + def reset_nick_value(self, nick, key): + """Resets the value for a given key to be associated with the nick.""" + nick = Identifier(nick) + nick_id = self.get_nick_id(nick) + self.execute('DELETE FROM nick_values WHERE nick_id = ? AND key = ?', [nick_id, key]) + + def adjust_nick_value(self, nick, key, value): + """Adjusts the value for a given key to be associated with the nick.""" + nick = Identifier(nick) + result = self.execute( + 'SELECT value FROM nicknames JOIN nick_values ' + 'ON nicknames.nick_id = nick_values.nick_id ' + 'WHERE slug = ? AND key = ?', + [nick.lower(), key] + ).fetchone() + if result is not None: + result = result[0] + current_value = _deserialize(result) + value = current_value + value + value = json.dumps(value, ensure_ascii=False) + nick_id = self.get_nick_id(nick) + self.execute('INSERT OR REPLACE INTO nick_values VALUES (?, ?, ?)', + [nick_id, key, value]) + + """Channels""" + + def reset_channel_value(self, channel, key): + """Resets the value for a given key to be associated with a channel.""" + channel = Identifier(channel).lower() + self.execute('DELETE FROM channel_values WHERE channel = ? AND key = ?', [channel, key]) + + def adjust_channel_value(self, channel, key, value): + """Adjusts the value for a given key to be associated with the channel.""" + channel = Identifier(channel).lower() + result = self.execute( + 'SELECT value FROM channel_values WHERE channel = ? AND key = ?', + [channel, key] + ).fetchone() + if result is not None: + result = result[0] + current_value = _deserialize(result) + value = current_value + value + value = json.dumps(value, ensure_ascii=False) + self.execute('INSERT OR REPLACE INTO channel_values VALUES (?, ?, ?)', + [channel, key, value]) diff --git a/sopel_modules/SpiceBot_Database_Addons/__init__.py b/sopel_modules/SpiceBot_Database_Addons/__init__.py new file mode 100644 index 0000000..31715ae --- /dev/null +++ b/sopel_modules/SpiceBot_Database_Addons/__init__.py @@ -0,0 +1,6 @@ +# coding=utf-8 +"""Useful miscellaneous tools and shortcuts for SpiceBot Sopel modules +""" +from __future__ import unicode_literals, absolute_import, print_function, division + +from .Databaseaddons import * diff --git a/sopel_modules/SpiceBot_Database_Cache/DatabaseCache.py b/sopel_modules/SpiceBot_Database_Cache/DatabaseCache.py new file mode 100644 index 0000000..bc9f133 --- /dev/null +++ b/sopel_modules/SpiceBot_Database_Cache/DatabaseCache.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# coding=utf-8 +from __future__ import unicode_literals, absolute_import, print_function, division + +# sopel imports +import sopel.module +from sopel.tools import stderr + + +def setup(bot): + stderr("[SpiceBot_DatabaseCache] Setting up Database Cache.") + bot.memory['SpiceBot_DatabaseCache'] = dict() diff --git a/sopel_modules/SpiceBot_Database_Cache/__init__.py b/sopel_modules/SpiceBot_Database_Cache/__init__.py new file mode 100644 index 0000000..212ae54 --- /dev/null +++ b/sopel_modules/SpiceBot_Database_Cache/__init__.py @@ -0,0 +1,6 @@ +# coding=utf-8 +"""Useful miscellaneous tools and shortcuts for SpiceBot Sopel modules +""" +from __future__ import unicode_literals, absolute_import, print_function, division + +from .DatabaseCache import * diff --git a/sopel_modules/SpiceBot_Database_Tools/DatabaseTools.py b/sopel_modules/SpiceBot_Database_Tools/DatabaseTools.py new file mode 100644 index 0000000..816c56f --- /dev/null +++ b/sopel_modules/SpiceBot_Database_Tools/DatabaseTools.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# coding=utf-8 +from __future__ import unicode_literals, absolute_import, print_function, division + +# sopel imports +import sopel.module + + +"""Dyanmic Table Creation""" + + +def db_create_table(bot, tablename): + bot.db._create_table(tablename) + + +"""Database Direct""" + + +# Get a value +def db_get_nick_value(bot, nick, key): + database_value = bot.db.get_nick_value(nick, key) or None + return database_value + + +# set a value +def db_set_nick_value(bot, nick, key, value): + bot.db.set_nick_value(nick, key, value) + + +# DELETE a value +def db_reset_value(bot, nick, key): + bot.db.reset_nick_value(nick, key) + + +# add or subtract from current value +def db_adjust_value(bot, nick, key, value): + bot.db.adjust_nick_value(nick, key, value) + + +# list stored in database, add or remove elements +def db_adjust_list(bot, nick, entries, key, adjustmentdirection): + if not isinstance(entries, list): + entries = [entries] + adjustlist = db_get_nick_value(bot, nick, key) or [] + adjustlistnew = [] + for x in adjustlist: + adjustlistnew.append(x) + db_reset_value(bot, nick, key) + adjustlist = [] + if adjustmentdirection == 'add': + for y in entries: + if y not in adjustlistnew: + adjustlistnew.append(y) + elif adjustmentdirection == 'del': + for y in entries: + if y in adjustlistnew: + adjustlistnew.remove(y) + for x in adjustlistnew: + if x not in adjustlist: + adjustlist.append(x) + if adjustlist == []: + db_reset_value(bot, nick, key) + else: + db_set_nick_value(bot, nick, key, adjustlist) diff --git a/sopel_modules/SpiceBot_Database_Tools/__init__.py b/sopel_modules/SpiceBot_Database_Tools/__init__.py new file mode 100644 index 0000000..6cd3826 --- /dev/null +++ b/sopel_modules/SpiceBot_Database_Tools/__init__.py @@ -0,0 +1,6 @@ +# coding=utf-8 +"""Useful miscellaneous tools and shortcuts for SpiceBot Sopel modules +""" +from __future__ import unicode_literals, absolute_import, print_function, division + +from .DatabaseTools import * diff --git a/sopel_modules/SpiceBot_SBTools/__init__.py b/sopel_modules/SpiceBot_SBTools/__init__.py new file mode 100644 index 0000000..f256903 --- /dev/null +++ b/sopel_modules/SpiceBot_SBTools/__init__.py @@ -0,0 +1,44 @@ +# coding=utf-8 +"""Useful miscellaneous tools and shortcuts for SpiceBot Sopel modules +""" +from __future__ import unicode_literals, absolute_import, print_function, division + +import collections + +import spicemanip + + +def sopel_triggerargs(bot, trigger, command_type): + triggerargs = [] + + if len(trigger.args) > 1: + triggerargs = spicemanip.main(trigger.args[1], 'create') + triggerargs = spicemanip.main(triggerargs, 'create') + + if command_type in ['module_command']: + triggerargs = spicemanip.main(triggerargs, '2+', 'list') + elif command_type in ['nickname_command']: + triggerargs = spicemanip.main(triggerargs, '3+', 'list') + + return triggerargs + + +def inlist(bot, searchterm, searchlist): + + # verify we are searching a list + if isinstance(searchlist, collections.abc.KeysView) or isinstance(searchlist, dict): + searchlist = [x for x in searchlist] + if not isinstance(searchlist, list): + searchlist = [searchlist] + rebuildlist = [] + for searchitem in searchlist: + rebuildlist.append(str(searchitem)) + + searchterm = str(searchterm) + + if searchterm in rebuildlist: + return True + elif searchterm.lower() in [searching.lower() for searching in rebuildlist]: + return True + else: + return False diff --git a/sopel_modules/__init__.py b/sopel_modules/__init__.py new file mode 100644 index 0000000..de40ea7 --- /dev/null +++ b/sopel_modules/__init__.py @@ -0,0 +1 @@ +__import__('pkg_resources').declare_namespace(__name__) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..9bad579 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/tests/test_cookiecutter.py b/tests/test_cookiecutter.py new file mode 100644 index 0000000..f5e0ca5 --- /dev/null +++ b/tests/test_cookiecutter.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# coding=utf-8 +from __future__ import unicode_literals, absolute_import, division, print_function + +import unittest + +from sopel_modules.SpiceBot import * + +class TestSpiceBot(unittest.TestCase): + def setUp(self): + pass + + def testSomething(self): + pass