Compare commits
57 commits
refresh
...
ChaosChemn
Author | SHA1 | Date | |
---|---|---|---|
![]() |
358b415566 | ||
![]() |
3e1ea6ac4e | ||
![]() |
48b627ad4b | ||
![]() |
1f1ff649c9 | ||
8344fe7693 | |||
a6992d5d1a | |||
![]() |
21db4efd98 | ||
![]() |
8941d59dc2 | ||
09ce708709 | |||
![]() |
29cc95f0ad | ||
4a02f545ea | |||
![]() |
28176ad2ad | ||
![]() |
651d4a953e | ||
![]() |
80711e1278 | ||
a88d5ef0bd | |||
827c5c73e8 | |||
![]() |
580415ed76 | ||
c562a2c7ae | |||
![]() |
4b5e86c237 | ||
![]() |
11687a9ad0 | ||
296de07e14 | |||
dfbead6946 | |||
![]() |
e30eca5ebd | ||
![]() |
2a3c9f52dc | ||
![]() |
f88c3267d4 | ||
![]() |
2b18eaac5a | ||
0650046d18 | |||
![]() |
4e386c9008 | ||
![]() |
05bfb9099a | ||
![]() |
bfa3fb6e74 | ||
![]() |
d415d0fc69 | ||
![]() |
433d97d276 | ||
![]() |
121715115a | ||
![]() |
620de651ce | ||
![]() |
3f205eaafa | ||
![]() |
59cd8380d2 | ||
![]() |
7cce9bf27e | ||
![]() |
0ba2001b62 | ||
![]() |
63fc042027 | ||
![]() |
9421d8160d | ||
![]() |
4b4ac2d918 | ||
![]() |
05a68faf1d | ||
![]() |
40328cf24f | ||
![]() |
fb471bee17 | ||
![]() |
48ab7417b8 | ||
![]() |
97a3283eff | ||
![]() |
6d147e1677 | ||
![]() |
c99c44616c | ||
![]() |
1062e69e56 | ||
![]() |
5ca45fea4b | ||
![]() |
04af154c5b | ||
![]() |
f82041fc87 | ||
![]() |
a027bd780f | ||
![]() |
a611e0df45 | ||
![]() |
e49ec9a9c9 | ||
![]() |
4652ed90a3 | ||
![]() |
ff3ef576b7 |
232 changed files with 21345 additions and 1396 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
persist
|
persist
|
||||||
config.json
|
config
|
||||||
|
config.ssl
|
||||||
gitflow
|
gitflow
|
||||||
*.db
|
*.db
|
||||||
*.log
|
*.log
|
||||||
|
@ -11,8 +12,4 @@ gitflow
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
.idea/
|
.idea/
|
||||||
data/GeoLiteCity.dat
|
plugins/data/GeoLiteCity.dat
|
||||||
plugins/New Text Document.txt
|
|
||||||
plugins/srvv.py
|
|
||||||
run.cmd
|
|
||||||
config
|
|
||||||
|
|
26
README.md
26
README.md
|
@ -14,37 +14,33 @@ Unzip the resulting file, and continue to read this document.
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
|
||||||
Before you can run the bot, you need to install a few Python dependencies. LXML is required while Enchant, PyGeoIP, TweePy and PyDNS are needed for several plugins.
|
Before you can run the bot, you need to install a few Python dependencies. LXML is required while Enchant and PyDNS are needed for several plugins.
|
||||||
|
|
||||||
|
|
||||||
These can be installed with `pip` (The Python package manager) by running the following command in the bot directory:
|
These can be installed with `pip` (The Python package manager):
|
||||||
|
|
||||||
pip install -r requirements.txt
|
[sudo] pip install -r requirements.txt
|
||||||
|
|
||||||
**Note:** If you use `pip`, you will also need the following packages on linux or `pip` will fail to install the requirements.
|
If you use `pip`, you will also need the following packages on linux or `pip` will fail to install the requirements.
|
||||||
```python, python-dev, libenchant-dev, libenchant1c2a, libxslt-dev, libxml2-dev.```
|
```python, python-dev, libenchant-dev, libenchant1c2a, libxslt-dev, libxml2-dev.```
|
||||||
|
|
||||||
(this can be done using your package manager (eg: *apt-get* or *yum*)
|
|
||||||
|
|
||||||
#### How to install `pip`
|
#### How to install `pip`
|
||||||
|
|
||||||
You can usually install pip on linux by installing the `python-pip` package using your package manager (eg. *apt-get install python-pip* or *yum install python-pip* as root), or you can try the below code to download and install it manually.
|
|
||||||
|
|
||||||
curl -O http://python-distribute.org/distribute_setup.py # or download with your browser on windows
|
curl -O http://python-distribute.org/distribute_setup.py # or download with your browser on windows
|
||||||
python distribute_setup.py
|
python distribute_setup.py
|
||||||
easy_install pip
|
easy_install pip
|
||||||
|
|
||||||
If you need help installing pip on Windows, follow [this guide](http://simpledeveloper.com/how-to-install-easy_install/) and then run `easy_install pip` on the command line.
|
If you are unable to use pip, there are Windows installers for LXML available for [64 bit](https://pypi.python.org/packages/2.7/l/lxml/lxml-2.3.win-amd64-py2.7.exe) and [32 bit](https://pypi.python.org/packages/2.7/l/lxml/lxml-2.3.win32-py2.7.exe) versions of Python.
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
|
|
||||||
Before you run the bot, rename `config.default` to `config.json` and edit it with your preferred settings. You can check if your JSON is valid on [this site](http://jsonlint.com/)!
|
Before you run the bot, rename `config.default` to `config` and edit it with your preferred settings.
|
||||||
|
|
||||||
Once you have installed the required dependencies and renamed the config file, you can run the bot! Make sure you are in the correct folder and run the following command:
|
Once you have installed the required dependencies and renamed the config file, you can run the bot! Make sure you are in the correct folder and run the following command:
|
||||||
|
|
||||||
`python cloudbot.py`
|
`python bot.py`
|
||||||
|
|
||||||
On Windows you can usually just double-click `cloudbot.py` to start the bot, as long as you have Python installed correctly.
|
On Windows you can usually just double-click `bot.py` to start the bot, as long as you have Python installed correctly.
|
||||||
|
|
||||||
## Getting help with CloudBot
|
## Getting help with CloudBot
|
||||||
|
|
||||||
|
@ -62,17 +58,15 @@ More at the [Wiki Main Page](http://git.io/cloudbotircwiki).
|
||||||
|
|
||||||
The developers reside in [#CloudBot](irc://irc.esper.net/cloudbot) on [EsperNet](http://esper.net) and would be glad to help you.
|
The developers reside in [#CloudBot](irc://irc.esper.net/cloudbot) on [EsperNet](http://esper.net) and would be glad to help you.
|
||||||
|
|
||||||
If you think you have found a bug/have a idea/suggestion, please **open a issue** here on Github and contact us on IRC!
|
If you think you have found a bug/have a idea/suggestion, please **open a issue** here on Github.
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
CloudBot runs on **Python** *2.7.x*. It is currently developed on **Windows** *8* with **Python** *2.7.5*.
|
CloudBot runs on **Python** *2.7.x*. It is currently developed on **Windows** *8* with **Python** *2.7.5*.
|
||||||
|
|
||||||
It **requires the Python modules** lXML, watchdog and BeautifulSoup4.
|
It **requires the Python module** lXML.
|
||||||
The module `Enchant` is needed for the spellcheck plugin.
|
The module `Enchant` is needed for the spellcheck plugin.
|
||||||
The module `PyDNS` is needed for SRV record lookup in the mcping plugin.
|
The module `PyDNS` is needed for SRV record lookup in the mcping plugin.
|
||||||
The module `PyGeoIP` is needed for location lookup in the geoip plugin.
|
|
||||||
The module `TweePy` is needed for the twitter plugin.
|
|
||||||
|
|
||||||
**Windows** users: Windows compatibility some plugins is **broken** (such as ping), but we do intend to add it. Eventually.
|
**Windows** users: Windows compatibility some plugins is **broken** (such as ping), but we do intend to add it. Eventually.
|
||||||
|
|
||||||
|
|
92
cloudbot.py
92
cloudbot.py
|
@ -1,52 +1,74 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from core import bot
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import Queue
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import signal
|
import re
|
||||||
|
|
||||||
# check python version
|
sys.path += ['plugins', 'lib'] # add stuff to the sys.path for easy imports
|
||||||
if sys.version_info < (3, 2, 0):
|
|
||||||
print("CloudBot3 requires Python 3.2 or newer.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# set up environment
|
|
||||||
os.chdir(sys.path[0] or '.') # do stuff relative to the install directory
|
os.chdir(sys.path[0] or '.') # do stuff relative to the install directory
|
||||||
|
|
||||||
# this is not the code you are looking for
|
|
||||||
if os.path.exists(os.path.abspath('lib')):
|
|
||||||
sys.path += ['lib']
|
|
||||||
|
|
||||||
print('CloudBot3 <http://git.io/cloudbotirc>')
|
class Bot(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
print 'CloudBot DEV <http://git.io/cloudbotirc>'
|
||||||
|
|
||||||
def exit_gracefully(signum, frame):
|
# create new bot object
|
||||||
# this doesn't really work at all
|
bot = Bot()
|
||||||
cloudbot.stop()
|
bot.vars = {}
|
||||||
|
|
||||||
# restore the original handler so if they do it again it triggers
|
# record start time for the uptime command
|
||||||
signal.signal(signal.SIGINT, original_sigint)
|
bot.start_time = time.time()
|
||||||
|
|
||||||
# store the original SIGINT handler
|
print 'Begin Plugin Loading.'
|
||||||
original_sigint = signal.getsignal(signal.SIGINT)
|
|
||||||
signal.signal(signal.SIGINT, exit_gracefully)
|
|
||||||
|
|
||||||
# create a bot master and start it
|
# bootstrap the reloader
|
||||||
cloudbot = bot.CloudBot()
|
eval(compile(open(os.path.join('core', 'reload.py'), 'U').read(),
|
||||||
cloudbot.start()
|
os.path.join('core', 'reload.py'), 'exec'))
|
||||||
|
reload(init=True)
|
||||||
|
|
||||||
|
config()
|
||||||
|
if not hasattr(bot, 'config'):
|
||||||
|
exit()
|
||||||
|
|
||||||
|
print 'Connecting to IRC...'
|
||||||
|
|
||||||
|
bot.conns = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
for name, conf in bot.config['connections'].iteritems():
|
||||||
|
# strip all spaces and capitalization from the connection name
|
||||||
|
name = name.replace(" ", "_")
|
||||||
|
name = re.sub('[^A-Za-z0-9_]+', '', name)
|
||||||
|
print 'Connecting to server: %s' % conf['server']
|
||||||
|
if conf.get('ssl'):
|
||||||
|
bot.conns[name] = SSLIRC(name, conf['server'], conf['nick'], conf=conf,
|
||||||
|
port=conf.get('port', 6667), channels=conf['channels'],
|
||||||
|
ignore_certificate_errors=conf.get('ignore_cert', True))
|
||||||
|
else:
|
||||||
|
bot.conns[name] = IRC(name, conf['server'], conf['nick'], conf=conf,
|
||||||
|
port=conf.get('port', 6667), channels=conf['channels'])
|
||||||
|
except Exception as e:
|
||||||
|
print 'ERROR: malformed config file', e
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
bot.persist_dir = os.path.abspath('persist')
|
||||||
|
if not os.path.exists(bot.persist_dir):
|
||||||
|
os.mkdir(bot.persist_dir)
|
||||||
|
|
||||||
|
print 'Connection(s) made, starting main loop.'
|
||||||
|
|
||||||
# watch to see if the bot stops running or needs a restart
|
|
||||||
while True:
|
while True:
|
||||||
if cloudbot.running:
|
reload() # these functions only do things
|
||||||
|
config() # if changes have occured
|
||||||
|
|
||||||
|
for conn in bot.conns.itervalues():
|
||||||
|
try:
|
||||||
|
out = conn.out.get_nowait()
|
||||||
|
main(conn, out)
|
||||||
|
except Queue.Empty:
|
||||||
|
pass
|
||||||
|
while all(conn.out.empty() for conn in bot.conns.itervalues()):
|
||||||
time.sleep(.1)
|
time.sleep(.1)
|
||||||
else:
|
|
||||||
if cloudbot.do_restart:
|
|
||||||
# create a new bot thread and start it
|
|
||||||
# Todo: Make this work
|
|
||||||
del cloudbot
|
|
||||||
cloudbot = bot.Bot()
|
|
||||||
cloudbot.start()
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
break
|
|
103
config.default
103
config.default
|
@ -1,49 +1,26 @@
|
||||||
{
|
{
|
||||||
"connections":
|
"connections": {
|
||||||
[
|
"hackint": {
|
||||||
{
|
"server": "irc.hackint.eu",
|
||||||
"name": "esper",
|
"nick": "antibot",
|
||||||
"connection": {
|
"user": "antibot",
|
||||||
"server": "irc.esper.net",
|
"realname": "CloudBot - http://git.io/cloudbotirc",
|
||||||
"port": 6667,
|
"mode": "",
|
||||||
"ssl": false,
|
"_nickserv_password": "",
|
||||||
"ignore_cert": true
|
"-nickserv_user": "",
|
||||||
},
|
"channels": [
|
||||||
"nick": "MyCloueqerdBot",
|
"#ChaosChemnitz",
|
||||||
"user": "cloudbot",
|
"#logbot"
|
||||||
"real_name": "CloudBot - http://git.io/cloudbotirc",
|
],
|
||||||
"channels": ["#cloudbot", "#cloudbot2"],
|
"invite_join": true,
|
||||||
"disabled_commands": [],
|
"auto_rejoin": false,
|
||||||
"acls": {},
|
|
||||||
"nickserv": {
|
|
||||||
"enabled": false,
|
|
||||||
"nickserv_password": "",
|
|
||||||
"nickserv_user": "",
|
|
||||||
"nickserv_name": "nickserv",
|
|
||||||
"nickserv_command": "IDENTIFY"
|
|
||||||
},
|
|
||||||
"permissions": {
|
|
||||||
"admins": {
|
|
||||||
"perms": ["adminonly", "addfactoid", "delfactoid", "ignore", "botcontrol", "permissions_users", "op"],
|
|
||||||
"users": ["examplea!user@example.com", "exampleb!user@example.com"]
|
|
||||||
},
|
|
||||||
"moderators": {
|
|
||||||
"perms": ["addfactoid", "delfactoid", "ignore"],
|
|
||||||
"users": ["examplec!user@example.com"]
|
|
||||||
},
|
|
||||||
"trusted": {
|
|
||||||
"perms": ["addfactoid", "delfactoid"],
|
|
||||||
"users": ["exampled!user@example.com"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"plugins": {
|
|
||||||
|
|
||||||
},
|
|
||||||
"command_prefix": "."
|
"command_prefix": "."
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
"api_keys":
|
"disabled_plugins": [],
|
||||||
{
|
"disabled_commands": [],
|
||||||
|
"acls": {},
|
||||||
|
"api_keys": {
|
||||||
"tvdb": "",
|
"tvdb": "",
|
||||||
"wolframalpha": "",
|
"wolframalpha": "",
|
||||||
"lastfm": "",
|
"lastfm": "",
|
||||||
|
@ -58,5 +35,43 @@
|
||||||
"rdio_key": "",
|
"rdio_key": "",
|
||||||
"rdio_secret": ""
|
"rdio_secret": ""
|
||||||
},
|
},
|
||||||
"disabled_plugins": []
|
"permissions": {
|
||||||
|
"admins": {
|
||||||
|
"perms": [
|
||||||
|
"adminonly",
|
||||||
|
"addfactoid",
|
||||||
|
"delfactoid",
|
||||||
|
"ignore",
|
||||||
|
"botcontrol",
|
||||||
|
"permissions_users",
|
||||||
|
"op"
|
||||||
|
],
|
||||||
|
"users": [
|
||||||
|
"examplea!user@example.com",
|
||||||
|
"exampleb!user@example.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"moderators": {
|
||||||
|
"perms": [
|
||||||
|
"addfactoid",
|
||||||
|
"delfactoid",
|
||||||
|
"ignore"
|
||||||
|
],
|
||||||
|
"users": [
|
||||||
|
"stummi!~Stummi@stummi.org"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"factoids": {
|
||||||
|
"prefix": false
|
||||||
|
},
|
||||||
|
"ignore": {
|
||||||
|
"ignored": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"censored_strings": [
|
||||||
|
"mypass",
|
||||||
|
"mysecret"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
175
core/bot.py
175
core/bot.py
|
@ -1,175 +0,0 @@
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import queue
|
|
||||||
import collections
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
|
||||||
from sqlalchemy import create_engine
|
|
||||||
|
|
||||||
from core import config, irc, main
|
|
||||||
from core.permissions import PermissionManager
|
|
||||||
from core.loader import PluginLoader
|
|
||||||
|
|
||||||
|
|
||||||
def clean_name(n):
|
|
||||||
"""strip all spaces and capitalization"""
|
|
||||||
return re.sub('[^A-Za-z0-9_]+', '', n.replace(" ", "_"))
|
|
||||||
|
|
||||||
|
|
||||||
def get_logger():
|
|
||||||
"""create and return a new logger object"""
|
|
||||||
# create logger
|
|
||||||
logger = logging.getLogger("cloudbot")
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
# add a file handler
|
|
||||||
log_name = "bot.log"
|
|
||||||
fh = logging.FileHandler(log_name)
|
|
||||||
fh.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
# stdout handler
|
|
||||||
sh = logging.StreamHandler()
|
|
||||||
sh.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
# create a formatter and set the formatter for the handler.
|
|
||||||
frmt = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
|
|
||||||
fh.setFormatter(frmt)
|
|
||||||
simple_frmt = logging.Formatter('[%(levelname)s] %(message)s')
|
|
||||||
sh.setFormatter(simple_frmt)
|
|
||||||
|
|
||||||
# add the Handlers to the logger
|
|
||||||
logger.addHandler(fh)
|
|
||||||
logger.addHandler(sh)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
|
|
||||||
class CloudBot(threading.Thread):
|
|
||||||
def __init__(self):
|
|
||||||
# basic variables
|
|
||||||
self.start_time = time.time()
|
|
||||||
self.running = True
|
|
||||||
self.do_restart = False
|
|
||||||
|
|
||||||
# stores each instance of the
|
|
||||||
self.instances = []
|
|
||||||
|
|
||||||
# set up config and logging
|
|
||||||
self.setup()
|
|
||||||
self.logger.debug("Bot setup completed.")
|
|
||||||
|
|
||||||
# start bot instances
|
|
||||||
self.create()
|
|
||||||
|
|
||||||
for instance in self.instances:
|
|
||||||
instance.permissions = PermissionManager(self, instance)
|
|
||||||
|
|
||||||
# run plugin loader
|
|
||||||
self.plugins = collections.defaultdict(list)
|
|
||||||
|
|
||||||
""" self.plugins format
|
|
||||||
{'PLUGIN_TYPE': [(<COMPILED_PLUGIN_FUNTION>,
|
|
||||||
{PLUGIN_ARGS}),
|
|
||||||
(<COMPILED_PLUGIN_FUNTION>,
|
|
||||||
{PLUGIN_ARGS})],
|
|
||||||
'PLUGIN_TYPE': [(<COMPILED_PLUGIN_FUNTION>,
|
|
||||||
{PLUGIN_ARGS})]
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.threads = {}
|
|
||||||
|
|
||||||
self.loader = PluginLoader(self)
|
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""recieves input from the IRC engine and processes it"""
|
|
||||||
self.logger.info("Starting main thread.")
|
|
||||||
while self.running:
|
|
||||||
for instance in self.instances:
|
|
||||||
try:
|
|
||||||
incoming = instance.parsed_queue.get_nowait()
|
|
||||||
if incoming == StopIteration:
|
|
||||||
print("StopIteration")
|
|
||||||
# IRC engine has signalled timeout, so reconnect (ugly)
|
|
||||||
instance.connection.reconnect()
|
|
||||||
main.main(self, instance, incoming)
|
|
||||||
except queue.Empty:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# if no messages are in the incoming queue, sleep
|
|
||||||
while self.running and all(i.parsed_queue.empty() for i in self.instances):
|
|
||||||
time.sleep(.1)
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
"""create the logger and config objects"""
|
|
||||||
# logging
|
|
||||||
self.logger = get_logger()
|
|
||||||
self.logger.debug("Logging system initalised.")
|
|
||||||
|
|
||||||
# data folder
|
|
||||||
self.data_dir = os.path.abspath('persist')
|
|
||||||
if not os.path.exists(self.data_dir):
|
|
||||||
self.logger.debug("Data folder not found, creating.")
|
|
||||||
os.mkdir(self.data_dir)
|
|
||||||
|
|
||||||
# config
|
|
||||||
self.config = config.Config(self)
|
|
||||||
self.logger.debug("Config system initalised.")
|
|
||||||
|
|
||||||
# db
|
|
||||||
engine = create_engine('sqlite:///cloudbot.db')
|
|
||||||
db_factory = sessionmaker(bind=engine)
|
|
||||||
self.db_session = scoped_session(db_factory)
|
|
||||||
self.logger.debug("Database system initalised.")
|
|
||||||
|
|
||||||
def create(self):
|
|
||||||
""" Create a BotInstance for all the networks defined in the config """
|
|
||||||
for conf in self.config['instances']:
|
|
||||||
|
|
||||||
# strip all spaces and capitalization from the connection name
|
|
||||||
name = clean_name(conf['name'])
|
|
||||||
nick = conf['nick']
|
|
||||||
server = conf['connection']['server']
|
|
||||||
port = conf['connection'].get('port', 6667)
|
|
||||||
|
|
||||||
self.logger.debug("Creating BotInstance for {}.".format(name))
|
|
||||||
|
|
||||||
self.instances.append(irc.BotInstance(name, server, nick, config=conf,
|
|
||||||
port=port, logger=self.logger, channels=conf['channels'],
|
|
||||||
ssl=conf['connection'].get('ssl', False)))
|
|
||||||
self.logger.debug("({}) Created connection.".format(name))
|
|
||||||
|
|
||||||
|
|
||||||
def stop(self, reason=None):
|
|
||||||
"""quits all networks and shuts the bot down"""
|
|
||||||
self.logger.info("Stopping bot.")
|
|
||||||
|
|
||||||
self.config.observer.stop()
|
|
||||||
self.logger.debug("Stopping config reloader.")
|
|
||||||
|
|
||||||
self.loader.stop()
|
|
||||||
self.logger.debug("Stopping plugin loader.")
|
|
||||||
|
|
||||||
for connection in self.connections:
|
|
||||||
self.logger.debug("({}) Closing connection.".format(connection.name))
|
|
||||||
|
|
||||||
if reason:
|
|
||||||
connection.cmd("QUIT", [reason])
|
|
||||||
else:
|
|
||||||
connection.cmd("QUIT")
|
|
||||||
|
|
||||||
connection.stop()
|
|
||||||
|
|
||||||
self.logger.debug("Logging engine stopped")
|
|
||||||
logging.shutdown()
|
|
||||||
|
|
||||||
self.running = False
|
|
||||||
|
|
||||||
def restart(self, reason=None):
|
|
||||||
"""shuts the bot down and restarts it"""
|
|
||||||
self.do_restart = True
|
|
||||||
self.stop(reason)
|
|
|
@ -1,69 +1,27 @@
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from watchdog.observers import Observer
|
|
||||||
from watchdog.tricks import Trick
|
|
||||||
|
|
||||||
|
|
||||||
class Config(dict):
|
def save(conf):
|
||||||
def __init__(self, bot, *args, **kwargs):
|
json.dump(conf, open('config', 'w'), sort_keys=True, indent=2)
|
||||||
self.filename = "config.json"
|
|
||||||
self.path = os.path.abspath(self.filename)
|
|
||||||
self.bot = bot
|
|
||||||
self.logger = bot.logger
|
|
||||||
self.update(*args, **kwargs)
|
|
||||||
|
|
||||||
# populate self with config data
|
if not os.path.exists('config'):
|
||||||
self.load_config()
|
print "Please rename 'config.default' to 'config' to set up your bot!"
|
||||||
|
print "For help, see http://git.io/cloudbotirc"
|
||||||
# start watcher
|
print "Thank you for using CloudBot!"
|
||||||
self.watcher()
|
|
||||||
|
|
||||||
|
|
||||||
def load_config(self):
|
|
||||||
"""(re)loads the bot config from the config file"""
|
|
||||||
if not os.path.exists(self.path):
|
|
||||||
# if there is no config, show an error and die
|
|
||||||
self.logger.critical("No config file found, bot shutting down!")
|
|
||||||
print("No config file found! Bot shutting down in five seconds.")
|
|
||||||
print("Copy 'config.default' to 'config.json' for defaults.")
|
|
||||||
print("For help, see http://git.io/cloudbotirc. Thank you for using CloudBot!")
|
|
||||||
time.sleep(5)
|
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
with open(self.path) as f:
|
|
||||||
self.update(json.load(f))
|
|
||||||
self.logger.info("Config loaded from file.")
|
|
||||||
|
|
||||||
# reload permissions
|
def config():
|
||||||
if self.bot.instances:
|
# reload config from file if file has changed
|
||||||
for instance in self.bot.instances:
|
config_mtime = os.stat('config').st_mtime
|
||||||
instance.permissions.reload()
|
if bot._config_mtime != config_mtime:
|
||||||
|
try:
|
||||||
def save_config(self):
|
bot.config = json.load(open('config'))
|
||||||
"""saves the contents of the config dict to the config file"""
|
bot._config_mtime = config_mtime
|
||||||
json.dump(self, open(self.path, 'w'), sort_keys=True, indent=2)
|
except ValueError, e:
|
||||||
self.logger.info("Config saved to file.")
|
print 'error: malformed config', e
|
||||||
|
|
||||||
def watcher(self):
|
|
||||||
"""starts the watchdog to automatically reload the config when it changes on disk"""
|
|
||||||
self.observer = Observer()
|
|
||||||
|
|
||||||
pattern = "*{}".format(self.filename)
|
|
||||||
|
|
||||||
self.event_handler = ConfigEventHandler(self, patterns=[pattern])
|
|
||||||
self.observer.schedule(self.event_handler, path='.', recursive=False)
|
|
||||||
self.observer.start()
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigEventHandler(Trick):
|
bot._config_mtime = 0
|
||||||
def __init__(self, config, *args, **kwargs):
|
|
||||||
self.config = config
|
|
||||||
self.logger = config.logger
|
|
||||||
Trick.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def on_any_event(self, event):
|
|
||||||
self.logger.info("Config changed, triggering reload.")
|
|
||||||
self.config.load_config()
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import _thread
|
import thread
|
||||||
|
|
||||||
threaddbs = {}
|
threaddbs = {}
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ def get_db_connection(conn, name=''):
|
||||||
if not name:
|
if not name:
|
||||||
name = '{}.db'.format(conn.name)
|
name = '{}.db'.format(conn.name)
|
||||||
|
|
||||||
threadid = _thread.get_ident()
|
threadid = thread.get_ident()
|
||||||
if name in threaddbs and threadid in threaddbs[name]:
|
if name in threaddbs and threadid in threaddbs[name]:
|
||||||
return threaddbs[name][threadid]
|
return threaddbs[name][threadid]
|
||||||
filename = os.path.join(bot.data_dir, name)
|
filename = os.path.join(bot.persist_dir, name)
|
||||||
|
|
||||||
db = sqlite3.connect(filename, timeout=10)
|
db = sqlite3.connect(filename, timeout=10)
|
||||||
if name in threaddbs:
|
if name in threaddbs:
|
||||||
|
|
316
core/irc.py
316
core/irc.py
|
@ -1,18 +1,11 @@
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import threading
|
import thread
|
||||||
import queue
|
import Queue
|
||||||
|
|
||||||
from core import permissions
|
|
||||||
|
|
||||||
from ssl import wrap_socket, CERT_NONE, CERT_REQUIRED, SSLError
|
from ssl import wrap_socket, CERT_NONE, CERT_REQUIRED, SSLError
|
||||||
|
|
||||||
irc_prefix_rem = re.compile(r'(.*?) (.*?) (.*)').match
|
|
||||||
irc_noprefix_rem = re.compile(r'()(.*?) (.*)').match
|
|
||||||
irc_netmask_rem = re.compile(r':?([^!@]*)!?([^@]*)@?(.*)').match
|
|
||||||
irc_param_ref = re.compile(r'(?:^|(?<= ))(:.*|[^ ]+)').findall
|
|
||||||
|
|
||||||
|
|
||||||
def decode(txt):
|
def decode(txt):
|
||||||
for codec in ('utf-8', 'iso-8859-1', 'shift_jis', 'cp1252'):
|
for codec in ('utf-8', 'iso-8859-1', 'shift_jis', 'cp1252'):
|
||||||
|
@ -24,44 +17,78 @@ def decode(txt):
|
||||||
|
|
||||||
|
|
||||||
def censor(text):
|
def censor(text):
|
||||||
|
text = text.replace('\n', '').replace('\r', '')
|
||||||
|
replacement = '[censored]'
|
||||||
|
if 'censored_strings' in bot.config:
|
||||||
|
if bot.config['censored_strings']:
|
||||||
|
words = map(re.escape, bot.config['censored_strings'])
|
||||||
|
regex = re.compile('({})'.format("|".join(words)))
|
||||||
|
text = regex.sub(replacement, text)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
class ReceiveThread(threading.Thread):
|
class crlf_tcp(object):
|
||||||
"""receives messages from IRC and puts them in the input_queue"""
|
"""Handles tcp connections that consist of utf-8 lines ending with crlf"""
|
||||||
def __init__(self, sock, input_queue, timeout):
|
|
||||||
self.input_buffer = b""
|
def __init__(self, host, port, timeout=300):
|
||||||
self.input_queue = input_queue
|
self.ibuffer = ""
|
||||||
self.socket = sock
|
self.obuffer = ""
|
||||||
|
self.oqueue = Queue.Queue() # lines to be sent out
|
||||||
|
self.iqueue = Queue.Queue() # lines that were received
|
||||||
|
self.socket = self.create_socket()
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
self.shutdown = False
|
def create_socket(self):
|
||||||
threading.Thread.__init__(self)
|
return socket.socket(socket.AF_INET, socket.TCP_NODELAY)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
noerror = 0
|
||||||
|
while 1:
|
||||||
|
try:
|
||||||
|
self.socket.connect((self.host, self.port))
|
||||||
|
break
|
||||||
|
except socket.gaierror as e:
|
||||||
|
time.sleep(5)
|
||||||
|
except socket.timeout as e:
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
thread.start_new_thread(self.recv_loop, ())
|
||||||
|
thread.start_new_thread(self.send_loop, ())
|
||||||
|
|
||||||
def recv_from_socket(self, nbytes):
|
def recv_from_socket(self, nbytes):
|
||||||
return self.socket.recv(nbytes)
|
return self.socket.recv(nbytes)
|
||||||
|
|
||||||
|
def get_timeout_exception_type(self):
|
||||||
|
return socket.timeout
|
||||||
|
|
||||||
def handle_receive_exception(self, error, last_timestamp):
|
def handle_receive_exception(self, error, last_timestamp):
|
||||||
|
print("Receive exception: %s" % (error))
|
||||||
if time.time() - last_timestamp > self.timeout:
|
if time.time() - last_timestamp > self.timeout:
|
||||||
self.input_queue.put(StopIteration)
|
print("Receive timeout. Restart connection.")
|
||||||
|
self.iqueue.put(StopIteration)
|
||||||
self.socket.close()
|
self.socket.close()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_timeout_exception_type(self):
|
def handle_send_exception(self, error):
|
||||||
return socket.timeout
|
print("Send exception: %s" % (error))
|
||||||
|
self.iqueue.put(StopIteration)
|
||||||
|
self.socket.close()
|
||||||
|
return True
|
||||||
|
|
||||||
def run(self):
|
def recv_loop(self):
|
||||||
last_timestamp = time.time()
|
last_timestamp = time.time()
|
||||||
while not self.shutdown:
|
while True:
|
||||||
try:
|
try:
|
||||||
data = self.recv_from_socket(4096)
|
data = self.recv_from_socket(4096)
|
||||||
self.input_buffer += data
|
self.ibuffer += data
|
||||||
if data:
|
if data:
|
||||||
last_timestamp = time.time()
|
last_timestamp = time.time()
|
||||||
else:
|
else:
|
||||||
if time.time() - last_timestamp > self.timeout:
|
if time.time() - last_timestamp > self.timeout:
|
||||||
self.input_queue.put(StopIteration)
|
self.iqueue.put(StopIteration)
|
||||||
self.socket.close()
|
self.socket.close()
|
||||||
return
|
return
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
@ -69,16 +96,40 @@ class ReceiveThread(threading.Thread):
|
||||||
if self.handle_receive_exception(e, last_timestamp):
|
if self.handle_receive_exception(e, last_timestamp):
|
||||||
return
|
return
|
||||||
continue
|
continue
|
||||||
|
except AttributeError:
|
||||||
|
return
|
||||||
|
|
||||||
while b'\r\n' in self.input_buffer:
|
while '\r\n' in self.ibuffer:
|
||||||
line, self.input_buffer = self.input_buffer.split(b'\r\n', 1)
|
line, self.ibuffer = self.ibuffer.split('\r\n', 1)
|
||||||
print(decode(line))
|
self.iqueue.put(decode(line))
|
||||||
self.input_queue.put(decode(line))
|
|
||||||
|
|
||||||
|
def send_loop(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
line = self.oqueue.get().splitlines()[0][:500]
|
||||||
|
if line == StopIteration:
|
||||||
|
return
|
||||||
|
print ">>> %r" % line
|
||||||
|
self.obuffer += line.encode('utf-8', 'replace') + '\r\n'
|
||||||
|
while self.obuffer:
|
||||||
|
sent = self.socket.send(self.obuffer)
|
||||||
|
self.obuffer = self.obuffer[sent:]
|
||||||
|
|
||||||
class SSLReceiveThread(ReceiveThread):
|
except socket.error as e:
|
||||||
def __init__(self, sock, input_queue, timeout):
|
self.handle_send_exception(e)
|
||||||
ReceiveThread.__init__(self, sock, input_queue, timeout)
|
return
|
||||||
|
|
||||||
|
class crlf_ssl_tcp(crlf_tcp):
|
||||||
|
"""Handles ssl tcp connetions that consist of utf-8 lines ending with crlf"""
|
||||||
|
|
||||||
|
def __init__(self, host, port, ignore_cert_errors, timeout=300):
|
||||||
|
self.ignore_cert_errors = ignore_cert_errors
|
||||||
|
crlf_tcp.__init__(self, host, port, timeout)
|
||||||
|
|
||||||
|
def create_socket(self):
|
||||||
|
return wrap_socket(crlf_tcp.create_socket(self), server_side=False,
|
||||||
|
cert_reqs=CERT_NONE if self.ignore_cert_errors else
|
||||||
|
CERT_REQUIRED)
|
||||||
|
|
||||||
def recv_from_socket(self, nbytes):
|
def recv_from_socket(self, nbytes):
|
||||||
return self.socket.read(nbytes)
|
return self.socket.read(nbytes)
|
||||||
|
@ -88,49 +139,59 @@ class SSLReceiveThread(ReceiveThread):
|
||||||
|
|
||||||
def handle_receive_exception(self, error, last_timestamp):
|
def handle_receive_exception(self, error, last_timestamp):
|
||||||
# this is terrible
|
# this is terrible
|
||||||
if not "timed out" in error.args[0]:
|
#if not "timed out" in error.args[0]:
|
||||||
raise
|
# raise
|
||||||
return ReceiveThread.handle_receive_exception(self, error, last_timestamp)
|
return crlf_tcp.handle_receive_exception(self, error, last_timestamp)
|
||||||
|
|
||||||
|
def handle_send_exception(self, error):
|
||||||
|
return crlf_tcp.handle_send_exception(self, error)
|
||||||
|
|
||||||
|
|
||||||
class SendThread(threading.Thread):
|
irc_prefix_rem = re.compile(r'(.*?) (.*?) (.*)').match
|
||||||
"""sends messages from output_queue to IRC"""
|
irc_noprefix_rem = re.compile(r'()(.*?) (.*)').match
|
||||||
def __init__(self, sock, conn_name, output_queue):
|
irc_netmask_rem = re.compile(r':?([^!@]*)!?([^@]*)@?(.*)').match
|
||||||
self.output_buffer = b""
|
irc_param_ref = re.compile(r'(?:^|(?<= ))(:.*|[^ ]+)').findall
|
||||||
self.output_queue = output_queue
|
|
||||||
self.conn_name = conn_name
|
|
||||||
self.socket = sock
|
|
||||||
|
|
||||||
self.shutdown = False
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while not self.shutdown:
|
|
||||||
line = self.output_queue.get().splitlines()[0][:500]
|
|
||||||
self.output_buffer += line.encode('utf-8', 'replace') + b'\r\n'
|
|
||||||
while self.output_buffer:
|
|
||||||
sent = self.socket.send(self.output_buffer)
|
|
||||||
self.output_buffer = self.output_buffer[sent:]
|
|
||||||
|
|
||||||
|
|
||||||
class ParseThread(threading.Thread):
|
class IRC(object):
|
||||||
"""parses messages from input_queue and puts them in parsed_queue"""
|
"""handles the IRC protocol"""
|
||||||
def __init__(self, input_queue, output_queue, parsed_queue):
|
|
||||||
self.input_queue = input_queue # lines that were received
|
|
||||||
self.output_queue = output_queue # lines to be sent out
|
|
||||||
self.parsed_queue = parsed_queue # lines that have been parsed
|
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
def __init__(self, name, server, nick, port=6667, channels=[], conf={}):
|
||||||
|
self.name = name
|
||||||
|
self.channels = channels
|
||||||
|
self.conf = conf
|
||||||
|
self.server = server
|
||||||
|
self.port = port
|
||||||
|
self.nick = nick
|
||||||
|
self.history = {}
|
||||||
|
self.vars = {}
|
||||||
|
|
||||||
def run(self):
|
self.out = Queue.Queue() # responses from the server are placed here
|
||||||
|
# format: [rawline, prefix, command, params,
|
||||||
|
# nick, user, host, paramlist, msg]
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
thread.start_new_thread(self.parse_loop, ())
|
||||||
|
|
||||||
|
def create_connection(self):
|
||||||
|
return crlf_tcp(self.server, self.port)
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.conn = self.create_connection()
|
||||||
|
thread.start_new_thread(self.conn.run, ())
|
||||||
|
self.set_pass(self.conf.get('server_password'))
|
||||||
|
self.set_nick(self.nick)
|
||||||
|
self.cmd("USER",
|
||||||
|
[conf.get('user', 'cloudbot'), "3", "*", conf.get('realname',
|
||||||
|
'CloudBot - http://git.io/cloudbot')])
|
||||||
|
|
||||||
|
def parse_loop(self):
|
||||||
while True:
|
while True:
|
||||||
# get a message from the input queue
|
# get a message from the input queue
|
||||||
msg = self.input_queue.get()
|
msg = self.conn.iqueue.get()
|
||||||
|
|
||||||
if msg == StopIteration:
|
if msg == StopIteration:
|
||||||
# got a StopIteration from the receive thread, pass it on
|
self.connect()
|
||||||
# so the main thread can restart the connection
|
|
||||||
self.parsed_queue.put(StopIteration)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# parse the message
|
# parse the message
|
||||||
|
@ -147,109 +208,11 @@ class ParseThread(threading.Thread):
|
||||||
paramlist[-1] = paramlist[-1][1:]
|
paramlist[-1] = paramlist[-1][1:]
|
||||||
lastparam = paramlist[-1]
|
lastparam = paramlist[-1]
|
||||||
# put the parsed message in the response queue
|
# put the parsed message in the response queue
|
||||||
self.parsed_queue.put([msg, prefix, command, params, nick, user, host,
|
self.out.put([msg, prefix, command, params, nick, user, host,
|
||||||
mask, paramlist, lastparam])
|
mask, paramlist, lastparam])
|
||||||
# if the server pings us, pong them back
|
# if the server pings us, pong them back
|
||||||
if command == "PING":
|
if command == "PING":
|
||||||
string = "PONG :" + paramlist[0]
|
self.cmd("PONG", paramlist)
|
||||||
self.output_queue.put(string)
|
|
||||||
|
|
||||||
|
|
||||||
class IRCConnection(object):
|
|
||||||
"""handles an IRC connection"""
|
|
||||||
def __init__(self, name, host, port, input_queue, output_queue):
|
|
||||||
self.output_queue = output_queue # lines to be sent out
|
|
||||||
self.input_queue = input_queue # lines that were received
|
|
||||||
self.socket = self.create_socket()
|
|
||||||
self.conn_name = name
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.timeout = 300
|
|
||||||
|
|
||||||
def create_socket(self):
|
|
||||||
return socket.socket(socket.AF_INET, socket.TCP_NODELAY)
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
self.socket.connect((self.host, self.port))
|
|
||||||
|
|
||||||
self.receive_thread = ReceiveThread(self.socket, self.input_queue, self.timeout)
|
|
||||||
self.receive_thread.start()
|
|
||||||
|
|
||||||
self.send_thread = SendThread(self.socket, self.conn_name, self.output_queue)
|
|
||||||
self.send_thread.start()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.send_thread.shutdown = True
|
|
||||||
self.receive_thread.shutdown = True
|
|
||||||
time.sleep(0.1)
|
|
||||||
self.socket.close()
|
|
||||||
|
|
||||||
def reconnect(self):
|
|
||||||
self.stop()
|
|
||||||
self.connect()
|
|
||||||
|
|
||||||
|
|
||||||
class SSLIRCConnection(IRCConnection):
|
|
||||||
"""handles a SSL IRC connection"""
|
|
||||||
|
|
||||||
def __init__(self, name, host, port, input_queue, output_queue, ignore_cert_errors):
|
|
||||||
self.ignore_cert_errors = ignore_cert_errors
|
|
||||||
IRCConnection.__init__(self, name, host, port, input_queue, output_queue)
|
|
||||||
|
|
||||||
def create_socket(self):
|
|
||||||
return wrap_socket(IRCConnection.create_socket(self), server_side=False,
|
|
||||||
cert_reqs=CERT_NONE if self.ignore_cert_errors else
|
|
||||||
CERT_REQUIRED)
|
|
||||||
|
|
||||||
|
|
||||||
class BotInstance(object):
|
|
||||||
""" A BotInstance represents each connection the bot makes to an IRC server """
|
|
||||||
|
|
||||||
def __init__(self, name, server, nick, port=6667, ssl=False, logger=None, channels=[], config={}):
|
|
||||||
self.name = name
|
|
||||||
self.channels = channels
|
|
||||||
self.config = config
|
|
||||||
self.ssl = ssl
|
|
||||||
self.server = server
|
|
||||||
self.port = port
|
|
||||||
self.logger = logger
|
|
||||||
self.nick = nick
|
|
||||||
self.vars = {}
|
|
||||||
self.history = {}
|
|
||||||
|
|
||||||
self.parsed_queue = queue.Queue() # responses from the server are placed here
|
|
||||||
# format: [rawline, prefix, command, params,
|
|
||||||
# nick, user, host, paramlist, msg]
|
|
||||||
|
|
||||||
self.parsed_queue = queue.Queue()
|
|
||||||
self.input_queue = queue.Queue()
|
|
||||||
self.output_queue = queue.Queue()
|
|
||||||
|
|
||||||
# create the IRC connection and connect
|
|
||||||
self.connection = self.create_connection()
|
|
||||||
self.connection.connect()
|
|
||||||
|
|
||||||
self.set_pass(self.config.get('server_password'))
|
|
||||||
self.set_nick(self.nick)
|
|
||||||
self.cmd("USER",
|
|
||||||
[self.config.get('user', 'cloudbot'), "3", "*",
|
|
||||||
self.config.get('realname', 'CloudBot - http://git.io/cloudbot')])
|
|
||||||
|
|
||||||
self.parse_thread = ParseThread(self.input_queue, self.output_queue,
|
|
||||||
self.parsed_queue)
|
|
||||||
self.parse_thread.daemon = True
|
|
||||||
self.parse_thread.start()
|
|
||||||
|
|
||||||
def create_connection(self):
|
|
||||||
if self.ssl:
|
|
||||||
return SSLIRCConnection(self.name, self.server, self.port, self.input_queue,
|
|
||||||
self.output_queue, True)
|
|
||||||
else:
|
|
||||||
return IRCConnection(self.name, self.server, self.port,
|
|
||||||
self.input_queue, self.output_queue)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.connection.stop()
|
|
||||||
|
|
||||||
def set_pass(self, password):
|
def set_pass(self, password):
|
||||||
if password:
|
if password:
|
||||||
|
@ -276,20 +239,25 @@ class BotInstance(object):
|
||||||
|
|
||||||
def ctcp(self, target, ctcp_type, text):
|
def ctcp(self, target, ctcp_type, text):
|
||||||
""" makes the bot send a PRIVMSG CTCP to a target """
|
""" makes the bot send a PRIVMSG CTCP to a target """
|
||||||
out = "\x01{} {}\x01".format(ctcp_type, text)
|
out = u"\x01{} {}\x01".format(ctcp_type, text)
|
||||||
self.cmd("PRIVMSG", [target, out])
|
self.cmd("PRIVMSG", [target, out])
|
||||||
|
|
||||||
def cmd(self, command, params=None):
|
def cmd(self, command, params=None):
|
||||||
if params:
|
if params:
|
||||||
params[-1] = ':' + params[-1]
|
params[-1] = u':' + params[-1]
|
||||||
self.send("{} {}".format(command, ' '.join(params)))
|
self.send(u"{} {}".format(command, ' '.join(params)))
|
||||||
else:
|
else:
|
||||||
self.send(command)
|
self.send(command)
|
||||||
|
|
||||||
def send(self, string):
|
def send(self, str):
|
||||||
try:
|
self.conn.oqueue.put(str)
|
||||||
self.logger.info("{} >> {}".format(self.name.upper(), string))
|
|
||||||
except:
|
|
||||||
# if this doesn't work, no big deal
|
class SSLIRC(IRC):
|
||||||
pass
|
def __init__(self, name, server, nick, port=6667, channels=[], conf={},
|
||||||
self.output_queue.put(string)
|
ignore_certificate_errors=True):
|
||||||
|
self.ignore_cert_errors = ignore_certificate_errors
|
||||||
|
IRC.__init__(self, name, server, nick, port, channels, conf)
|
||||||
|
|
||||||
|
def create_connection(self):
|
||||||
|
return crlf_ssl_tcp(self.server, self.port, self.ignore_cert_errors)
|
||||||
|
|
153
core/loader.py
153
core/loader.py
|
@ -1,153 +0,0 @@
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import glob
|
|
||||||
import collections
|
|
||||||
|
|
||||||
from watchdog.observers import Observer
|
|
||||||
from watchdog.tricks import Trick
|
|
||||||
from pprint import pprint
|
|
||||||
|
|
||||||
from core import main
|
|
||||||
|
|
||||||
|
|
||||||
def make_signature(f):
|
|
||||||
return f.__code__.co_filename, f.__name__, f.__code__.co_firstlineno
|
|
||||||
|
|
||||||
|
|
||||||
def format_plug(plug, kind='', lpad=0):
|
|
||||||
out = ' ' * lpad + '{}:{}:{}'.format(*make_signature(plug[0]))
|
|
||||||
if kind == 'command':
|
|
||||||
out += ' ' * (50 - len(out)) + plug[1]['name']
|
|
||||||
|
|
||||||
if kind == 'event':
|
|
||||||
out += ' ' * (50 - len(out)) + ', '.join(plug[1]['events'])
|
|
||||||
|
|
||||||
if kind == 'regex':
|
|
||||||
out += ' ' * (50 - len(out)) + plug[1]['regex']
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
class PluginLoader(object):
|
|
||||||
def __init__(self, bot):
|
|
||||||
self.observer = Observer()
|
|
||||||
self.path = os.path.abspath("plugins")
|
|
||||||
self.bot = bot
|
|
||||||
|
|
||||||
self.event_handler = PluginEventHandler(self, patterns=["*.py"])
|
|
||||||
self.observer.schedule(self.event_handler, self.path, recursive=False)
|
|
||||||
self.observer.start()
|
|
||||||
|
|
||||||
self.load_all()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""shuts down the plugin reloader"""
|
|
||||||
self.observer.stop()
|
|
||||||
|
|
||||||
def load_all(self):
|
|
||||||
"""runs load_file() on all python files in the plugins folder"""
|
|
||||||
files = set(glob.glob(os.path.join(self.path, '*.py')))
|
|
||||||
for f in files:
|
|
||||||
self.load_file(f, rebuild=True)
|
|
||||||
self.rebuild()
|
|
||||||
|
|
||||||
def load_file(self, path, rebuild=False):
|
|
||||||
"""loads (or reloads) all valid plugins from a specified file"""
|
|
||||||
filename = os.path.basename(path)
|
|
||||||
title = os.path.splitext(filename)[0]
|
|
||||||
|
|
||||||
disabled = self.bot.config.get('disabled_plugins', [])
|
|
||||||
if title in disabled:
|
|
||||||
self.bot.logger.info("Did not load plugins from: {} (plugin disabled)".format(filename))
|
|
||||||
return
|
|
||||||
|
|
||||||
# compile the file and eval it in a namespace
|
|
||||||
try:
|
|
||||||
code = compile(open(path, 'U').read(), filename, 'exec')
|
|
||||||
namespace = {}
|
|
||||||
eval(code, namespace)
|
|
||||||
except Exception:
|
|
||||||
self.bot.logger.exception("Error compiling {}:".format(filename))
|
|
||||||
return
|
|
||||||
|
|
||||||
# remove plugins already loaded from this file
|
|
||||||
for plug_type, data in self.bot.plugins.items():
|
|
||||||
self.bot.plugins[plug_type] = [x for x in data
|
|
||||||
if x[0]._filename != filename]
|
|
||||||
|
|
||||||
# stop all currently running instances of the plugins from this file
|
|
||||||
for func, handler in list(self.bot.threads.items()):
|
|
||||||
if func._filename == filename:
|
|
||||||
handler.stop()
|
|
||||||
del self.bot.threads[func]
|
|
||||||
|
|
||||||
# find objects with hooks in the plugin namespace
|
|
||||||
# TODO: kill it with fire, kill it all
|
|
||||||
for obj in namespace.values():
|
|
||||||
if hasattr(obj, '_hook'): # check for magic
|
|
||||||
if obj._thread:
|
|
||||||
self.bot.threads[obj] = main.Handler(self.bot, obj)
|
|
||||||
for plug_type, data in obj._hook:
|
|
||||||
# add plugin to the plugin list
|
|
||||||
self.bot.plugins[plug_type] += [data]
|
|
||||||
self.bot.logger.info("Loaded plugin: {} ({})".format(format_plug(data), plug_type))
|
|
||||||
|
|
||||||
# do a rebuild, unless the bot is loading all plugins (rebuild happens after load_all)
|
|
||||||
if not rebuild:
|
|
||||||
self.rebuild()
|
|
||||||
|
|
||||||
def unload_file(self, path):
|
|
||||||
"""unloads all loaded plugins from a specified file"""
|
|
||||||
filename = os.path.basename(path)
|
|
||||||
self.bot.logger.info("Unloading plugins from: {}".format(filename))
|
|
||||||
|
|
||||||
# remove plugins loaded from this file
|
|
||||||
for plugin_type, plugins in self.bot.plugins.items():
|
|
||||||
self.bot.plugins[plugin_type] = [x for x in plugins if x[0]._filename != filename]
|
|
||||||
|
|
||||||
# stop all currently running instances of the plugins from this file
|
|
||||||
for func, handler in list(self.bot.threads.items()):
|
|
||||||
if func._filename == filename:
|
|
||||||
handler.stop()
|
|
||||||
del self.bot.threads[func]
|
|
||||||
|
|
||||||
self.rebuild()
|
|
||||||
|
|
||||||
def rebuild(self):
|
|
||||||
"""rebuilds the cloudbot command and event hook lists"""
|
|
||||||
self.bot.commands = {}
|
|
||||||
for plugin in self.bot.plugins['command']:
|
|
||||||
name = plugin[1]['name'].lower()
|
|
||||||
if not re.match(r'^\w+$', name):
|
|
||||||
self.bot.logger.error('Invalid command name: "{}" ({})'.format(name, format_plug(plugin)))
|
|
||||||
continue
|
|
||||||
if name in self.bot.commands:
|
|
||||||
self.bot.logger.error('Command already registered: "{}" ({}, {})'.format(name,
|
|
||||||
format_plug(self.bot.commands[name]),
|
|
||||||
format_plug(plugin)))
|
|
||||||
continue
|
|
||||||
self.bot.commands[name] = plugin
|
|
||||||
|
|
||||||
self.bot.events = collections.defaultdict(list)
|
|
||||||
for func, args in self.bot.plugins['event']:
|
|
||||||
for event in args['events']:
|
|
||||||
self.bot.events[event].append((func, args))
|
|
||||||
|
|
||||||
|
|
||||||
class PluginEventHandler(Trick):
|
|
||||||
def __init__(self, loader, *args, **kwargs):
|
|
||||||
self.loader = loader
|
|
||||||
Trick.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
def on_created(self, event):
|
|
||||||
self.loader.load_file(event.src_path)
|
|
||||||
|
|
||||||
def on_deleted(self, event):
|
|
||||||
self.loader.unload_file(event.src_path)
|
|
||||||
|
|
||||||
def on_modified(self, event):
|
|
||||||
self.loader.load_file(event.src_path)
|
|
||||||
|
|
||||||
def on_moved(self, event):
|
|
||||||
self.loader.unload_file(event.src_path)
|
|
||||||
self.loader.load_file(event.dest_path)
|
|
121
core/main.py
121
core/main.py
|
@ -1,16 +1,12 @@
|
||||||
import _thread
|
import thread
|
||||||
import traceback
|
import traceback
|
||||||
import queue
|
|
||||||
import re
|
|
||||||
|
|
||||||
from sqlalchemy.orm import scoped_session
|
|
||||||
|
|
||||||
_thread.stack_size(1024 * 512) # reduce vm size
|
|
||||||
|
|
||||||
|
|
||||||
#TODO: redesign this messy thing
|
thread.stack_size(1024 * 512) # reduce vm size
|
||||||
|
|
||||||
|
|
||||||
class Input(dict):
|
class Input(dict):
|
||||||
def __init__(self, bot, conn, raw, prefix, command, params,
|
def __init__(self, conn, raw, prefix, command, params,
|
||||||
nick, user, host, mask, paraml, msg):
|
nick, user, host, mask, paraml, msg):
|
||||||
|
|
||||||
chan = paraml[0].lower()
|
chan = paraml[0].lower()
|
||||||
|
@ -26,7 +22,7 @@ class Input(dict):
|
||||||
if target == nick:
|
if target == nick:
|
||||||
conn.msg(target, message)
|
conn.msg(target, message)
|
||||||
else:
|
else:
|
||||||
conn.msg(target, "({}) {}".format(nick, message))
|
conn.msg(target, u"({}) {}".format(nick, message))
|
||||||
|
|
||||||
def action(message, target=chan):
|
def action(message, target=chan):
|
||||||
"""sends an action to the current channel/user or a specific channel/user"""
|
"""sends an action to the current channel/user or a specific channel/user"""
|
||||||
|
@ -54,59 +50,67 @@ class Input(dict):
|
||||||
self[key] = value
|
self[key] = value
|
||||||
|
|
||||||
|
|
||||||
def run(bot, func, input):
|
def run(func, input):
|
||||||
uses_db = True
|
args = func._args
|
||||||
# TODO: change to bot.get_db_session()
|
|
||||||
print(input)
|
|
||||||
if 'text' not in input:
|
|
||||||
input.text = input.paraml
|
|
||||||
|
|
||||||
if uses_db:
|
if 'inp' not in input:
|
||||||
# create SQLAlchemy session
|
input.inp = input.paraml
|
||||||
bot.logger.debug("Opened DB session for: {}".format(func._filename))
|
|
||||||
input.db = input.bot.db_session()
|
|
||||||
|
|
||||||
try:
|
|
||||||
out = func(input, input.conn)
|
|
||||||
except:
|
|
||||||
bot.logger.exception("Error in plugin {}:".format(func._filename))
|
|
||||||
return
|
|
||||||
finally:
|
|
||||||
if uses_db:
|
|
||||||
bot.logger.debug("Closed DB session for: {}".format(func._filename))
|
|
||||||
input.db.close()
|
|
||||||
|
|
||||||
|
if args:
|
||||||
|
if 'db' in args and 'db' not in input:
|
||||||
|
input.db = get_db_connection(input.conn)
|
||||||
|
if 'input' in args:
|
||||||
|
input.input = input
|
||||||
|
if 0 in args:
|
||||||
|
out = func(input.inp, **input)
|
||||||
|
else:
|
||||||
|
kw = dict((key, input[key]) for key in args if key in input)
|
||||||
|
out = func(input.inp, **kw)
|
||||||
|
else:
|
||||||
|
out = func(input.inp)
|
||||||
if out is not None:
|
if out is not None:
|
||||||
input.reply(str(out))
|
input.reply(unicode(out))
|
||||||
|
|
||||||
|
|
||||||
def do_sieve(sieve, bot, input, func, type, args):
|
def do_sieve(sieve, bot, input, func, type, args):
|
||||||
try:
|
try:
|
||||||
return sieve(bot, input, func, type, args)
|
return sieve(bot, input, func, type, args)
|
||||||
except Exception:
|
except Exception:
|
||||||
bot.logger.exception("Error in sieve {}:".format(func._filename))
|
print 'sieve error',
|
||||||
|
traceback.print_exc()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Handler(object):
|
class Handler(object):
|
||||||
"""Runs plugins in their own threads (ensures order)"""
|
"""Runs plugins in their own threads (ensures order)"""
|
||||||
|
|
||||||
def __init__(self, bot, func):
|
def __init__(self, func):
|
||||||
self.func = func
|
self.func = func
|
||||||
self.bot = bot
|
self.input_queue = Queue.Queue()
|
||||||
self.input_queue = queue.Queue()
|
thread.start_new_thread(self.start, ())
|
||||||
_thread.start_new_thread(self.start, ())
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
uses_db = True
|
uses_db = 'db' in self.func._args
|
||||||
|
db_conns = {}
|
||||||
while True:
|
while True:
|
||||||
input = self.input_queue.get()
|
input = self.input_queue.get()
|
||||||
|
|
||||||
if input == StopIteration:
|
if input == StopIteration:
|
||||||
break
|
break
|
||||||
|
|
||||||
run(self.bot, self.func, input)
|
if uses_db:
|
||||||
|
db = db_conns.get(input.conn)
|
||||||
|
if db is None:
|
||||||
|
db = bot.get_db_connection(input.conn)
|
||||||
|
db_conns[input.conn] = db
|
||||||
|
input.db = db
|
||||||
|
|
||||||
|
try:
|
||||||
|
run(self.func, input)
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.input_queue.put(StopIteration)
|
self.input_queue.put(StopIteration)
|
||||||
|
@ -115,27 +119,27 @@ class Handler(object):
|
||||||
self.input_queue.put(value)
|
self.input_queue.put(value)
|
||||||
|
|
||||||
|
|
||||||
def dispatch(bot, input, kind, func, args, autohelp=False):
|
def dispatch(input, kind, func, args, autohelp=False):
|
||||||
for sieve, in bot.plugins['sieve']:
|
for sieve, in bot.plugs['sieve']:
|
||||||
input = do_sieve(sieve, bot, input, func, kind, args)
|
input = do_sieve(sieve, bot, input, func, kind, args)
|
||||||
if input is None:
|
if input is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not (not autohelp or not args.get('autohelp', True) or input.inp or not (func.__doc__ is not None)):
|
if not (not autohelp or not args.get('autohelp', True) or input.inp or not (func.__doc__ is not None)):
|
||||||
input.notice(input.conn.config["command_prefix"] + func.__doc__)
|
input.notice(input.conn.conf["command_prefix"] + func.__doc__)
|
||||||
return
|
return
|
||||||
|
|
||||||
if func._thread:
|
if func._thread:
|
||||||
bot.threads[func].put(input)
|
bot.threads[func].put(input)
|
||||||
else:
|
else:
|
||||||
_thread.start_new_thread(run, (bot, func, input))
|
thread.start_new_thread(run, (func, input))
|
||||||
|
|
||||||
|
|
||||||
def match_command(bot, command):
|
def match_command(command):
|
||||||
commands = list(bot.commands)
|
commands = list(bot.commands)
|
||||||
|
|
||||||
# do some fuzzy matching
|
# do some fuzzy matching
|
||||||
prefix = [x for x in commands if x.startswith(command)]
|
prefix = filter(lambda x: x.startswith(command), commands)
|
||||||
if len(prefix) == 1:
|
if len(prefix) == 1:
|
||||||
return prefix[0]
|
return prefix[0]
|
||||||
elif prefix and command not in prefix:
|
elif prefix and command not in prefix:
|
||||||
|
@ -144,13 +148,13 @@ def match_command(bot, command):
|
||||||
return command
|
return command
|
||||||
|
|
||||||
|
|
||||||
def main(bot, conn, out):
|
def main(conn, out):
|
||||||
inp = Input(bot, conn, *out)
|
inp = Input(conn, *out)
|
||||||
command_prefix = conn.config.get('command_prefix', '.')
|
command_prefix = conn.conf.get('command_prefix', '.')
|
||||||
|
|
||||||
# EVENTS
|
# EVENTS
|
||||||
for func, args in bot.events[inp.command] + bot.events['*']:
|
for func, args in bot.events[inp.command] + bot.events['*']:
|
||||||
dispatch(bot, Input(bot, conn, *out), "event", func, args)
|
dispatch(Input(conn, *out), "event", func, args)
|
||||||
|
|
||||||
if inp.command == 'PRIVMSG':
|
if inp.command == 'PRIVMSG':
|
||||||
# COMMANDS
|
# COMMANDS
|
||||||
|
@ -158,6 +162,7 @@ def main(bot, conn, out):
|
||||||
prefix = '^(?:[{}]?|'.format(command_prefix)
|
prefix = '^(?:[{}]?|'.format(command_prefix)
|
||||||
else:
|
else:
|
||||||
prefix = '^(?:[{}]|'.format(command_prefix)
|
prefix = '^(?:[{}]|'.format(command_prefix)
|
||||||
|
|
||||||
command_re = prefix + inp.conn.nick
|
command_re = prefix + inp.conn.nick
|
||||||
command_re += r'[,;:]+\s+)(\w+)(?:$|\s+)(.*)'
|
command_re += r'[,;:]+\s+)(\w+)(?:$|\s+)(.*)'
|
||||||
|
|
||||||
|
@ -165,26 +170,26 @@ def main(bot, conn, out):
|
||||||
|
|
||||||
if m:
|
if m:
|
||||||
trigger = m.group(1).lower()
|
trigger = m.group(1).lower()
|
||||||
command = match_command(bot, trigger)
|
command = match_command(trigger)
|
||||||
|
|
||||||
if isinstance(command, list): # multiple potential matches
|
if isinstance(command, list): # multiple potential matches
|
||||||
input = Input(bot, conn, *out)
|
input = Input(conn, *out)
|
||||||
input.notice("Did you mean {} or {}?".format
|
input.notice("Did you mean {} or {}?".format
|
||||||
(', '.join(command[:-1]), command[-1]))
|
(', '.join(command[:-1]), command[-1]))
|
||||||
elif command in bot.commands:
|
elif command in bot.commands:
|
||||||
input = Input(bot, conn, *out)
|
input = Input(conn, *out)
|
||||||
input.trigger = trigger
|
input.trigger = trigger
|
||||||
input.text_unstripped = m.group(2)
|
input.inp_unstripped = m.group(2)
|
||||||
input.text = input.text_unstripped.strip()
|
input.inp = input.inp_unstripped.strip()
|
||||||
|
|
||||||
func, args = bot.commands[command]
|
func, args = bot.commands[command]
|
||||||
dispatch(bot, input, "command", func, args, autohelp=True)
|
dispatch(input, "command", func, args, autohelp=True)
|
||||||
|
|
||||||
# REGEXES
|
# REGEXES
|
||||||
for func, args in bot.plugins['regex']:
|
for func, args in bot.plugs['regex']:
|
||||||
m = args['re'].search(inp.lastparam)
|
m = args['re'].search(inp.lastparam)
|
||||||
if m:
|
if m:
|
||||||
input = Input(bot, conn, *out)
|
input = Input(conn, *out)
|
||||||
input.text = m
|
input.inp = m
|
||||||
|
|
||||||
dispatch(bot, input, "regex", func, args)
|
dispatch(input, "regex", func, args)
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
from fnmatch import fnmatch
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionManager(object):
|
|
||||||
def __init__(self, bot, conn):
|
|
||||||
|
|
||||||
# this is all legacy code, needs to be redone with classes and whatnot
|
|
||||||
self.logger = bot.logger
|
|
||||||
|
|
||||||
self.logger.info("Creating simple permission manager for {}.".format(conn.name))
|
|
||||||
|
|
||||||
# stuff
|
|
||||||
self.bot = bot
|
|
||||||
self.conn = conn
|
|
||||||
self.config = conn.config
|
|
||||||
|
|
||||||
self.group_perms = {}
|
|
||||||
self.group_users = {}
|
|
||||||
self.perm_users = {}
|
|
||||||
|
|
||||||
self.reload()
|
|
||||||
|
|
||||||
def reload(self):
|
|
||||||
self.logger.info("Reloading permissions for {}.".format(self.conn.name))
|
|
||||||
groups = self.conn.config.get("permissions", [])
|
|
||||||
# work out the permissions and users each group has
|
|
||||||
for key, value in groups.items():
|
|
||||||
self.group_perms[key] = []
|
|
||||||
self.group_users[key] = []
|
|
||||||
for permission in value["perms"]:
|
|
||||||
self.group_perms[key].append(permission)
|
|
||||||
for user in value["users"]:
|
|
||||||
self.group_users[key].append(user)
|
|
||||||
|
|
||||||
for group, users in self.group_users.items():
|
|
||||||
group_perms = self.group_perms[group]
|
|
||||||
for perm in group_perms:
|
|
||||||
self.perm_users[perm] = []
|
|
||||||
self.perm_users[perm] = users
|
|
||||||
|
|
||||||
def has_perm_mask(self, mask, perm):
|
|
||||||
|
|
||||||
allowed_users = self.perm_users[perm]
|
|
||||||
|
|
||||||
for pattern in allowed_users:
|
|
||||||
if fnmatch(mask.lower(), pattern.lower()):
|
|
||||||
return input
|
|
||||||
|
|
160
core/reload.py
Normal file
160
core/reload.py
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
import collections
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
if 'mtimes' not in globals():
|
||||||
|
mtimes = {}
|
||||||
|
|
||||||
|
if 'lastfiles' not in globals():
|
||||||
|
lastfiles = set()
|
||||||
|
|
||||||
|
|
||||||
|
def make_signature(f):
|
||||||
|
return f.func_code.co_filename, f.func_name, f.func_code.co_firstlineno
|
||||||
|
|
||||||
|
|
||||||
|
def format_plug(plug, kind='', lpad=0):
|
||||||
|
out = ' ' * lpad + '{}:{}:{}'.format(*make_signature(plug[0]))
|
||||||
|
if kind == 'command':
|
||||||
|
out += ' ' * (50 - len(out)) + plug[1]['name']
|
||||||
|
|
||||||
|
if kind == 'event':
|
||||||
|
out += ' ' * (50 - len(out)) + ', '.join(plug[1]['events'])
|
||||||
|
|
||||||
|
if kind == 'regex':
|
||||||
|
out += ' ' * (50 - len(out)) + plug[1]['regex']
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def reload(init=False):
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
if init:
|
||||||
|
bot.plugs = collections.defaultdict(list)
|
||||||
|
bot.threads = {}
|
||||||
|
|
||||||
|
core_fileset = set(glob.glob(os.path.join("core", "*.py")))
|
||||||
|
|
||||||
|
for filename in core_fileset:
|
||||||
|
mtime = os.stat(filename).st_mtime
|
||||||
|
if mtime != mtimes.get(filename):
|
||||||
|
mtimes[filename] = mtime
|
||||||
|
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
eval(compile(open(filename, 'U').read(), filename, 'exec'),
|
||||||
|
globals())
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
if init: # stop if there's an error (syntax?) in a core
|
||||||
|
sys.exit() # script on startup
|
||||||
|
continue
|
||||||
|
|
||||||
|
if filename == os.path.join('core', 'reload.py'):
|
||||||
|
reload(init=init)
|
||||||
|
return
|
||||||
|
|
||||||
|
fileset = set(glob.glob(os.path.join('plugins', '*.py')))
|
||||||
|
|
||||||
|
# remove deleted/moved plugins
|
||||||
|
for name, data in bot.plugs.iteritems():
|
||||||
|
bot.plugs[name] = [x for x in data if x[0]._filename in fileset]
|
||||||
|
|
||||||
|
for filename in list(mtimes):
|
||||||
|
if filename not in fileset and filename not in core_fileset:
|
||||||
|
mtimes.pop(filename)
|
||||||
|
|
||||||
|
for func, handler in list(bot.threads.iteritems()):
|
||||||
|
if func._filename not in fileset:
|
||||||
|
handler.stop()
|
||||||
|
del bot.threads[func]
|
||||||
|
|
||||||
|
# compile new plugins
|
||||||
|
for filename in fileset:
|
||||||
|
mtime = os.stat(filename).st_mtime
|
||||||
|
if mtime != mtimes.get(filename):
|
||||||
|
mtimes[filename] = mtime
|
||||||
|
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
code = compile(open(filename, 'U').read(), filename, 'exec')
|
||||||
|
namespace = {}
|
||||||
|
eval(code, namespace)
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# remove plugins already loaded from this filename
|
||||||
|
for name, data in bot.plugs.iteritems():
|
||||||
|
bot.plugs[name] = [x for x in data
|
||||||
|
if x[0]._filename != filename]
|
||||||
|
|
||||||
|
for func, handler in list(bot.threads.iteritems()):
|
||||||
|
if func._filename == filename:
|
||||||
|
handler.stop()
|
||||||
|
del bot.threads[func]
|
||||||
|
|
||||||
|
for obj in namespace.itervalues():
|
||||||
|
if hasattr(obj, '_hook'): # check for magic
|
||||||
|
if obj._thread:
|
||||||
|
bot.threads[obj] = Handler(obj)
|
||||||
|
|
||||||
|
for type, data in obj._hook:
|
||||||
|
bot.plugs[type] += [data]
|
||||||
|
|
||||||
|
if not init:
|
||||||
|
print '### new plugin (type: %s) loaded:' % \
|
||||||
|
type, format_plug(data)
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
bot.commands = {}
|
||||||
|
for plug in bot.plugs['command']:
|
||||||
|
name = plug[1]['name'].lower()
|
||||||
|
if not re.match(r'^\w+$', name):
|
||||||
|
print '### ERROR: invalid command name "{}" ({})'.format(name, format_plug(plug))
|
||||||
|
continue
|
||||||
|
if name in bot.commands:
|
||||||
|
print "### ERROR: command '{}' already registered ({}, {})".format(name,
|
||||||
|
format_plug(bot.commands[name]),
|
||||||
|
format_plug(plug))
|
||||||
|
continue
|
||||||
|
bot.commands[name] = plug
|
||||||
|
|
||||||
|
bot.events = collections.defaultdict(list)
|
||||||
|
for func, args in bot.plugs['event']:
|
||||||
|
for event in args['events']:
|
||||||
|
bot.events[event].append((func, args))
|
||||||
|
|
||||||
|
if init:
|
||||||
|
print ' plugin listing:'
|
||||||
|
|
||||||
|
if bot.commands:
|
||||||
|
# hack to make commands with multiple aliases
|
||||||
|
# print nicely
|
||||||
|
|
||||||
|
print ' command:'
|
||||||
|
commands = collections.defaultdict(list)
|
||||||
|
|
||||||
|
for name, (func, args) in bot.commands.iteritems():
|
||||||
|
commands[make_signature(func)].append(name)
|
||||||
|
|
||||||
|
for sig, names in sorted(commands.iteritems()):
|
||||||
|
names.sort(key=lambda x: (-len(x), x)) # long names first
|
||||||
|
out = ' ' * 6 + '%s:%s:%s' % sig
|
||||||
|
out += ' ' * (50 - len(out)) + ', '.join(names)
|
||||||
|
print out
|
||||||
|
|
||||||
|
for kind, plugs in sorted(bot.plugs.iteritems()):
|
||||||
|
if kind == 'command':
|
||||||
|
continue
|
||||||
|
print ' {}:'.format(kind)
|
||||||
|
for plug in plugs:
|
||||||
|
print format_plug(plug, kind=kind, lpad=6)
|
||||||
|
print
|
|
@ -3,15 +3,15 @@ import random
|
||||||
from util import hook
|
from util import hook
|
||||||
|
|
||||||
|
|
||||||
with open("data/larts.txt") as f:
|
with open("plugins/data/larts.txt") as f:
|
||||||
larts = [line.strip() for line in f.readlines()
|
larts = [line.strip() for line in f.readlines()
|
||||||
if not line.startswith("//")]
|
if not line.startswith("//")]
|
||||||
|
|
||||||
with open("data/insults.txt") as f:
|
with open("plugins/data/insults.txt") as f:
|
||||||
insults = [line.strip() for line in f.readlines()
|
insults = [line.strip() for line in f.readlines()
|
||||||
if not line.startswith("//")]
|
if not line.startswith("//")]
|
||||||
|
|
||||||
with open("data/flirts.txt") as f:
|
with open("plugins/data/flirts.txt") as f:
|
||||||
flirts = [line.strip() for line in f.readlines()
|
flirts = [line.strip() for line in f.readlines()
|
||||||
if not line.startswith("//")]
|
if not line.startswith("//")]
|
||||||
|
|
121
disabled_stuff/cleverbot.py
Normal file
121
disabled_stuff/cleverbot.py
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
# from jessi bot
|
||||||
|
import urllib2
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
import unicodedata
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
# these are just parts required
|
||||||
|
# TODO: Merge them.
|
||||||
|
|
||||||
|
arglist = ['', 'y', '', '', '', '', '', '', '', '', 'wsf', '',
|
||||||
|
'', '', '', '', '', '', '', '0', 'Say', '1', 'false']
|
||||||
|
|
||||||
|
always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
|
'abcdefghijklmnopqrstuvwxyz'
|
||||||
|
'0123456789' '_.-')
|
||||||
|
|
||||||
|
headers = {'X-Moz': 'prefetch', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1)Gecko/20100101 Firefox/7.0',
|
||||||
|
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'Referer': 'http://www.cleverbot.com',
|
||||||
|
'Pragma': 'no-cache', 'Cache-Control': 'no-cache, no-cache', 'Accept-Language': 'en-us;q=0.8,en;q=0.5'}
|
||||||
|
|
||||||
|
keylist = ['stimulus', 'start', 'sessionid', 'vText8', 'vText7', 'vText6',
|
||||||
|
'vText5', 'vText4', 'vText3', 'vText2', 'icognoid',
|
||||||
|
'icognocheck', 'prevref', 'emotionaloutput', 'emotionalhistory',
|
||||||
|
'asbotname', 'ttsvoice', 'typing', 'lineref', 'fno', 'sub',
|
||||||
|
'islearning', 'cleanslate']
|
||||||
|
|
||||||
|
MsgList = list()
|
||||||
|
|
||||||
|
|
||||||
|
def quote(s, safe='/'): # quote('abc def') -> 'abc%20def'
|
||||||
|
s = s.encode('utf-8')
|
||||||
|
s = s.decode('utf-8')
|
||||||
|
print "s= " + s
|
||||||
|
print "safe= " + safe
|
||||||
|
safe += always_safe
|
||||||
|
safe_map = dict()
|
||||||
|
for i in range(256):
|
||||||
|
c = chr(i)
|
||||||
|
safe_map[c] = (c in safe) and c or ('%%%02X' % i)
|
||||||
|
try:
|
||||||
|
res = map(safe_map.__getitem__, s)
|
||||||
|
except:
|
||||||
|
print "blank"
|
||||||
|
return ''
|
||||||
|
print "res= " + ''.join(res)
|
||||||
|
return ''.join(res)
|
||||||
|
|
||||||
|
|
||||||
|
def encode(keylist, arglist):
|
||||||
|
text = str()
|
||||||
|
for i in range(len(keylist)):
|
||||||
|
k = keylist[i]
|
||||||
|
v = quote(arglist[i])
|
||||||
|
text += '&' + k + '=' + v
|
||||||
|
text = text[1:]
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def Send():
|
||||||
|
data = encode(keylist, arglist)
|
||||||
|
digest_txt = data[9:29]
|
||||||
|
new_hash = hashlib.md5(digest_txt).hexdigest()
|
||||||
|
arglist[keylist.index('icognocheck')] = new_hash
|
||||||
|
data = encode(keylist, arglist)
|
||||||
|
req = urllib2.Request('http://www.cleverbot.com/webservicemin',
|
||||||
|
data, headers)
|
||||||
|
f = urllib2.urlopen(req)
|
||||||
|
reply = f.read()
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
def parseAnswers(text):
|
||||||
|
d = dict()
|
||||||
|
keys = ['text', 'sessionid', 'logurl', 'vText8', 'vText7', 'vText6',
|
||||||
|
'vText5', 'vText4', 'vText3', 'vText2', 'prevref', 'foo',
|
||||||
|
'emotionalhistory', 'ttsLocMP3', 'ttsLocTXT', 'ttsLocTXT3',
|
||||||
|
'ttsText', 'lineRef', 'lineURL', 'linePOST', 'lineChoices',
|
||||||
|
'lineChoicesAbbrev', 'typingData', 'divert']
|
||||||
|
values = text.split('\r')
|
||||||
|
i = 0
|
||||||
|
for key in keys:
|
||||||
|
d[key] = values[i]
|
||||||
|
i += 1
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def ask(inp):
|
||||||
|
arglist[keylist.index('stimulus')] = inp
|
||||||
|
if MsgList:
|
||||||
|
arglist[keylist.index('lineref')] = '!0' + str(len(
|
||||||
|
MsgList) / 2)
|
||||||
|
asw = Send()
|
||||||
|
MsgList.append(inp)
|
||||||
|
answer = parseAnswers(asw)
|
||||||
|
for k, v in answer.iteritems():
|
||||||
|
try:
|
||||||
|
arglist[keylist.index(k)] = v
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
arglist[keylist.index('emotionaloutput')] = str()
|
||||||
|
text = answer['ttsText']
|
||||||
|
MsgList.append(text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
@hook.command("cb")
|
||||||
|
def cleverbot(inp, reply=None):
|
||||||
|
reply(ask(inp))
|
||||||
|
|
||||||
|
|
||||||
|
''' # TODO: add in command to control extra verbose per channel
|
||||||
|
@hook.event('PRIVMSG')
|
||||||
|
def cbevent(inp, reply=None):
|
||||||
|
reply(ask(inp))
|
||||||
|
|
||||||
|
@hook.command("cbver", permissions=['cleverbot'])
|
||||||
|
def cleverbotverbose(inp, notice=None):
|
||||||
|
if on in input
|
||||||
|
'''
|
126
disabled_stuff/cloudbot.sh
Normal file
126
disabled_stuff/cloudbot.sh
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
#!/bin/bash
|
||||||
|
echo ""
|
||||||
|
echo " ________ ______ __ "
|
||||||
|
echo " / ____/ /___ __ ______/ / __ )____ / /_"
|
||||||
|
echo " / / / / __ \/ / / / __ / __ / __ \/ __/"
|
||||||
|
echo "/ /___/ / /_/ / /_/ / /_/ / /_/ / /_/ / /_ "
|
||||||
|
echo "\____/_/\____/\__,_/\__,_/_____/\____/\__/ "
|
||||||
|
echo " http://git.io/cloudbotirc by ClouDev "
|
||||||
|
echo ""
|
||||||
|
locatefiles() {
|
||||||
|
botfile="/bot.py"
|
||||||
|
botfile=$(pwd)$botfile
|
||||||
|
logfile="/bot.log"
|
||||||
|
logfile=$(pwd)$logfile
|
||||||
|
}
|
||||||
|
|
||||||
|
running() {
|
||||||
|
if [[ $(ps aux|grep bot.py|grep -v grep|grep -v daemon|grep -v SCREEN) != "" ]]; then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
checkbackend() {
|
||||||
|
if dpkg -l| grep ^ii|grep daemon|grep 'turns other' > /dev/null; then
|
||||||
|
backend="daemon"
|
||||||
|
elif dpkg -l| grep ^ii|grep screen|grep 'terminal multi' > /dev/null; then
|
||||||
|
backend="screen"
|
||||||
|
else
|
||||||
|
backend="manual"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setcommands() {
|
||||||
|
status() {
|
||||||
|
if running; then
|
||||||
|
echo "CloudBot is running!"
|
||||||
|
else
|
||||||
|
echo "CloudBot is not running!"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
: > $logfile
|
||||||
|
}
|
||||||
|
if [ "$backend" == "daemon" ]; then
|
||||||
|
start() {
|
||||||
|
daemon -r -n cloudbot -O $logfile python $botfile
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
daemon -n cloudbot --stop
|
||||||
|
}
|
||||||
|
elif [ "$backend" == "screen" ]; then
|
||||||
|
start() {
|
||||||
|
screen -d -m -S cloudbot -t cloudbot python $botfile > $logfile 2>&1
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
pid=`ps ax|grep -v grep|grep python|grep -v SCREEN|grep $botfile|awk '{print $1}'`
|
||||||
|
kill $pid
|
||||||
|
}
|
||||||
|
elif [ "$backend" == "manual" ]; then
|
||||||
|
start() {
|
||||||
|
$botfile
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
pid=`ps ax|grep -v grep|grep python|grep $botfile|awk '{print $1}'`
|
||||||
|
kill $pid
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
processargs() {
|
||||||
|
case $1 in
|
||||||
|
start|-start|--start)
|
||||||
|
if running; then
|
||||||
|
echo "Cannot start! Bot is already running!"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "Starting CloudBot... ($backend)"
|
||||||
|
start
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
stop|-stop|--stop)
|
||||||
|
if running; then
|
||||||
|
echo "Stopping CloudBot... ($backend)"
|
||||||
|
stop
|
||||||
|
else
|
||||||
|
echo "Cannot stop! Bot is not already running!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
restart|-restart|--restart)
|
||||||
|
if running; then
|
||||||
|
echo "Restarting CloudBot... ($backend)"
|
||||||
|
stop
|
||||||
|
sleep 3
|
||||||
|
start
|
||||||
|
else
|
||||||
|
echo "Cannot restart! Bot is not already running!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
clear|-clear|--clear)
|
||||||
|
echo "Clearing logs..."
|
||||||
|
clear
|
||||||
|
;;
|
||||||
|
status|-status|--status)
|
||||||
|
status
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage="usage: ./cloudbot {start|stop|restart|clear|status}"
|
||||||
|
echo $usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
locatefiles
|
||||||
|
checkbackend
|
||||||
|
setcommands
|
||||||
|
processargs $1
|
||||||
|
}
|
||||||
|
|
||||||
|
main $*
|
||||||
|
exit 0
|
37
disabled_stuff/correction.py
Normal file
37
disabled_stuff/correction.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
from util import hook
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
CORRECTION_RE = r'^(s|S)/.*/.*/?\S*$'
|
||||||
|
|
||||||
|
|
||||||
|
@hook.regex(CORRECTION_RE)
|
||||||
|
def correction(match, input=None, conn=None, message=None):
|
||||||
|
split = input.msg.split("/")
|
||||||
|
|
||||||
|
if len(split) == 4:
|
||||||
|
nick = split[3].lower()
|
||||||
|
else:
|
||||||
|
nick = None
|
||||||
|
|
||||||
|
find = split[1]
|
||||||
|
replace = split[2]
|
||||||
|
|
||||||
|
for item in conn.history[input.chan].__reversed__():
|
||||||
|
name, timestamp, msg = item
|
||||||
|
if msg.startswith("s/"):
|
||||||
|
# don't correct corrections, it gets really confusing
|
||||||
|
continue
|
||||||
|
if nick:
|
||||||
|
if nick != name.lower():
|
||||||
|
continue
|
||||||
|
if find in msg:
|
||||||
|
if "\x01ACTION" in msg:
|
||||||
|
msg = msg.replace("\x01ACTION ", "/me ").replace("\x01", "")
|
||||||
|
message(u"Correction, <{}> {}".format(name, msg.replace(find, "\x02" + replace + "\x02")))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return u"Did not find {} in any recent messages.".format(find)
|
||||||
|
|
|
@ -5,21 +5,21 @@ from util import http, hook
|
||||||
exchanges = {
|
exchanges = {
|
||||||
"blockchain": {
|
"blockchain": {
|
||||||
"api_url": "https://blockchain.info/ticker",
|
"api_url": "https://blockchain.info/ticker",
|
||||||
"func": lambda data: "Blockchain // Buy: \x0307${:,.2f}\x0f -"
|
"func": lambda data: u"Blockchain // Buy: \x0307${:,.2f}\x0f -"
|
||||||
" Sell: \x0307${:,.2f}\x0f".format(data["USD"]["buy"], data["USD"]["sell"])
|
u" Sell: \x0307${:,.2f}\x0f".format(data["USD"]["buy"], data["USD"]["sell"])
|
||||||
},
|
},
|
||||||
"coinbase": {
|
"coinbase": {
|
||||||
"api_url": "https://coinbase.com/api/v1/prices/spot_rate",
|
"api_url": "https://coinbase.com/api/v1/prices/spot_rate",
|
||||||
"func": lambda data: "Coinbase // Current: \x0307${:,.2f}\x0f".format(float(data['amount']))
|
"func": lambda data: u"Coinbase // Current: \x0307${:,.2f}\x0f".format(float(data['amount']))
|
||||||
},
|
},
|
||||||
"bitpay": {
|
"bitpay": {
|
||||||
"api_url": "https://bitpay.com/api/rates",
|
"api_url": "https://bitpay.com/api/rates",
|
||||||
"func": lambda data: "Bitpay // Current: \x0307${:,.2f}\x0f".format(data[0]['rate'])
|
"func": lambda data: u"Bitpay // Current: \x0307${:,.2f}\x0f".format(data[0]['rate'])
|
||||||
},
|
},
|
||||||
"bitstamp": {
|
"bitstamp": {
|
||||||
"api_url": "https://www.bitstamp.net/api/ticker/",
|
"api_url": "https://www.bitstamp.net/api/ticker/",
|
||||||
"func": lambda data: "BitStamp // Current: \x0307${:,.2f}\x0f - High: \x0307${:,.2f}\x0f -"
|
"func": lambda data: u"BitStamp // Current: \x0307${:,.2f}\x0f - High: \x0307${:,.2f}\x0f -"
|
||||||
" Low: \x0307${:,.2f}\x0f - Volume: {:,.2f} BTC".format(float(data['last']),
|
u" Low: \x0307${:,.2f}\x0f - Volume: {:,.2f} BTC".format(float(data['last']),
|
||||||
float(data['high']),
|
float(data['high']),
|
||||||
float(data['low']),
|
float(data['low']),
|
||||||
float(data['volume']))
|
float(data['volume']))
|
BIN
disabled_stuff/data/GeoLiteCity.dat
Normal file
BIN
disabled_stuff/data/GeoLiteCity.dat
Normal file
Binary file not shown.
|
@ -1,6 +1,5 @@
|
||||||
# Written by Scaevolus, updated by Lukeroge
|
# Written by Scaevolus, updated by Lukeroge
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
@ -18,15 +17,15 @@ split_re = re.compile(r'([\d+-]*)d?(F|\d*)', re.I)
|
||||||
def n_rolls(count, n):
|
def n_rolls(count, n):
|
||||||
"""roll an n-sided die count times"""
|
"""roll an n-sided die count times"""
|
||||||
if n == "F":
|
if n == "F":
|
||||||
return [random.randint(-1, 1) for x in range(min(count, 100))]
|
return [random.randint(-1, 1) for x in xrange(min(count, 100))]
|
||||||
if n < 2: # it's a coin
|
if n < 2: # it's a coin
|
||||||
if count < 100:
|
if count < 100:
|
||||||
return [random.randint(0, 1) for x in range(count)]
|
return [random.randint(0, 1) for x in xrange(count)]
|
||||||
else: # fake it
|
else: # fake it
|
||||||
return [int(random.normalvariate(.5 * count, (.75 * count) ** .5))]
|
return [int(random.normalvariate(.5 * count, (.75 * count) ** .5))]
|
||||||
else:
|
else:
|
||||||
if count < 100:
|
if count < 100:
|
||||||
return [random.randint(1, n) for x in range(count)]
|
return [random.randint(1, n) for x in xrange(count)]
|
||||||
else: # fake it
|
else: # fake it
|
||||||
return [int(random.normalvariate(.5 * (1 + n) * count,
|
return [int(random.normalvariate(.5 * (1 + n) * count,
|
||||||
(((n + 1) * (2 * n + 1) / 6. -
|
(((n + 1) * (2 * n + 1) / 6. -
|
||||||
|
@ -75,7 +74,7 @@ def dice(inp):
|
||||||
try:
|
try:
|
||||||
if count > 0:
|
if count > 0:
|
||||||
d = n_rolls(count, side)
|
d = n_rolls(count, side)
|
||||||
rolls += list(map(str, d))
|
rolls += map(str, d)
|
||||||
total += sum(d)
|
total += sum(d)
|
||||||
else:
|
else:
|
||||||
d = n_rolls(-count, side)
|
d = n_rolls(-count, side)
|
|
@ -19,10 +19,10 @@ def define(inp):
|
||||||
'//div[@class="example"]')
|
'//div[@class="example"]')
|
||||||
|
|
||||||
if not definition:
|
if not definition:
|
||||||
return 'No results for ' + inp + ' :('
|
return u'No results for {} :('.format(inp)
|
||||||
|
|
||||||
def format_output(show_examples):
|
def format_output(show_examples):
|
||||||
result = '{}: '.format(h.xpath('//dt[@class="title-word"]/a/text()')[0])
|
result = u'{}: '.format(h.xpath('//dt[@class="title-word"]/a/text()')[0])
|
||||||
|
|
||||||
correction = h.xpath('//span[@class="correct-word"]/text()')
|
correction = h.xpath('//span[@class="correct-word"]/text()')
|
||||||
if correction:
|
if correction:
|
||||||
|
@ -41,7 +41,7 @@ def define(inp):
|
||||||
for article in sections:
|
for article in sections:
|
||||||
result += article[0]
|
result += article[0]
|
||||||
if len(article) > 2:
|
if len(article) > 2:
|
||||||
result += ' '.join('{}. {}'.format(n + 1, section)
|
result += u' '.join(u'{}. {}'.format(n + 1, section)
|
||||||
for n, section in enumerate(article[1:]))
|
for n, section in enumerate(article[1:]))
|
||||||
else:
|
else:
|
||||||
result += article[1] + ' '
|
result += article[1] + ' '
|
||||||
|
@ -77,7 +77,7 @@ def etymology(inp):
|
||||||
etym = h.xpath('//dl')
|
etym = h.xpath('//dl')
|
||||||
|
|
||||||
if not etym:
|
if not etym:
|
||||||
return 'No etymology found for {} :('.format(inp)
|
return u'No etymology found for {} :('.format(inp)
|
||||||
|
|
||||||
etym = etym[0].text_content()
|
etym = etym[0].text_content()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import urllib.parse
|
import urlparse
|
||||||
|
|
||||||
from util import hook, http
|
from util import hook, http
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ def down(inp):
|
||||||
if 'http://' not in inp:
|
if 'http://' not in inp:
|
||||||
inp = 'http://' + inp
|
inp = 'http://' + inp
|
||||||
|
|
||||||
inp = 'http://' + urllib.parse.urlparse(inp).netloc
|
inp = 'http://' + urlparse.urlparse(inp).netloc
|
||||||
|
|
||||||
# http://mail.python.org/pipermail/python-list/2006-December/589854.html
|
# http://mail.python.org/pipermail/python-list/2006-December/589854.html
|
||||||
try:
|
try:
|
|
@ -9,15 +9,15 @@ color_codes = {
|
||||||
"<y>": "\x02"
|
"<y>": "\x02"
|
||||||
}
|
}
|
||||||
|
|
||||||
with open("./data/8ball_responses.txt") as f:
|
with open("plugins/data/8ball_responses.txt") as f:
|
||||||
responses = [line.strip() for line in
|
responses = [line.strip() for line in
|
||||||
f.readlines() if not line.startswith("//")]
|
f.readlines() if not line.startswith("//")]
|
||||||
|
|
||||||
|
|
||||||
@hook.command()
|
@hook.command('8ball')
|
||||||
def eightball(input, conn):
|
def eightball(inp, action=None):
|
||||||
"""8ball <question> -- The all knowing magic eight ball,
|
"""8ball <question> -- The all knowing magic eight ball,
|
||||||
in electronic form. Ask and it shall be answered!"""
|
in electronic form. Ask and it shall be answered!"""
|
||||||
|
|
||||||
magic = text.multiword_replace(random.choice(responses), color_codes)
|
magic = text.multiword_replace(random.choice(responses), color_codes)
|
||||||
input.action("shakes the magic 8 ball... {}".format(magic))
|
action("shakes the magic 8 ball... {}".format(magic))
|
|
@ -37,8 +37,8 @@ def get_salt(bot):
|
||||||
"""generate an encryption salt if none exists, then returns the salt"""
|
"""generate an encryption salt if none exists, then returns the salt"""
|
||||||
if not bot.config.get("random_salt", False):
|
if not bot.config.get("random_salt", False):
|
||||||
bot.config["random_salt"] = hashlib.md5(os.urandom(16)).hexdigest()
|
bot.config["random_salt"] = hashlib.md5(os.urandom(16)).hexdigest()
|
||||||
bot.config.save_config()
|
json.dump(bot.config, open('config', 'w'), sort_keys=True, indent=2)
|
||||||
return bot.config.get("random_salt")
|
return bot.config["random_salt"]
|
||||||
|
|
||||||
|
|
||||||
@hook.command
|
@hook.command
|
||||||
|
@ -69,8 +69,7 @@ def encrypt(inp, bot=None, db=None, notice=None):
|
||||||
|
|
||||||
# store the encoded text and IV in the DB for decoding later
|
# store the encoded text and IV in the DB for decoding later
|
||||||
db.execute("insert or replace into encryption(encrypted, iv)"
|
db.execute("insert or replace into encryption(encrypted, iv)"
|
||||||
"values(:encoded,:iv)", {'encoded': encoded,
|
"values(?,?)", (encoded, iv_encoded))
|
||||||
'iv': iv_encoded})
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return encoded
|
return encoded
|
||||||
|
@ -98,7 +97,7 @@ def decrypt(inp, bot=None, db=None, notice=None):
|
||||||
|
|
||||||
# get the encoded IV from the database and decode it
|
# get the encoded IV from the database and decode it
|
||||||
iv_encoded = db.execute("select iv from encryption where"
|
iv_encoded = db.execute("select iv from encryption where"
|
||||||
" encrypted=:text", {'text': text}).fetchone()[0]
|
" encrypted=?", (text,)).fetchone()[0]
|
||||||
iv = base64.b64decode(iv_encoded)
|
iv = base64.b64decode(iv_encoded)
|
||||||
|
|
||||||
# create AES cipher, decode text, decrypt text, and unpad it
|
# create AES cipher, decode text, decrypt text, and unpad it
|
|
@ -114,6 +114,10 @@ def info(inp, notice=None, db=None):
|
||||||
@hook.regex(r'^\? ?(.+)')
|
@hook.regex(r'^\? ?(.+)')
|
||||||
def factoid(inp, message=None, db=None, bot=None, action=None, conn=None, input=None):
|
def factoid(inp, message=None, db=None, bot=None, action=None, conn=None, input=None):
|
||||||
"""?<word> -- Shows what data is associated with <word>."""
|
"""?<word> -- Shows what data is associated with <word>."""
|
||||||
|
try:
|
||||||
|
prefix_on = bot.config["plugins"]["factoids"].get("prefix", False)
|
||||||
|
except KeyError:
|
||||||
|
prefix_on = False
|
||||||
|
|
||||||
db_init(db)
|
db_init(db)
|
||||||
|
|
||||||
|
@ -151,5 +155,8 @@ def factoid(inp, message=None, db=None, bot=None, action=None, conn=None, input=
|
||||||
message(http.get(url))
|
message(http.get(url))
|
||||||
except http.HttpError:
|
except http.HttpError:
|
||||||
message("Could not fetch URL.")
|
message("Could not fetch URL.")
|
||||||
|
else:
|
||||||
|
if prefix_on:
|
||||||
|
message("\x02[{}]:\x02 {}".format(factoid_id, result))
|
||||||
else:
|
else:
|
||||||
message(result)
|
message(result)
|
|
@ -1,4 +1,4 @@
|
||||||
from urllib.parse import quote_plus
|
from urllib import quote_plus
|
||||||
|
|
||||||
from util import hook, http
|
from util import hook, http
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ def bancount(inp):
|
||||||
services = request["stats"]["service"]
|
services = request["stats"]["service"]
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
for service, ban_count in list(services.items()):
|
for service, ban_count in services.items():
|
||||||
if ban_count != 0:
|
if ban_count != 0:
|
||||||
out.append("{}: \x02{}\x02".format(service, ban_count))
|
out.append("{}: \x02{}\x02".format(service, ban_count))
|
||||||
else:
|
else:
|
|
@ -3,8 +3,7 @@ import random
|
||||||
from util import hook
|
from util import hook
|
||||||
|
|
||||||
|
|
||||||
with open("./data/fortunes.txt") as f:
|
with open("plugins/data/fortunes.txt") as f:
|
||||||
|
|
||||||
fortunes = [line.strip() for line in f.readlines()
|
fortunes = [line.strip() for line in f.readlines()
|
||||||
if not line.startswith("//")]
|
if not line.startswith("//")]
|
||||||
|
|
13
disabled_stuff/freddy.py
Normal file
13
disabled_stuff/freddy.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from util import hook, http, web
|
||||||
|
from subprocess import check_output, CalledProcessError
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def freddycode(inp):
|
||||||
|
"""freddycode <code> - Check if the Freddy Fresh code is correct."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return "Freddy: '%s' ist %s" % (inp, \
|
||||||
|
check_output(["/bin/freddycheck", inp]))
|
||||||
|
except CalledProcessError as err:
|
||||||
|
return "Freddy: Skript returned %s" % (str(err))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os.path
|
import os.path
|
||||||
import json
|
import json
|
||||||
import gzip
|
import gzip
|
||||||
from io import StringIO
|
from StringIO import StringIO
|
||||||
|
|
||||||
import pygeoip
|
import pygeoip
|
||||||
|
|
||||||
|
@ -9,22 +9,22 @@ from util import hook, http
|
||||||
|
|
||||||
|
|
||||||
# load region database
|
# load region database
|
||||||
with open("./data/geoip_regions.json", "rb") as f:
|
with open("./plugins/data/geoip_regions.json", "rb") as f:
|
||||||
regions = json.loads(f.read())
|
regions = json.loads(f.read())
|
||||||
|
|
||||||
if os.path.isfile(os.path.abspath("./data/GeoLiteCity.dat")):
|
if os.path.isfile(os.path.abspath("./plugins/data/GeoLiteCity.dat")):
|
||||||
# initialise geolocation database
|
# initialise geolocation database
|
||||||
geo = pygeoip.GeoIP(os.path.abspath("./data/GeoLiteCity.dat"))
|
geo = pygeoip.GeoIP(os.path.abspath("./plugins/data/GeoLiteCity.dat"))
|
||||||
else:
|
else:
|
||||||
download = http.get("http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz")
|
download = http.get("http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz")
|
||||||
string_io = StringIO(download)
|
string_io = StringIO(download)
|
||||||
geoip_file = gzip.GzipFile(fileobj=string_io, mode='rb')
|
geoip_file = gzip.GzipFile(fileobj=string_io, mode='rb')
|
||||||
|
|
||||||
output = open(os.path.abspath("./data/GeoLiteCity.dat"), 'wb')
|
output = open(os.path.abspath("./plugins/data/GeoLiteCity.dat"), 'wb')
|
||||||
output.write(geoip_file.read())
|
output.write(geoip_file.read())
|
||||||
output.close()
|
output.close()
|
||||||
|
|
||||||
geo = pygeoip.GeoIP(os.path.abspath("./data/GeoLiteCity.dat"))
|
geo = pygeoip.GeoIP(os.path.abspath("./plugins/data/GeoLiteCity.dat"))
|
||||||
|
|
||||||
|
|
||||||
@hook.command
|
@hook.command
|
||||||
|
@ -51,4 +51,4 @@ def geoip(inp):
|
||||||
data["cc"] = record["country_code"] or "N/A"
|
data["cc"] = record["country_code"] or "N/A"
|
||||||
data["country"] = record["country_name"] or "Unknown"
|
data["country"] = record["country_name"] or "Unknown"
|
||||||
data["city"] = record["city"] or "Unknown"
|
data["city"] = record["city"] or "Unknown"
|
||||||
return "\x02Country:\x02 {country} ({cc}), \x02City:\x02 {city}{region}".format(**data)
|
return u"\x02Country:\x02 {country} ({cc}), \x02City:\x02 {city}{region}".format(**data)
|
|
@ -1,5 +1,5 @@
|
||||||
import json
|
import json
|
||||||
import urllib.request, urllib.error, urllib.parse
|
import urllib2
|
||||||
|
|
||||||
from util import hook, http
|
from util import hook, http
|
||||||
|
|
||||||
|
@ -37,18 +37,18 @@ def ghissues(inp):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return "Invalid syntax. .github issues username/repo [number]"
|
return "Invalid syntax. .github issues username/repo [number]"
|
||||||
try:
|
try:
|
||||||
url += "/{}".format(args[1])
|
url += "/%s" % args[1]
|
||||||
number = True
|
number = True
|
||||||
except IndexError:
|
except IndexError:
|
||||||
number = False
|
number = False
|
||||||
try:
|
try:
|
||||||
data = json.loads(http.open(url).read())
|
data = json.loads(http.open(url).read())
|
||||||
print(url)
|
print url
|
||||||
if not number:
|
if not number:
|
||||||
try:
|
try:
|
||||||
data = data[0]
|
data = data[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
print(data)
|
print data
|
||||||
return "Repo has no open issues"
|
return "Repo has no open issues"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return "Invalid data returned. Check arguments (.github issues username/repo [number]"
|
return "Invalid data returned. Check arguments (.github issues username/repo [number]"
|
||||||
|
@ -56,9 +56,9 @@ def ghissues(inp):
|
||||||
fmt1 = "Issue: #%s (%s) by %s: %s %s" # (number, state, user.login, title, gitio.gitio(data.url))
|
fmt1 = "Issue: #%s (%s) by %s: %s %s" # (number, state, user.login, title, gitio.gitio(data.url))
|
||||||
number = data["number"]
|
number = data["number"]
|
||||||
if data["state"] == "open":
|
if data["state"] == "open":
|
||||||
state = "\x033\x02OPEN\x02\x0f"
|
state = u"\x033\x02OPEN\x02\x0f"
|
||||||
else:
|
else:
|
||||||
state = "\x034\x02CLOSED\x02\x0f by {}".format(data["closed_by"]["login"])
|
state = u"\x034\x02CLOSED\x02\x0f by {}".format(data["closed_by"]["login"])
|
||||||
user = data["user"]["login"]
|
user = data["user"]["login"]
|
||||||
title = data["title"]
|
title = data["title"]
|
||||||
summary = truncate(data["body"])
|
summary = truncate(data["body"])
|
||||||
|
@ -93,12 +93,12 @@ def gitio(inp):
|
||||||
url = 'url=' + str(url)
|
url = 'url=' + str(url)
|
||||||
if code:
|
if code:
|
||||||
url = url + '&code=' + str(code)
|
url = url + '&code=' + str(code)
|
||||||
req = urllib.request.Request(url='http://git.io', data=url)
|
req = urllib2.Request(url='http://git.io', data=url)
|
||||||
|
|
||||||
# try getting url, catch http error
|
# try getting url, catch http error
|
||||||
try:
|
try:
|
||||||
f = urllib.request.urlopen(req)
|
f = urllib2.urlopen(req)
|
||||||
except urllib.error.HTTPError:
|
except urllib2.HTTPError:
|
||||||
return "Failed to get URL!"
|
return "Failed to get URL!"
|
||||||
urlinfo = str(f.info())
|
urlinfo = str(f.info())
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ def gitio(inp):
|
||||||
if row.find("Location") != -1:
|
if row.find("Location") != -1:
|
||||||
location = row
|
location = row
|
||||||
|
|
||||||
print(status)
|
print status
|
||||||
if not "201" in status:
|
if not "201" in status:
|
||||||
return "Failed to get URL!"
|
return "Failed to get URL!"
|
||||||
|
|
|
@ -48,4 +48,4 @@ def google(inp):
|
||||||
content = http.html.fromstring(content).text_content()
|
content = http.html.fromstring(content).text_content()
|
||||||
content = text.truncate_str(content, 150)
|
content = text.truncate_str(content, 150)
|
||||||
|
|
||||||
return '{} -- \x02{}\x02: "{}"'.format(result['unescapedUrl'], title, content)
|
return u'{} -- \x02{}\x02: "{}"'.format(result['unescapedUrl'], title, content)
|
|
@ -3,7 +3,7 @@ A Google API key is required and retrieved from the bot config file.
|
||||||
Since December 1, 2011, the Google Translate API is a paid service only.
|
Since December 1, 2011, the Google Translate API is a paid service only.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import html.entities
|
import htmlentitydefs
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from util import hook, http
|
from util import hook, http
|
||||||
|
@ -22,15 +22,15 @@ def unescape(text):
|
||||||
# character reference
|
# character reference
|
||||||
try:
|
try:
|
||||||
if text[:3] == "&#x":
|
if text[:3] == "&#x":
|
||||||
return chr(int(text[3:-1], 16))
|
return unichr(int(text[3:-1], 16))
|
||||||
else:
|
else:
|
||||||
return chr(int(text[2:-1]))
|
return unichr(int(text[2:-1]))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# named entity
|
# named entity
|
||||||
try:
|
try:
|
||||||
text = chr(html.entities.name2codepoint[text[1:-1]])
|
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return text # leave as is
|
return text # leave as is
|
||||||
|
@ -83,7 +83,7 @@ def translate(inp, bot=None):
|
||||||
if not api_key:
|
if not api_key:
|
||||||
return "This command requires a paid API key."
|
return "This command requires a paid API key."
|
||||||
|
|
||||||
args = inp.split(' ', 2)
|
args = inp.split(u' ', 2)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(args) >= 2:
|
if len(args) >= 2:
|
||||||
|
@ -100,7 +100,7 @@ def translate(inp, bot=None):
|
||||||
return goog_trans(api_key, args[1] + ' ' + args[2], sl, 'en')
|
return goog_trans(api_key, args[1] + ' ' + args[2], sl, 'en')
|
||||||
return goog_trans(api_key, args[2], sl, tl)
|
return goog_trans(api_key, args[2], sl, tl)
|
||||||
return goog_trans(api_key, inp, '', 'en')
|
return goog_trans(api_key, inp, '', 'en')
|
||||||
except IOError as e:
|
except IOError, e:
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from util import hook
|
from util import hook
|
||||||
from urllib.parse import unquote
|
from urllib import unquote
|
||||||
|
|
||||||
@hook.command(autohelp=False)
|
@hook.command(autohelp=False)
|
||||||
def googleurl(inp, db=None, nick=None):
|
def googleurl(inp, db=None, nick=None):
|
|
@ -22,11 +22,8 @@ def track_seen(input, message_time, db, conn):
|
||||||
# keep private messages private
|
# keep private messages private
|
||||||
if input.chan[:1] == "#" and not re.findall('^s/.*/.*/$', input.msg.lower()):
|
if input.chan[:1] == "#" and not re.findall('^s/.*/.*/$', input.msg.lower()):
|
||||||
db.execute("insert or replace into seen_user(name, time, quote, chan, host)"
|
db.execute("insert or replace into seen_user(name, time, quote, chan, host)"
|
||||||
"values(:name,:time,:quote,:chan,:host)", {'name': input.nick.lower(),
|
"values(?,?,?,?,?)", (input.nick.lower(), message_time, input.msg,
|
||||||
'time': time.time(),
|
input.chan, input.mask))
|
||||||
'quote': input.msg,
|
|
||||||
'chan': input.chan,
|
|
||||||
'host': input.mask})
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,6 +56,7 @@ def resethistory(inp, input=None, conn=None):
|
||||||
# wat
|
# wat
|
||||||
return "There is no history for this channel."
|
return "There is no history for this channel."
|
||||||
|
|
||||||
|
"""seen.py: written by sklnd in about two beers July 2009"""
|
||||||
|
|
||||||
@hook.command
|
@hook.command
|
||||||
def seen(inp, nick='', chan='', db=None, input=None, conn=None):
|
def seen(inp, nick='', chan='', db=None, input=None, conn=None):
|
||||||
|
@ -76,7 +74,7 @@ def seen(inp, nick='', chan='', db=None, input=None, conn=None):
|
||||||
db_init(db, conn.name)
|
db_init(db, conn.name)
|
||||||
|
|
||||||
last_seen = db.execute("select name, time, quote from seen_user where name"
|
last_seen = db.execute("select name, time, quote from seen_user where name"
|
||||||
" like :name and chan = :chan", {'name': inp, 'chan': chan}).fetchone()
|
" like ? and chan = ?", (inp, chan)).fetchone()
|
||||||
|
|
||||||
if last_seen:
|
if last_seen:
|
||||||
reltime = timesince.timesince(last_seen[1])
|
reltime = timesince.timesince(last_seen[1])
|
|
@ -14,11 +14,6 @@ def db_init(db):
|
||||||
db_ready = True
|
db_ready = True
|
||||||
|
|
||||||
|
|
||||||
@hook.onload
|
|
||||||
def init(paraml, db=None):
|
|
||||||
db_init(db)
|
|
||||||
|
|
||||||
|
|
||||||
@hook.command(autohelp=False)
|
@hook.command(autohelp=False)
|
||||||
def horoscope(inp, db=None, notice=None, nick=None):
|
def horoscope(inp, db=None, notice=None, nick=None):
|
||||||
"""horoscope <sign> -- Get your horoscope."""
|
"""horoscope <sign> -- Get your horoscope."""
|
||||||
|
@ -34,8 +29,8 @@ def horoscope(inp, db=None, notice=None, nick=None):
|
||||||
db.execute("create table if not exists horoscope(nick primary key, sign)")
|
db.execute("create table if not exists horoscope(nick primary key, sign)")
|
||||||
|
|
||||||
if not sign:
|
if not sign:
|
||||||
sign = db.execute("select sign from horoscope where "
|
sign = db.execute("select sign from horoscope where nick=lower(?)",
|
||||||
"nick=lower(:nick)", {'nick': nick}).fetchone()
|
(nick,)).fetchone()
|
||||||
if not sign:
|
if not sign:
|
||||||
notice("horoscope <sign> -- Get your horoscope")
|
notice("horoscope <sign> -- Get your horoscope")
|
||||||
return
|
return
|
||||||
|
@ -46,7 +41,7 @@ def horoscope(inp, db=None, notice=None, nick=None):
|
||||||
|
|
||||||
title = soup.find_all('h1', {'class': 'h1b'})[1]
|
title = soup.find_all('h1', {'class': 'h1b'})[1]
|
||||||
horoscope_text = soup.find('div', {'class': 'fontdef1'})
|
horoscope_text = soup.find('div', {'class': 'fontdef1'})
|
||||||
result = "\x02{}\x02 {}".format(title, horoscope_text)
|
result = u"\x02%s\x02 %s" % (title, horoscope_text)
|
||||||
result = text.strip_html(result)
|
result = text.strip_html(result)
|
||||||
#result = unicode(result, "utf8").replace('flight ','')
|
#result = unicode(result, "utf8").replace('flight ','')
|
||||||
|
|
||||||
|
@ -54,8 +49,8 @@ def horoscope(inp, db=None, notice=None, nick=None):
|
||||||
return "Could not get the horoscope for {}.".format(inp)
|
return "Could not get the horoscope for {}.".format(inp)
|
||||||
|
|
||||||
if inp and not dontsave:
|
if inp and not dontsave:
|
||||||
db.execute("insert or replace into horoscope(nick, sign) values (:nick, :sign)",
|
db.execute("insert or replace into horoscope(nick, sign) values (?,?)",
|
||||||
{'nick': nick.lower(), 'sign': sign})
|
(nick.lower(), sign))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return result
|
return result
|
|
@ -1,4 +1,4 @@
|
||||||
from urllib.parse import urlencode
|
from urllib import urlencode
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from util import hook, http, timeformat
|
from util import hook, http, timeformat
|
|
@ -1,4 +1,4 @@
|
||||||
import urllib.parse
|
import urlparse
|
||||||
|
|
||||||
from util import hook, http, urlnorm
|
from util import hook, http, urlnorm
|
||||||
|
|
||||||
|
@ -8,15 +8,14 @@ def isup(inp):
|
||||||
"""isup -- uses isup.me to see if a site is up or not"""
|
"""isup -- uses isup.me to see if a site is up or not"""
|
||||||
|
|
||||||
# slightly overcomplicated, esoteric URL parsing
|
# slightly overcomplicated, esoteric URL parsing
|
||||||
scheme, auth, path, query, fragment = urllib.parse.urlsplit(inp.strip())
|
scheme, auth, path, query, fragment = urlparse.urlsplit(inp.strip())
|
||||||
|
|
||||||
domain = auth.encode('utf-8') or path.encode('utf-8')
|
domain = auth.encode('utf-8') or path.encode('utf-8')
|
||||||
url = urlnorm.normalize(domain, assume_scheme="http")
|
url = urlnorm.normalize(domain, assume_scheme="http")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
soup = http.get_soup('http://isup.me/' + domain)
|
soup = http.get_soup('http://isup.me/' + domain)
|
||||||
except http.HTTPError as xxx_todo_changeme:
|
except http.HTTPError, http.URLError:
|
||||||
http.URLError = xxx_todo_changeme
|
|
||||||
return "Could not get status."
|
return "Could not get status."
|
||||||
|
|
||||||
content = soup.find('div').text.strip()
|
content = soup.find('div').text.strip()
|
|
@ -26,7 +26,7 @@ def kill(inp, action=None, nick=None, conn=None, notice=None):
|
||||||
"user": target
|
"user": target
|
||||||
}
|
}
|
||||||
|
|
||||||
with open("./data/kills.json") as f:
|
with open("plugins/data/kills.json") as f:
|
||||||
generator = get_generator(f.read(), variables)
|
generator = get_generator(f.read(), variables)
|
||||||
|
|
||||||
# act out the message
|
# act out the message
|
|
@ -25,8 +25,8 @@ def lastfm(inp, nick='', db=None, bot=None, notice=None):
|
||||||
db.execute("create table if not exists lastfm(nick primary key, acc)")
|
db.execute("create table if not exists lastfm(nick primary key, acc)")
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
user = db.execute("select acc from lastfm where nick=lower(:nick)",
|
user = db.execute("select acc from lastfm where nick=lower(?)",
|
||||||
{'nick': nick}).fetchone()
|
(nick,)).fetchone()
|
||||||
if not user:
|
if not user:
|
||||||
notice(lastfm.__doc__)
|
notice(lastfm.__doc__)
|
||||||
return
|
return
|
||||||
|
@ -36,10 +36,10 @@ def lastfm(inp, nick='', db=None, bot=None, notice=None):
|
||||||
api_key=api_key, user=user, limit=1)
|
api_key=api_key, user=user, limit=1)
|
||||||
|
|
||||||
if 'error' in response:
|
if 'error' in response:
|
||||||
return "Error: {}.".format(response["message"])
|
return u"Error: {}.".format(response["message"])
|
||||||
|
|
||||||
if not "track" in response["recenttracks"] or len(response["recenttracks"]["track"]) == 0:
|
if not "track" in response["recenttracks"] or len(response["recenttracks"]["track"]) == 0:
|
||||||
return 'No recent tracks for user "{}" found.'.format(user)
|
return u'No recent tracks for user "{}" found.'.format(user)
|
||||||
|
|
||||||
tracks = response["recenttracks"]["track"]
|
tracks = response["recenttracks"]["track"]
|
||||||
|
|
||||||
|
@ -66,18 +66,18 @@ def lastfm(inp, nick='', db=None, bot=None, notice=None):
|
||||||
album = track["album"]["#text"]
|
album = track["album"]["#text"]
|
||||||
artist = track["artist"]["#text"]
|
artist = track["artist"]["#text"]
|
||||||
|
|
||||||
out = '{} {} "{}"'.format(user, status, title)
|
out = u'{} {} "{}"'.format(user, status, title)
|
||||||
if artist:
|
if artist:
|
||||||
out += " by \x02{}\x0f".format(artist)
|
out += u" by \x02{}\x0f".format(artist)
|
||||||
if album:
|
if album:
|
||||||
out += " from the album \x02{}\x0f".format(album)
|
out += u" from the album \x02{}\x0f".format(album)
|
||||||
|
|
||||||
# append ending based on what type it was
|
# append ending based on what type it was
|
||||||
out += ending
|
out += ending
|
||||||
|
|
||||||
if inp and not dontsave:
|
if inp and not dontsave:
|
||||||
db.execute("insert or replace into lastfm(nick, acc) values "
|
db.execute("insert or replace into lastfm(nick, acc) values (?,?)",
|
||||||
"(:nick, :account)", {'nick': nick.lower(), 'account': user})
|
(nick.lower(), user))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
return out
|
return out
|
|
@ -6,7 +6,7 @@ from util import hook, web, http
|
||||||
def lmgtfy(inp):
|
def lmgtfy(inp):
|
||||||
"""lmgtfy [phrase] - Posts a google link for the specified phrase"""
|
"""lmgtfy [phrase] - Posts a google link for the specified phrase"""
|
||||||
|
|
||||||
link = "http://lmgtfy.com/?q={}".format(http.quote_plus(inp))
|
link = u"http://lmgtfy.com/?q={}".format(http.quote_plus(inp))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return web.isgd(link)
|
return web.isgd(link)
|
|
@ -3,7 +3,6 @@ log.py: written by Scaevolus 2009
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import codecs
|
import codecs
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
|
@ -89,12 +88,12 @@ def get_log_fd(dir, server, chan):
|
||||||
return fd
|
return fd
|
||||||
|
|
||||||
|
|
||||||
#@hook.singlethread
|
@hook.singlethread
|
||||||
@hook.event('*')
|
@hook.event('*')
|
||||||
def log(paraml, input=None, bot=None):
|
def log(paraml, input=None, bot=None):
|
||||||
timestamp = gmtime(timestamp_format)
|
timestamp = gmtime(timestamp_format)
|
||||||
|
|
||||||
fd = get_log_fd(bot.data_dir, input.server, 'raw')
|
fd = get_log_fd(bot.persist_dir, input.server, 'raw')
|
||||||
fd.write(timestamp + ' ' + input.raw + '\n')
|
fd.write(timestamp + ' ' + input.raw + '\n')
|
||||||
|
|
||||||
if input.command == 'QUIT': # these are temporary fixes until proper
|
if input.command == 'QUIT': # these are temporary fixes until proper
|
||||||
|
@ -108,9 +107,7 @@ def log(paraml, input=None, bot=None):
|
||||||
return
|
return
|
||||||
|
|
||||||
if input.chan:
|
if input.chan:
|
||||||
fd = get_log_fd(bot.data_dir, input.server, input.chan)
|
fd = get_log_fd(bot.persist_dir, input.server, input.chan)
|
||||||
fd.write(timestamp + ' ' + beau + '\n')
|
fd.write(timestamp + ' ' + beau + '\n')
|
||||||
|
|
||||||
out = "{} {} {}".format(timestamp, input.chan, beau)
|
print timestamp, input.chan, beau.encode('utf8', 'ignore')
|
||||||
|
|
||||||
bot.logger.debug(out)
|
|
|
@ -1,7 +1,7 @@
|
||||||
# metacritic.com scraper
|
# metacritic.com scraper
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from urllib.error import HTTPError
|
from urllib2 import HTTPError
|
||||||
|
|
||||||
from util import hook, http
|
from util import hook, http
|
||||||
|
|
|
@ -56,7 +56,7 @@ def plugin_random():
|
||||||
|
|
||||||
while not results:
|
while not results:
|
||||||
plugin_number = random.randint(1, count_total)
|
plugin_number = random.randint(1, count_total)
|
||||||
print("trying {}".format(plugin_number))
|
print "trying {}".format(plugin_number)
|
||||||
try:
|
try:
|
||||||
results = http.get_json(random_url.format(plugin_number))
|
results = http.get_json(random_url.format(plugin_number))
|
||||||
except (http.HTTPError, http.URLError) as e:
|
except (http.HTTPError, http.URLError) as e:
|
||||||
|
@ -84,7 +84,7 @@ def format_output(data):
|
||||||
description = text.truncate_str(data['description'], 30)
|
description = text.truncate_str(data['description'], 30)
|
||||||
url = data['website']
|
url = data['website']
|
||||||
authors = data['authors'][0]
|
authors = data['authors'][0]
|
||||||
authors = authors[0] + "\u200b" + authors[1:]
|
authors = authors[0] + u"\u200b" + authors[1:]
|
||||||
stage = data['stage']
|
stage = data['stage']
|
||||||
|
|
||||||
current_version = data['versions'][0]
|
current_version = data['versions'][0]
|
||||||
|
@ -97,11 +97,11 @@ def format_output(data):
|
||||||
link = web.try_isgd(current_version['link'])
|
link = web.try_isgd(current_version['link'])
|
||||||
|
|
||||||
if description:
|
if description:
|
||||||
line_a = "\x02{}\x02, by \x02{}\x02 - {} - ({}) \x02{}".format(name, authors, description, stage, url)
|
line_a = u"\x02{}\x02, by \x02{}\x02 - {} - ({}) \x02{}".format(name, authors, description, stage, url)
|
||||||
else:
|
else:
|
||||||
line_a = "\x02{}\x02, by \x02{}\x02 ({}) \x02{}".format(name, authors, stage, url)
|
line_a = u"\x02{}\x02, by \x02{}\x02 ({}) \x02{}".format(name, authors, stage, url)
|
||||||
|
|
||||||
line_b = "Last release: \x02v{}\x02 for \x02{}\x02 at {} \x02{}\x02".format(version_number, bukkit_versions,
|
line_b = u"Last release: \x02v{}\x02 for \x02{}\x02 at {} \x02{}\x02".format(version_number, bukkit_versions,
|
||||||
last_update, link)
|
last_update, link)
|
||||||
|
|
||||||
return line_a, line_b
|
return line_a, line_b
|
|
@ -24,7 +24,7 @@ class Recipe(object):
|
||||||
return self.line
|
return self.line
|
||||||
|
|
||||||
|
|
||||||
with open("./data/recipes.txt") as f:
|
with open("plugins/data/recipes.txt") as f:
|
||||||
for line in f.readlines():
|
for line in f.readlines():
|
||||||
if line.startswith("//"):
|
if line.startswith("//"):
|
||||||
continue
|
continue
|
||||||
|
@ -39,7 +39,7 @@ with open("./data/recipes.txt") as f:
|
||||||
|
|
||||||
ids = []
|
ids = []
|
||||||
|
|
||||||
with open("./data/itemids.txt") as f:
|
with open("plugins/data/itemids.txt") as f:
|
||||||
for line in f.readlines():
|
for line in f.readlines():
|
||||||
if line.startswith("//"):
|
if line.startswith("//"):
|
||||||
continue
|
continue
|
|
@ -13,12 +13,12 @@ except ImportError:
|
||||||
has_dns = False
|
has_dns = False
|
||||||
|
|
||||||
|
|
||||||
mc_colors = [('\xa7f', '\x0300'), ('\xa70', '\x0301'), ('\xa71', '\x0302'), ('\xa72', '\x0303'),
|
mc_colors = [(u'\xa7f', u'\x0300'), (u'\xa70', u'\x0301'), (u'\xa71', u'\x0302'), (u'\xa72', u'\x0303'),
|
||||||
('\xa7c', '\x0304'), ('\xa74', '\x0305'), ('\xa75', '\x0306'), ('\xa76', '\x0307'),
|
(u'\xa7c', u'\x0304'), (u'\xa74', u'\x0305'), (u'\xa75', u'\x0306'), (u'\xa76', u'\x0307'),
|
||||||
('\xa7e', '\x0308'), ('\xa7a', '\x0309'), ('\xa73', '\x0310'), ('\xa7b', '\x0311'),
|
(u'\xa7e', u'\x0308'), (u'\xa7a', u'\x0309'), (u'\xa73', u'\x0310'), (u'\xa7b', u'\x0311'),
|
||||||
('\xa71', '\x0312'), ('\xa7d', '\x0313'), ('\xa78', '\x0314'), ('\xa77', '\x0315'),
|
(u'\xa71', u'\x0312'), (u'\xa7d', u'\x0313'), (u'\xa78', u'\x0314'), (u'\xa77', u'\x0315'),
|
||||||
('\xa7l', '\x02'), ('\xa79', '\x0310'), ('\xa7o', '\t'), ('\xa7m', '\x13'),
|
(u'\xa7l', u'\x02'), (u'\xa79', u'\x0310'), (u'\xa7o', u'\t'), (u'\xa7m', u'\x13'),
|
||||||
('\xa7r', '\x0f'), ('\xa7n', '\x15')]
|
(u'\xa7r', u'\x0f'), (u'\xa7n', u'\x15')]
|
||||||
|
|
||||||
|
|
||||||
## EXCEPTIONS
|
## EXCEPTIONS
|
||||||
|
@ -98,9 +98,9 @@ def mcping_modern(host, port):
|
||||||
try:
|
try:
|
||||||
version = data["version"]["name"]
|
version = data["version"]["name"]
|
||||||
try:
|
try:
|
||||||
desc = " ".join(data["description"]["text"].split())
|
desc = u" ".join(data["description"]["text"].split())
|
||||||
except TypeError:
|
except TypeError:
|
||||||
desc = " ".join(data["description"].split())
|
desc = u" ".join(data["description"].split())
|
||||||
max_players = data["players"]["max"]
|
max_players = data["players"]["max"]
|
||||||
online = data["players"]["online"]
|
online = data["players"]["online"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -136,10 +136,10 @@ def mcping_legacy(host, port):
|
||||||
|
|
||||||
length = struct.unpack('!h', sock.recv(2))[0]
|
length = struct.unpack('!h', sock.recv(2))[0]
|
||||||
values = sock.recv(length * 2).decode('utf-16be')
|
values = sock.recv(length * 2).decode('utf-16be')
|
||||||
data = values.split('\x00') # try to decode data using new format
|
data = values.split(u'\x00') # try to decode data using new format
|
||||||
if len(data) == 1:
|
if len(data) == 1:
|
||||||
# failed to decode data, server is using old format
|
# failed to decode data, server is using old format
|
||||||
data = values.split('\xa7')
|
data = values.split(u'\xa7')
|
||||||
output = {
|
output = {
|
||||||
"motd": format_colors(" ".join(data[0].split())),
|
"motd": format_colors(" ".join(data[0].split())),
|
||||||
"motd_raw": data[0],
|
"motd_raw": data[0],
|
||||||
|
@ -199,17 +199,17 @@ def parse_input(inp):
|
||||||
def format_colors(motd):
|
def format_colors(motd):
|
||||||
for original, replacement in mc_colors:
|
for original, replacement in mc_colors:
|
||||||
motd = motd.replace(original, replacement)
|
motd = motd.replace(original, replacement)
|
||||||
motd = motd.replace("\xa7k", "")
|
motd = motd.replace(u"\xa7k", "")
|
||||||
return motd
|
return motd
|
||||||
|
|
||||||
|
|
||||||
def format_output(data):
|
def format_output(data):
|
||||||
if data["version"]:
|
if data["version"]:
|
||||||
return "{motd}\x0f - {version}\x0f - {players}/{players_max}" \
|
return u"{motd}\x0f - {version}\x0f - {players}/{players_max}" \
|
||||||
" players.".format(**data).replace("\n", "\x0f - ")
|
u" players.".format(**data).replace("\n", u"\x0f - ")
|
||||||
else:
|
else:
|
||||||
return "{motd}\x0f - {players}/{players_max}" \
|
return u"{motd}\x0f - {players}/{players_max}" \
|
||||||
" players.".format(**data).replace("\n", "\x0f - ")
|
u" players.".format(**data).replace("\n", u"\x0f - ")
|
||||||
|
|
||||||
|
|
||||||
@hook.command
|
@hook.command
|
|
@ -21,7 +21,7 @@ def mcstatus(inp):
|
||||||
green = []
|
green = []
|
||||||
yellow = []
|
yellow = []
|
||||||
red = []
|
red = []
|
||||||
for server, status in list(data.items()):
|
for server, status in data.items():
|
||||||
if status == "green":
|
if status == "green":
|
||||||
green.append(server)
|
green.append(server)
|
||||||
elif status == "yellow":
|
elif status == "yellow":
|
|
@ -87,15 +87,15 @@ def mcuser(inp):
|
||||||
profile["lt"] = ", legacy" if profile["legacy"] else ""
|
profile["lt"] = ", legacy" if profile["legacy"] else ""
|
||||||
|
|
||||||
if profile["paid"]:
|
if profile["paid"]:
|
||||||
return "The account \x02{name}\x02 ({id}{lt}) exists. It is a \x02paid\x02" \
|
return u"The account \x02{name}\x02 ({id}{lt}) exists. It is a \x02paid\x02" \
|
||||||
" account.".format(**profile)
|
u" account.".format(**profile)
|
||||||
else:
|
else:
|
||||||
return "The account \x02{name}\x02 ({id}{lt}) exists. It \x034\x02is NOT\x02\x0f a paid" \
|
return u"The account \x02{name}\x02 ({id}{lt}) exists. It \x034\x02is NOT\x02\x0f a paid" \
|
||||||
" account.".format(**profile)
|
u" account.".format(**profile)
|
||||||
elif name_status == "free":
|
elif name_status == "free":
|
||||||
return "The account \x02{}\x02 does not exist.".format(user)
|
return u"The account \x02{}\x02 does not exist.".format(user)
|
||||||
elif name_status == "invalid":
|
elif name_status == "invalid":
|
||||||
return "The name \x02{}\x02 contains invalid characters.".format(user)
|
return u"The name \x02{}\x02 contains invalid characters.".format(user)
|
||||||
else:
|
else:
|
||||||
# if you see this, panic
|
# if you see this, panic
|
||||||
return "Unknown Error."
|
return "Unknown Error."
|
|
@ -45,7 +45,7 @@ def mcwiki(inp):
|
||||||
summary = " ".join(p.text_content().splitlines())
|
summary = " ".join(p.text_content().splitlines())
|
||||||
summary = re.sub("\[\d+\]", "", summary)
|
summary = re.sub("\[\d+\]", "", summary)
|
||||||
summary = text.truncate_str(summary, 200)
|
summary = text.truncate_str(summary, 200)
|
||||||
return "{} :: {}".format(summary, url)
|
return u"{} :: {}".format(summary, url)
|
||||||
|
|
||||||
# this shouldn't happen
|
# this shouldn't happen
|
||||||
return "Unknown Error."
|
return "Unknown Error."
|
183
disabled_stuff/mtg.py
Normal file
183
disabled_stuff/mtg.py
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
from util import hook, http
|
||||||
|
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def mtg(inp):
|
||||||
|
".mtg <name> -- Gets information about Magic the Gathering card <name>."
|
||||||
|
|
||||||
|
url = 'http://magiccards.info/query?v=card&s=cname'
|
||||||
|
h = http.get_html(url, q=inp)
|
||||||
|
|
||||||
|
name = h.find('body/table/tr/td/span/a')
|
||||||
|
if name is None:
|
||||||
|
return "No cards found :("
|
||||||
|
card = name.getparent().getparent().getparent()
|
||||||
|
|
||||||
|
type = card.find('td/p').text.replace('\n', '')
|
||||||
|
|
||||||
|
# this is ugly
|
||||||
|
text = http.html.tostring(card.xpath("//p[@class='ctext']/b")[0])
|
||||||
|
text = text.replace('<br>', '$')
|
||||||
|
text = http.html.fromstring(text).text_content()
|
||||||
|
text = re.sub(r'(\w+\s*)\$+(\s*\w+)', r'\1. \2', text)
|
||||||
|
text = text.replace('$', ' ')
|
||||||
|
text = re.sub(r'\(.*?\)', '', text) # strip parenthetical explanations
|
||||||
|
text = re.sub(r'\.(\S)', r'. \1', text) # fix spacing
|
||||||
|
|
||||||
|
printings = card.find('td/small').text_content()
|
||||||
|
printings = re.search(r'Editions:(.*)Languages:', printings).group(1)
|
||||||
|
printings = re.findall(r'\s*(.+?(?: \([^)]+\))*) \((.*?)\)',
|
||||||
|
' '.join(printings.split()))
|
||||||
|
|
||||||
|
printing_out = ', '.join('%s (%s)' % (set_abbrevs.get(x[0], x[0]),
|
||||||
|
rarity_abbrevs.get(x[1], x[1]))
|
||||||
|
for x in printings)
|
||||||
|
|
||||||
|
name.make_links_absolute(base_url=url)
|
||||||
|
link = name.attrib['href']
|
||||||
|
name = name.text_content().strip()
|
||||||
|
type = type.strip()
|
||||||
|
text = ' '.join(text.split())
|
||||||
|
|
||||||
|
return ' | '.join((name, type, text, printing_out, link))
|
||||||
|
|
||||||
|
|
||||||
|
set_abbrevs = {
|
||||||
|
'15th Anniversary': '15ANN',
|
||||||
|
'APAC Junior Series': 'AJS',
|
||||||
|
'Alara Reborn': 'ARB',
|
||||||
|
'Alliances': 'AI',
|
||||||
|
'Anthologies': 'AT',
|
||||||
|
'Antiquities': 'AQ',
|
||||||
|
'Apocalypse': 'AP',
|
||||||
|
'Arabian Nights': 'AN',
|
||||||
|
'Arena League': 'ARENA',
|
||||||
|
'Asia Pacific Land Program': 'APAC',
|
||||||
|
'Battle Royale': 'BR',
|
||||||
|
'Battle Royale Box Set': 'BRB',
|
||||||
|
'Beatdown': 'BTD',
|
||||||
|
'Beatdown Box Set': 'BTD',
|
||||||
|
'Betrayers of Kamigawa': 'BOK',
|
||||||
|
'Celebration Cards': 'UQC',
|
||||||
|
'Champions of Kamigawa': 'CHK',
|
||||||
|
'Champs': 'CP',
|
||||||
|
'Chronicles': 'CH',
|
||||||
|
'Classic Sixth Edition': '6E',
|
||||||
|
'Coldsnap': 'CS',
|
||||||
|
'Coldsnap Theme Decks': 'CSTD',
|
||||||
|
'Conflux': 'CFX',
|
||||||
|
'Core Set - Eighth Edition': '8E',
|
||||||
|
'Core Set - Ninth Edition': '9E',
|
||||||
|
'Darksteel': 'DS',
|
||||||
|
'Deckmasters': 'DM',
|
||||||
|
'Dissension': 'DI',
|
||||||
|
'Dragon Con': 'DRC',
|
||||||
|
'Duel Decks: Divine vs. Demonic': 'DVD',
|
||||||
|
'Duel Decks: Elves vs. Goblins': 'EVG',
|
||||||
|
'Duel Decks: Garruk vs. Liliana': 'GVL',
|
||||||
|
'Duel Decks: Jace vs. Chandra': 'JVC',
|
||||||
|
'Eighth Edition': '8ED',
|
||||||
|
'Eighth Edition Box Set': '8EB',
|
||||||
|
'European Land Program': 'EURO',
|
||||||
|
'Eventide': 'EVE',
|
||||||
|
'Exodus': 'EX',
|
||||||
|
'Fallen Empires': 'FE',
|
||||||
|
'Fifth Dawn': '5DN',
|
||||||
|
'Fifth Edition': '5E',
|
||||||
|
'Fourth Edition': '4E',
|
||||||
|
'Friday Night Magic': 'FNMP',
|
||||||
|
'From the Vault: Dragons': 'FVD',
|
||||||
|
'From the Vault: Exiled': 'FVE',
|
||||||
|
'Future Sight': 'FUT',
|
||||||
|
'Gateway': 'GRC',
|
||||||
|
'Grand Prix': 'GPX',
|
||||||
|
'Guildpact': 'GP',
|
||||||
|
'Guru': 'GURU',
|
||||||
|
'Happy Holidays': 'HHO',
|
||||||
|
'Homelands': 'HL',
|
||||||
|
'Ice Age': 'IA',
|
||||||
|
'Introductory Two-Player Set': 'ITP',
|
||||||
|
'Invasion': 'IN',
|
||||||
|
'Judge Gift Program': 'JR',
|
||||||
|
'Judgment': 'JU',
|
||||||
|
'Junior Series': 'JSR',
|
||||||
|
'Legend Membership': 'DCILM',
|
||||||
|
'Legends': 'LG',
|
||||||
|
'Legions': 'LE',
|
||||||
|
'Limited Edition (Alpha)': 'LEA',
|
||||||
|
'Limited Edition (Beta)': 'LEB',
|
||||||
|
'Limited Edition Alpha': 'LEA',
|
||||||
|
'Limited Edition Beta': 'LEB',
|
||||||
|
'Lorwyn': 'LW',
|
||||||
|
'MTGO Masters Edition': 'MED',
|
||||||
|
'MTGO Masters Edition II': 'ME2',
|
||||||
|
'MTGO Masters Edition III': 'ME3',
|
||||||
|
'Magic 2010': 'M10',
|
||||||
|
'Magic Game Day Cards': 'MGDC',
|
||||||
|
'Magic Player Rewards': 'MPRP',
|
||||||
|
'Magic Scholarship Series': 'MSS',
|
||||||
|
'Magic: The Gathering Launch Parties': 'MLP',
|
||||||
|
'Media Inserts': 'MBP',
|
||||||
|
'Mercadian Masques': 'MM',
|
||||||
|
'Mirage': 'MR',
|
||||||
|
'Mirrodin': 'MI',
|
||||||
|
'Morningtide': 'MT',
|
||||||
|
'Multiverse Gift Box Cards': 'MGBC',
|
||||||
|
'Nemesis': 'NE',
|
||||||
|
'Ninth Edition Box Set': '9EB',
|
||||||
|
'Odyssey': 'OD',
|
||||||
|
'Onslaught': 'ON',
|
||||||
|
'Planar Chaos': 'PC',
|
||||||
|
'Planechase': 'PCH',
|
||||||
|
'Planeshift': 'PS',
|
||||||
|
'Portal': 'PO',
|
||||||
|
'Portal Demogame': 'POT',
|
||||||
|
'Portal Second Age': 'PO2',
|
||||||
|
'Portal Three Kingdoms': 'P3K',
|
||||||
|
'Premium Deck Series: Slivers': 'PDS',
|
||||||
|
'Prerelease Events': 'PTC',
|
||||||
|
'Pro Tour': 'PRO',
|
||||||
|
'Prophecy': 'PR',
|
||||||
|
'Ravnica: City of Guilds': 'RAV',
|
||||||
|
'Release Events': 'REP',
|
||||||
|
'Revised Edition': 'RV',
|
||||||
|
'Saviors of Kamigawa': 'SOK',
|
||||||
|
'Scourge': 'SC',
|
||||||
|
'Seventh Edition': '7E',
|
||||||
|
'Shadowmoor': 'SHM',
|
||||||
|
'Shards of Alara': 'ALA',
|
||||||
|
'Starter': 'ST',
|
||||||
|
'Starter 1999': 'S99',
|
||||||
|
'Starter 2000 Box Set': 'ST2K',
|
||||||
|
'Stronghold': 'SH',
|
||||||
|
'Summer of Magic': 'SOM',
|
||||||
|
'Super Series': 'SUS',
|
||||||
|
'Tempest': 'TP',
|
||||||
|
'Tenth Edition': '10E',
|
||||||
|
'The Dark': 'DK',
|
||||||
|
'Time Spiral': 'TS',
|
||||||
|
'Time Spiral Timeshifted': 'TSTS',
|
||||||
|
'Torment': 'TR',
|
||||||
|
'Two-Headed Giant Tournament': 'THGT',
|
||||||
|
'Unglued': 'UG',
|
||||||
|
'Unhinged': 'UH',
|
||||||
|
'Unhinged Alternate Foils': 'UHAA',
|
||||||
|
'Unlimited Edition': 'UN',
|
||||||
|
"Urza's Destiny": 'UD',
|
||||||
|
"Urza's Legacy": 'UL',
|
||||||
|
"Urza's Saga": 'US',
|
||||||
|
'Visions': 'VI',
|
||||||
|
'Weatherlight': 'WL',
|
||||||
|
'Worlds': 'WRL',
|
||||||
|
'WotC Online Store': 'WOTC',
|
||||||
|
'Zendikar': 'ZEN'}
|
||||||
|
|
||||||
|
rarity_abbrevs = {
|
||||||
|
'Land': 'L',
|
||||||
|
'Common': 'C',
|
||||||
|
'Uncommon': 'UC',
|
||||||
|
'Rare': 'R',
|
||||||
|
'Special': 'S',
|
||||||
|
'Mythic Rare': 'MR'}
|
115
disabled_stuff/mygengo_translate.py
Normal file
115
disabled_stuff/mygengo_translate.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
# BING translation plugin by Lukeroge and neersighted
|
||||||
|
from util import hook
|
||||||
|
from util import http
|
||||||
|
import re
|
||||||
|
import htmlentitydefs
|
||||||
|
import mygengo
|
||||||
|
|
||||||
|
gengo = mygengo.MyGengo(
|
||||||
|
public_key='PlwtF1CZ2tu27IdX_SXNxTFmfN0j|_-pJ^Rf({O-oLl--r^QM4FygRdt^jusSSDE',
|
||||||
|
private_key='wlXpL=SU[#JpPu[dQaf$v{S3@rg[=95$$TA(k$sb3_6~B_zDKkTbd4#hXxaorIae',
|
||||||
|
sandbox=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def gengo_translate(text, source, target):
|
||||||
|
try:
|
||||||
|
translation = gengo.postTranslationJob(job={
|
||||||
|
'type': 'text',
|
||||||
|
'slug': 'Translating '+source+' to '+target+' with the myGengo API',
|
||||||
|
'body_src': text,
|
||||||
|
'lc_src': source,
|
||||||
|
'lc_tgt': target,
|
||||||
|
'tier': 'machine',
|
||||||
|
})
|
||||||
|
translated = translation['response']['job']['body_tgt']
|
||||||
|
return u"(%s > %s) %s" % (source, target, translated)
|
||||||
|
except mygengo.MyGengoError:
|
||||||
|
return "error: could not translate"
|
||||||
|
|
||||||
|
def match_language(fragment):
|
||||||
|
fragment = fragment.lower()
|
||||||
|
for short, _ in lang_pairs:
|
||||||
|
if fragment in short.lower().split():
|
||||||
|
return short.split()[0]
|
||||||
|
|
||||||
|
for short, full in lang_pairs:
|
||||||
|
if fragment in full.lower():
|
||||||
|
return short.split()[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
@hook.command
|
||||||
|
def translate(inp):
|
||||||
|
".translate <source language> <target language> <sentence> -- Translates <sentence> from <source language> to <target language> using MyGengo."
|
||||||
|
args = inp.split(' ')
|
||||||
|
sl = match_language(args[0])
|
||||||
|
tl = match_language(args[1])
|
||||||
|
txt = unicode(" ".join(args[2:]))
|
||||||
|
if sl and tl:
|
||||||
|
return unicode(gengo_translate(txt, sl, tl))
|
||||||
|
else:
|
||||||
|
return "error: translate could not reliably determine one or both languages"
|
||||||
|
|
||||||
|
languages = 'ja fr de ko ru zh'.split()
|
||||||
|
language_pairs = zip(languages[:-1], languages[1:])
|
||||||
|
lang_pairs = [
|
||||||
|
("no", "Norwegian"),
|
||||||
|
("it", "Italian"),
|
||||||
|
("ht", "Haitian Creole"),
|
||||||
|
("af", "Afrikaans"),
|
||||||
|
("sq", "Albanian"),
|
||||||
|
("ar", "Arabic"),
|
||||||
|
("hy", "Armenian"),
|
||||||
|
("az", "Azerbaijani"),
|
||||||
|
("eu", "Basque"),
|
||||||
|
("be", "Belarusian"),
|
||||||
|
("bg", "Bulgarian"),
|
||||||
|
("ca", "Catalan"),
|
||||||
|
("zh-CN zh", "Chinese"),
|
||||||
|
("hr", "Croatian"),
|
||||||
|
("cs cz", "Czech"),
|
||||||
|
("da dk", "Danish"),
|
||||||
|
("nl", "Dutch"),
|
||||||
|
("en", "English"),
|
||||||
|
("et", "Estonian"),
|
||||||
|
("tl", "Filipino"),
|
||||||
|
("fi", "Finnish"),
|
||||||
|
("fr", "French"),
|
||||||
|
("gl", "Galician"),
|
||||||
|
("ka", "Georgian"),
|
||||||
|
("de", "German"),
|
||||||
|
("el", "Greek"),
|
||||||
|
("ht", "Haitian Creole"),
|
||||||
|
("iw", "Hebrew"),
|
||||||
|
("hi", "Hindi"),
|
||||||
|
("hu", "Hungarian"),
|
||||||
|
("is", "Icelandic"),
|
||||||
|
("id", "Indonesian"),
|
||||||
|
("ga", "Irish"),
|
||||||
|
("it", "Italian"),
|
||||||
|
("ja jp jpn", "Japanese"),
|
||||||
|
("ko", "Korean"),
|
||||||
|
("lv", "Latvian"),
|
||||||
|
("lt", "Lithuanian"),
|
||||||
|
("mk", "Macedonian"),
|
||||||
|
("ms", "Malay"),
|
||||||
|
("mt", "Maltese"),
|
||||||
|
("no", "Norwegian"),
|
||||||
|
("fa", "Persian"),
|
||||||
|
("pl", "Polish"),
|
||||||
|
("pt", "Portuguese"),
|
||||||
|
("ro", "Romanian"),
|
||||||
|
("ru", "Russian"),
|
||||||
|
("sr", "Serbian"),
|
||||||
|
("sk", "Slovak"),
|
||||||
|
("sl", "Slovenian"),
|
||||||
|
("es", "Spanish"),
|
||||||
|
("sw", "Swahili"),
|
||||||
|
("sv", "Swedish"),
|
||||||
|
("th", "Thai"),
|
||||||
|
("tr", "Turkish"),
|
||||||
|
("uk", "Ukrainian"),
|
||||||
|
("ur", "Urdu"),
|
||||||
|
("vi", "Vietnamese"),
|
||||||
|
("cy", "Welsh"),
|
||||||
|
("yi", "Yiddish")
|
||||||
|
]
|
|
@ -14,12 +14,12 @@ def get_generator(_json):
|
||||||
|
|
||||||
|
|
||||||
@hook.command(autohelp=False)
|
@hook.command(autohelp=False)
|
||||||
def namegen(input, instance, bot):
|
def namegen(inp, notice=None):
|
||||||
"""namegen [generator] -- Generates some names using the chosen generator.
|
"""namegen [generator] -- Generates some names using the chosen generator.
|
||||||
'namegen list' will display a list of all generators."""
|
'namegen list' will display a list of all generators."""
|
||||||
|
|
||||||
# clean up the input
|
# clean up the input
|
||||||
inp = input.text.strip().lower()
|
inp = inp.strip().lower()
|
||||||
|
|
||||||
# get a list of available name generators
|
# get a list of available name generators
|
||||||
files = os.listdir(GEN_DIR)
|
files = os.listdir(GEN_DIR)
|
||||||
|
@ -33,7 +33,7 @@ def namegen(input, instance, bot):
|
||||||
if inp == "list":
|
if inp == "list":
|
||||||
message = "Available generators: "
|
message = "Available generators: "
|
||||||
message += text.get_text_list(all_modules, 'and')
|
message += text.get_text_list(all_modules, 'and')
|
||||||
input.notice(message)
|
notice(message)
|
||||||
return
|
return
|
||||||
|
|
||||||
if inp:
|
if inp:
|
|
@ -46,18 +46,18 @@ def format_item(item, show_url=True):
|
||||||
tags.append("\x02Featured\x02")
|
tags.append("\x02Featured\x02")
|
||||||
|
|
||||||
if item["IsShellShockerItem"]:
|
if item["IsShellShockerItem"]:
|
||||||
tags.append("\x02SHELL SHOCKER\u00AE\x02")
|
tags.append(u"\x02SHELL SHOCKER\u00AE\x02")
|
||||||
|
|
||||||
# join all the tags together in a comma separated string ("tag1, tag2, tag3")
|
# join all the tags together in a comma separated string ("tag1, tag2, tag3")
|
||||||
tag_text = ", ".join(tags)
|
tag_text = u", ".join(tags)
|
||||||
|
|
||||||
if show_url:
|
if show_url:
|
||||||
# create the item URL and shorten it
|
# create the item URL and shorten it
|
||||||
url = web.try_isgd(ITEM_URL.format(item["NeweggItemNumber"]))
|
url = web.try_isgd(ITEM_URL.format(item["NeweggItemNumber"]))
|
||||||
return "\x02{}\x02 ({}) - {} - {} - {}".format(title, price, rating,
|
return u"\x02{}\x02 ({}) - {} - {} - {}".format(title, price, rating,
|
||||||
tag_text, url)
|
tag_text, url)
|
||||||
else:
|
else:
|
||||||
return "\x02{}\x02 ({}) - {} - {}".format(title, price, rating,
|
return u"\x02{}\x02 ({}) - {} - {}".format(title, price, rating,
|
||||||
tag_text)
|
tag_text)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ def test(s):
|
||||||
def newgrounds_url(match):
|
def newgrounds_url(match):
|
||||||
location = match.group(4).split("/")[-1]
|
location = match.group(4).split("/")[-1]
|
||||||
if not test(location):
|
if not test(location):
|
||||||
print("Not a valid Newgrounds portal ID. Example: http://www.newgrounds.com/portal/view/593993")
|
print "Not a valid Newgrounds portal ID. Example: http://www.newgrounds.com/portal/view/593993"
|
||||||
return None
|
return None
|
||||||
soup = http.get_soup("http://www.newgrounds.com/portal/view/" + location)
|
soup = http.get_soup("http://www.newgrounds.com/portal/view/" + location)
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ def newgrounds_url(match):
|
||||||
# get rating
|
# get rating
|
||||||
try:
|
try:
|
||||||
rating_info = soup.find('dd', {'class': 'star-variable'})['title'].split("Stars –")[0].strip()
|
rating_info = soup.find('dd', {'class': 'star-variable'})['title'].split("Stars –")[0].strip()
|
||||||
rating = " - rated \x02{}\x02/\x025.0\x02".format(rating_info)
|
rating = u" - rated \x02{}\x02/\x025.0\x02".format(rating_info)
|
||||||
except:
|
except:
|
||||||
rating = ""
|
rating = ""
|
||||||
|
|
|
@ -29,7 +29,7 @@ def password(inp, notice=None):
|
||||||
|
|
||||||
# add numbers
|
# add numbers
|
||||||
if "numeric" in inp or "number" in inp:
|
if "numeric" in inp or "number" in inp:
|
||||||
okay = okay + [str(x) for x in range(0, 10)]
|
okay = okay + [str(x) for x in xrange(0, 10)]
|
||||||
|
|
||||||
# add symbols
|
# add symbols
|
||||||
if "symbol" in inp:
|
if "symbol" in inp:
|
|
@ -36,5 +36,3 @@ def pre(inp):
|
||||||
size = ''
|
size = ''
|
||||||
|
|
||||||
return '{} - {}{} - {} ({} ago)'.format(section, name, size, date_string, since)
|
return '{} - {}{} - {} ({} ago)'.format(section, name, size, date_string, since)
|
||||||
|
|
||||||
print(pre("top gear"))
|
|
|
@ -1,4 +1,4 @@
|
||||||
import urllib.request, urllib.parse, urllib.error
|
import urllib
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ def getdata(inp, types, api_key, api_secret):
|
||||||
consumer = oauth.Consumer(api_key, api_secret)
|
consumer = oauth.Consumer(api_key, api_secret)
|
||||||
client = oauth.Client(consumer)
|
client = oauth.Client(consumer)
|
||||||
response = client.request('http://api.rdio.com/1/', 'POST',
|
response = client.request('http://api.rdio.com/1/', 'POST',
|
||||||
urllib.parse.urlencode({'method': 'search', 'query': inp, 'types': types, 'count': '1'}))
|
urllib.urlencode({'method': 'search', 'query': inp, 'types': types, 'count': '1'}))
|
||||||
data = json.loads(response[1])
|
data = json.loads(response[1])
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -34,16 +34,16 @@ def rdio(inp, bot=None):
|
||||||
artist = info['artist']
|
artist = info['artist']
|
||||||
album = info['album']
|
album = info['album']
|
||||||
url = info['shortUrl']
|
url = info['shortUrl']
|
||||||
return "\x02{}\x02 by \x02{}\x02 - {} {}".format(name, artist, album, url)
|
return u"\x02{}\x02 by \x02{}\x02 - {} {}".format(name, artist, album, url)
|
||||||
elif 'artist' in info and not 'album' in info: # Album
|
elif 'artist' in info and not 'album' in info: # Album
|
||||||
name = info['name']
|
name = info['name']
|
||||||
artist = info['artist']
|
artist = info['artist']
|
||||||
url = info['shortUrl']
|
url = info['shortUrl']
|
||||||
return "\x02{}\x02 by \x02{}\x02 - {}".format(name, artist, url)
|
return u"\x02{}\x02 by \x02{}\x02 - {}".format(name, artist, url)
|
||||||
else: # Artist
|
else: # Artist
|
||||||
name = info['name']
|
name = info['name']
|
||||||
url = info['shortUrl']
|
url = info['shortUrl']
|
||||||
return "\x02{}\x02 - {}".format(name, url)
|
return u"\x02{}\x02 - {}".format(name, url)
|
||||||
|
|
||||||
|
|
||||||
@hook.command
|
@hook.command
|
||||||
|
@ -62,7 +62,7 @@ def rdiot(inp, bot=None):
|
||||||
artist = info['artist']
|
artist = info['artist']
|
||||||
album = info['album']
|
album = info['album']
|
||||||
url = info['shortUrl']
|
url = info['shortUrl']
|
||||||
return "\x02{}\x02 by \x02{}\x02 - {} - {}".format(name, artist, album, url)
|
return u"\x02{}\x02 by \x02{}\x02 - {} - {}".format(name, artist, album, url)
|
||||||
|
|
||||||
|
|
||||||
@hook.command
|
@hook.command
|
||||||
|
@ -79,7 +79,7 @@ def rdioar(inp, bot=None):
|
||||||
return "No results."
|
return "No results."
|
||||||
name = info['name']
|
name = info['name']
|
||||||
url = info['shortUrl']
|
url = info['shortUrl']
|
||||||
return "\x02{}\x02 - {}".format(name, url)
|
return u"\x02{}\x02 - {}".format(name, url)
|
||||||
|
|
||||||
|
|
||||||
@hook.command
|
@hook.command
|
||||||
|
@ -97,7 +97,7 @@ def rdioal(inp, bot=None):
|
||||||
name = info['name']
|
name = info['name']
|
||||||
artist = info['artist']
|
artist = info['artist']
|
||||||
url = info['shortUrl']
|
url = info['shortUrl']
|
||||||
return "\x02{}\x02 by \x02{}\x02 - {}".format(name, artist, url)
|
return u"\x02{}\x02 by \x02{}\x02 - {}".format(name, artist, url)
|
||||||
|
|
||||||
|
|
||||||
rdio_re = (r'(.*:)//(rd.io|www.rdio.com|rdio.com)(:[0-9]+)?(.*)', re.I)
|
rdio_re = (r'(.*:)//(rd.io|www.rdio.com|rdio.com)(:[0-9]+)?(.*)', re.I)
|
||||||
|
@ -113,7 +113,7 @@ def rdio_url(match, bot=None):
|
||||||
consumer = oauth.Consumer(api_key, api_secret)
|
consumer = oauth.Consumer(api_key, api_secret)
|
||||||
client = oauth.Client(consumer)
|
client = oauth.Client(consumer)
|
||||||
response = client.request('http://api.rdio.com/1/', 'POST',
|
response = client.request('http://api.rdio.com/1/', 'POST',
|
||||||
urllib.parse.urlencode({'method': 'getObjectFromUrl', 'url': url}))
|
urllib.urlencode({'method': 'getObjectFromUrl', 'url': url}))
|
||||||
data = json.loads(response[1])
|
data = json.loads(response[1])
|
||||||
info = data['result']
|
info = data['result']
|
||||||
if 'name' in info:
|
if 'name' in info:
|
||||||
|
@ -121,11 +121,11 @@ def rdio_url(match, bot=None):
|
||||||
name = info['name']
|
name = info['name']
|
||||||
artist = info['artist']
|
artist = info['artist']
|
||||||
album = info['album']
|
album = info['album']
|
||||||
return "Rdio track: \x02{}\x02 by \x02{}\x02 - {}".format(name, artist, album)
|
return u"Rdio track: \x02{}\x02 by \x02{}\x02 - {}".format(name, artist, album)
|
||||||
elif 'artist' in info and not 'album' in info: # Album
|
elif 'artist' in info and not 'album' in info: # Album
|
||||||
name = info['name']
|
name = info['name']
|
||||||
artist = info['artist']
|
artist = info['artist']
|
||||||
return "Rdio album: \x02{}\x02 by \x02{}\x02".format(name, artist)
|
return u"Rdio album: \x02{}\x02 by \x02{}\x02".format(name, artist)
|
||||||
else: # Artist
|
else: # Artist
|
||||||
name = info['name']
|
name = info['name']
|
||||||
return "Rdio artist: \x02{}\x02".format(name)
|
return u"Rdio artist: \x02{}\x02".format(name)
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue