Compare commits

..

No commits in common. "master" and "flask_and_peewee" have entirely different histories.

8 changed files with 99 additions and 178 deletions

3
.gitignore vendored
View file

@ -1,4 +1 @@
*.pyc
irc.db
seen_prs

15
Db.py
View file

@ -23,16 +23,11 @@ class LogMessage(BaseModel):
message_type = CharField()
message = CharField()
class Quote(BaseModel):
datetime = DateTimeField()
author = CharField()
message = CharField()
def create_tables():
database.connect()
try:
database.create_tables([Quote, LogMessage, Day, Channel])
database.create_tables([LogMessage, Day, Channel])
except OperationalError:
pass # Database already exists
@ -60,12 +55,6 @@ def add_log_message(channel, nickname, message_type, message = None):
message_type = message_type,
message = message)
def add_quote(author, message):
Quote.create(
author = author,
message = message,
datetime = datetime.datetime.now().strftime("%H:%m:%S"))
def show_all_messages():
for message in LogMessage.select():
print("<%s> %s" % (message.nickname, message.message))
print "<%s> %s" % (message.nickname, message.message)

View file

@ -1,32 +1,21 @@
# smooth-operator
LogBot 0.4.2
============
smooth-operator is a IRC bot which logs everything in a channel and offers more convinient things like notifications about new commits on GitHub, etc. For a roadmap please check the issues on GitHub.
Written by Chris Oliver <chris@excid3.com>
Originally this was written by Chris Oliver <chris@excid3.com> with contributions from Filip Slagter. Now it has diverged quite a lot.
Many thanks to Filip Slagter for his contributions.
## Requirements
smooth-operator shows logs using flask, and stores logs using peewee. Install these dependencies using ``pip``:
Requirements
------------
LogBot shows logs using flask, and stores logs using peewee. Install these dependencies using ``pip``:
pip install flask peewee
## Usage
Usage
-----
LogBot requires Python 2. It is NOT compatible with Python 3.
Configuration is done inside logbot.py.
smooth-operator requires Python 2. It is NOT compatible with Python 3. Configuration is either done inside logbot.py, or using environment variables. The following environment variables are respected:
python logbot.py
- ``IRC_SERVER``: IRC server
- ``IRC_PORT``: IRC server port
- ``IRC_SERVER_PASS``: Password for IRC server, if any
- ``IRC_CHANNELS``: IRC channels to join, separated by ``,``
- ``IRC_NICK``: Nickname
- ``IRC_NICK_PASS``: Password to use when authenticating to nickserv, if any
The bot can be launched using:
python2 logbot.py
You can view the logs on http://localhost:5000
## License
This project is licensed under the GPLv2.
You can view logs on http://localhost:5000

View file

@ -1,44 +0,0 @@
from Db import *
from irclib import nm_to_n
import random
class Commands:
def __init__(self):
self.commands = {
"quote": self.cmd_quote,
"remember_quote": self.cmd_remember_quote
}
def process(self, c, e):
msg = e.arguments()[0]
if msg.startswith("!"):
cmd = msg.split("!")[1].split(" ")[0]
if cmd in self.commands:
msg = " ".join(msg.split(" ")[1:])
self.commands[cmd](c, msg, e.target(), nm_to_n(e.source()))
else:
print "Unknown command", cmd
def cmd_quote(self, c, msg, target, source):
replies = [
"%s once said \"%s\"",
"I head from %s that \"%s\"",
"A wise man (haha, just kidding, it was actually %s) once said \"%s\""
]
reply = lambda msg: c.privmsg(target, msg)
random_query = Quote.select().order_by(fn.Random())
try:
one_quote = random_query.get()
reply(random.choice(replies) % (one_quote.author, one_quote.message))
except: # No quotes
reply("I don't know ay quotes :(")
def cmd_remember_quote(self, c, msg, target, source):
reply = lambda msg: c.privmsg(target, msg)
if (len(msg) > 1):
add_quote(msg.split(" ")[0], ' '.join(msg.split(" ")[1:]))
reply("I'll try to remember that!")
else:
reply("I didn't get that :(")

View file

@ -25,14 +25,7 @@ write simpler bots.
"""
import sys
# UserDict is moved to collections in Python3
# In order to support Python 2.7 this has to be imported
# in the following way
try:
from UserDict import UserDict
except ImportError:
from collections import UserDict
from UserDict import UserDict
from irclib import SimpleIRCClient
from irclib import nm_to_n, irc_lower, all_events
@ -167,7 +160,7 @@ class SingleServerIRCBot(SimpleIRCClient):
"""[Internal]"""
before = nm_to_n(e.source())
after = e.target()
for ch in list(self.channels.values()):
for ch in self.channels.values():
if ch.has_user(before):
ch.change_nick(before, after)
@ -184,7 +177,7 @@ class SingleServerIRCBot(SimpleIRCClient):
def _on_quit(self, c, e):
"""[Internal]"""
nick = nm_to_n(e.source())
for ch in list(self.channels.values()):
for ch in self.channels.values():
if ch.has_user(nick):
ch.remove_user(nick)
@ -290,6 +283,8 @@ class IRCDict:
del self.canon_keys[ck]
def __iter__(self):
return iter(self.data)
def __contains__(self, key):
return self.has_key(key)
def clear(self):
self.data.clear()
self.canon_keys.clear()
@ -299,15 +294,15 @@ class IRCDict:
import copy
return copy.copy(self)
def keys(self):
return list(self.data.keys())
return self.data.keys()
def items(self):
return list(self.data.items())
return self.data.items()
def values(self):
return list(self.data.values())
return self.data.values()
def has_key(self, key):
return irc_lower(key) in self.canon_keys
def update(self, dict):
for k, v in list(dict.items()):
for k, v in dict.items():
self.data[k] = v
def get(self, key, failobj=None):
return self.data.get(key, failobj)
@ -327,16 +322,16 @@ class Channel:
def users(self):
"""Returns an unsorted list of the channel's users."""
return list(self.userdict.keys())
return self.userdict.keys()
def opers(self):
"""Returns an unsorted list of the channel's operators."""
return list(self.operdict.keys())
return self.operdict.keys()
def voiced(self):
"""Returns an unsorted list of the persons that have voice
mode set in the channel."""
return list(self.voiceddict.keys())
return self.voiceddict.keys()
def has_user(self, nick):
"""Check whether the channel has a user."""

View file

@ -207,8 +207,8 @@ class IRC:
incoming data, if there are any. If that seems boring, look
at the process_forever method.
"""
sockets = [x._get_socket() for x in self.connections]
sockets = [x for x in sockets if x != None]
sockets = map(lambda x: x._get_socket(), self.connections)
sockets = filter(lambda x: x != None, sockets)
if sockets:
(i, o, e) = select.select(sockets, [], [], timeout)
self.process_data(i)
@ -342,7 +342,7 @@ class Connection:
self.irclibobj = irclibobj
def _get_socket():
raise IRCError("Not overridden")
raise IRCError, "Not overridden"
##############################
### Convenience wrappers.
@ -433,10 +433,10 @@ class ServerConnection(Connection):
self.socket.connect((self.server, self.port))
if ssl:
self.ssl = socket.ssl(self.socket)
except socket.error as x:
except socket.error, x:
self.socket.close()
self.socket = None
raise ServerConnectionError("Couldn't connect to socket: %s" % x)
raise ServerConnectionError, "Couldn't connect to socket: %s" % x
self.connected = 1
if self.irclibobj.fn_to_add_socket:
self.irclibobj.fn_to_add_socket(self.socket)
@ -491,7 +491,7 @@ class ServerConnection(Connection):
new_data = self.ssl.read(2**14)
else:
new_data = self.socket.recv(2**14)
except socket.error as x:
except socket.error, x:
# The server hung up.
self.disconnect("Connection reset by peer")
return
@ -500,14 +500,14 @@ class ServerConnection(Connection):
self.disconnect("Connection reset by peer")
return
lines = _linesep_regexp.split(self.previous_buffer + new_data.decode())
lines = _linesep_regexp.split(self.previous_buffer + new_data)
# Save the last, unfinished line.
self.previous_buffer = lines.pop()
for line in lines:
if DEBUG:
print("FROM SERVER:", line)
print "FROM SERVER:", line
if not line:
continue
@ -561,7 +561,7 @@ class ServerConnection(Connection):
command = "privnotice"
for m in messages:
if type(m) is tuple:
if type(m) is types.TupleType:
if command in ["privmsg", "pubmsg"]:
command = "ctcp"
else:
@ -569,15 +569,15 @@ class ServerConnection(Connection):
m = list(m)
if DEBUG:
print("command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, m))
print "command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, m)
self._handle_event(Event(command, prefix, target, m))
if command == "ctcp" and m[0] == "ACTION":
self._handle_event(Event("action", prefix, target, m[1:]))
else:
if DEBUG:
print("command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, [m]))
print "command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, [m])
self._handle_event(Event(command, prefix, target, [m]))
else:
target = None
@ -595,8 +595,8 @@ class ServerConnection(Connection):
command = "umode"
if DEBUG:
print("command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, arguments))
print "command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, arguments)
self._handle_event(Event(command, prefix, target, arguments))
def _handle_event(self, event):
@ -660,7 +660,7 @@ class ServerConnection(Connection):
try:
self.socket.close()
except socket.error as x:
except socket.error, x:
pass
self.socket = None
self._handle_event(Event("disconnect", self.server, "", [message]))
@ -743,7 +743,7 @@ class ServerConnection(Connection):
def part(self, channels, message=""):
"""Send a PART command."""
if type(channels) == bytes:
if type(channels) == types.StringType:
self.send_raw("PART " + channels + (message and (" " + message)))
else:
self.send_raw("PART " + ",".join(channels) + (message and (" " + message)))
@ -782,16 +782,15 @@ class ServerConnection(Connection):
The string will be padded with appropriate CR LF.
"""
if self.socket is None:
raise ServerNotConnectedError("Not connected.")
raise ServerNotConnectedError, "Not connected."
try:
string += "\r\n"
if self.ssl:
self.ssl.write(string.encode())
self.ssl.write(string + "\r\n")
else:
self.socket.send(string.encode())
self.socket.send(string + "\r\n")
if DEBUG:
print("TO SERVER:", string)
except socket.error as x:
print "TO SERVER:", string
except socket.error, x:
# Ouch!
self.disconnect("Connection reset by peer.")
@ -889,8 +888,8 @@ class DCCConnection(Connection):
self.passive = 0
try:
self.socket.connect((self.peeraddress, self.peerport))
except socket.error as x:
raise DCCConnectionError("Couldn't connect to socket: %s" % x)
except socket.error, x:
raise DCCConnectionError, "Couldn't connect to socket: %s" % x
self.connected = 1
if self.irclibobj.fn_to_add_socket:
self.irclibobj.fn_to_add_socket(self.socket)
@ -914,8 +913,8 @@ class DCCConnection(Connection):
self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
self.localaddress, self.localport = self.socket.getsockname()
self.socket.listen(10)
except socket.error as x:
raise DCCConnectionError("Couldn't bind socket: %s" % x)
except socket.error, x:
raise DCCConnectionError, "Couldn't bind socket: %s" % x
return self
def disconnect(self, message=""):
@ -931,7 +930,7 @@ class DCCConnection(Connection):
self.connected = 0
try:
self.socket.close()
except socket.error as x:
except socket.error, x:
pass
self.socket = None
self.irclibobj._handle_event(
@ -948,8 +947,8 @@ class DCCConnection(Connection):
self.socket = conn
self.connected = 1
if DEBUG:
print("DCC connection from %s:%d" % (
self.peeraddress, self.peerport))
print "DCC connection from %s:%d" % (
self.peeraddress, self.peerport)
self.irclibobj._handle_event(
self,
Event("dcc_connect", self.peeraddress, None, None))
@ -957,7 +956,7 @@ class DCCConnection(Connection):
try:
new_data = self.socket.recv(2**14)
except socket.error as x:
except socket.error, x:
# The server hung up.
self.disconnect("Connection reset by peer")
return
@ -986,11 +985,11 @@ class DCCConnection(Connection):
target = None
for chunk in chunks:
if DEBUG:
print("FROM PEER:", chunk)
print "FROM PEER:", chunk
arguments = [chunk]
if DEBUG:
print("command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, arguments))
print "command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, arguments)
self.irclibobj._handle_event(
self,
Event(command, prefix, target, arguments))
@ -1010,8 +1009,8 @@ class DCCConnection(Connection):
if self.dcctype == "chat":
self.socket.send("\n")
if DEBUG:
print("TO PEER: %s\n" % string)
except socket.error as x:
print "TO PEER: %s\n" % string
except socket.error, x:
# Ouch!
self.disconnect("Connection reset by peer.")
@ -1182,6 +1181,10 @@ def mask_matches(nick, mask):
r = re.compile(mask, re.IGNORECASE)
return r.match(nick)
_special = "-[]\\`^{}"
nick_characters = string.ascii_letters + string.digits + _special
_ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^",
string.ascii_lowercase + "{}|~")
def irc_lower(s):
"""Returns a lowercased string.
@ -1189,12 +1192,7 @@ def irc_lower(s):
The definition of lowercased comes from the IRC specification (RFC
1459).
"""
s = s.lower()
s = s.replace("[", "{")
s = s.replace("]", "}")
s = s.replace("\\", "|")
s = s.replace("^", "~")
return s
return s.translate(_ircstring_translation)
def _ctcp_dequote(message):
"""[Internal] Dequote a message according to CTCP specifications.
@ -1261,16 +1259,16 @@ def ip_numstr_to_quad(num):
"""Convert an IP number as an integer given in ASCII
representation (e.g. '3232235521') to an IP address string
(e.g. '192.168.0.1')."""
n = int(num)
p = list(map(str, list(map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
n >> 8 & 0xFF, n & 0xFF]))))
n = long(num)
p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
n >> 8 & 0xFF, n & 0xFF]))
return ".".join(p)
def ip_quad_to_numstr(quad):
"""Convert an IP address string (e.g. '192.168.0.1') to an IP
number as an integer given in ASCII representation
(e.g. '3232235521')."""
p = list(map(int, quad.split(".")))
p = map(long, quad.split("."))
s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3])
if s[-1] == "L":
s = s[:-1]
@ -1559,4 +1557,4 @@ protocol_events = [
"pong",
]
all_events = generated_events + protocol_events + list(numeric_events.values())
all_events = generated_events + protocol_events + numeric_events.values()

View file

@ -59,7 +59,6 @@ from pullrequest import PullRequest
from Db import *
from flask import *
import threading
from commands import Commands
pat1 = re.compile(r"(^|[\n ])(([\w]+?://[\w\#$%&~.\-;:=,?@\[\]+]*)(/[\w\#$%&~/.\-;:=,?@\[\]+]*)?)", re.IGNORECASE | re.DOTALL)
@ -80,12 +79,12 @@ def urlify2(value):
DEBUG = False
# IRC Server Configuration
SERVER = os.getenv("IRC_SERVER", "irc.freenode.net")
PORT = os.getenv("IRC_PORT", 6667)
SERVER_PASS = os.getenv("IRC_SERVER_PASS", None)
CHANNELS = os.getenv("IRC_CHANNELS", "#pelux").split(",")
NICK = os.getenv("IRC_NICK", "pelux")
NICK_PASS = os.getenv("IRC_NICK_PASS", "")
SERVER = "irc.freenode.net"
PORT = 6667
SERVER_PASS = None
CHANNELS=["#pelux"]
NICK = "pelux"
NICK_PASS = ""
# The local folder to save logs
LOG_FOLDER = "/var/www/html/"
@ -116,13 +115,13 @@ def search(channel = None, nickname = None):
try:
channel = Channel.get(Channel.name == channel)
messages = LogMessage.select() \
.where(LogMessage.channel == channel, \
.where(LogMessage.channel == channel and \
LogMessage.message.contains(query))
except:
pass # No such channel
elif nickname:
messages = LogMessage.select() \
.where(LogMessage.nickname == nickname, \
.where(LogMessage.nickname == nickname and \
LogMessage.message.contains(query))
else:
messages = LogMessage.select() \
@ -136,13 +135,13 @@ def search(channel = None, nickname = None):
@flaskapp.route("/channels/<channel>/")
@flaskapp.route("/channels/<channel>/<day>/")
def channel(channel, day = None):
def channel(channel, day = None, query = None):
channel = Channel.get(Channel.name == channel)
if day:
d = Day.get(Day.date == day)
messages = LogMessage.select() \
.where(LogMessage.day == d, \
.where(LogMessage.day == d and \
LogMessage.channel == channel)
return render_template("messages.html",
@ -165,17 +164,18 @@ def channels():
def append_line(filename, line):
data = open(filename, "rb").readlines()[:-2]
data += [line.encode(), "\n".encode(), "\n</body>".encode(), "\n</html>".encode()]
data += [line, "\n", "\n</body>", "\n</html>"]
write_lines(filename, data)
def write_lines(filename, lines):
with open(filename, "wb") as f:
f.writelines(lines)
f = open(filename, "wb")
f.writelines(lines)
f.close()
def write_string(filename, string):
with open(filename, "wb") as f:
f.write(string.encode())
f = open(filename, "wb")
f.write(string)
f.close()
color_pattern = re.compile(r'(\[\d{1,2}m)')
"Pattern that matches ANSI color codes and the text that follows"
@ -204,17 +204,16 @@ class Logbot(SingleServerIRCBot):
self.chans = [x.lower() for x in channels]
self.set_ftp()
self.nick_pass = nick_pass
self.commands = Commands()
print("Logbot %s" % __version__)
print("Connecting to %s:%i..." % (server, port))
print("Press Ctrl-C to quit")
print "Logbot %s" % __version__
print "Connecting to %s:%i..." % (server, port)
print "Press Ctrl-C to quit"
def quit(self):
self.connection.disconnect("Quitting...")
def color(self, user):
return "#%s" % md5(user.encode()).hexdigest()[:6]
return "#%s" % md5(user).hexdigest()[:6]
def set_ftp(self, ftp=None):
self.ftp = ftp
@ -224,7 +223,6 @@ class Logbot(SingleServerIRCBot):
if event_name == "nick":
message = params["new"]
target = params["chan"]
elif event_name == "kick":
message = "%s kicked %s from %s. Reason: %s" % (nm_to_n(params["kicker"]),
params["user"], params["channel"], params["reason"])
@ -259,7 +257,7 @@ class Logbot(SingleServerIRCBot):
def on_all_raw_messages(self, c, e):
"""Display all IRC connections in terminal"""
if DEBUG: print(e.arguments()[0])
if DEBUG: print e.arguments()[0]
def on_welcome(self, c, e):
"""Join channels after successful connection"""
@ -322,7 +320,6 @@ class Logbot(SingleServerIRCBot):
def on_pubmsg(self, c, e):
# if e.arguments()[0].startswith(NICK):
# c.privmsg(e.target(), self.format["help"])
self.commands.process(c, e)
self.write_event("pubmsg", e)
def on_pubnotice(self, c, e):
@ -330,7 +327,7 @@ class Logbot(SingleServerIRCBot):
def on_privmsg(self, c, e):
# c.privmsg(nm_to_n(e.source()), self.format["help"])
pass
pas
def on_quit(self, c, e):
nick = nm_to_n(e.source())
@ -343,7 +340,7 @@ class Logbot(SingleServerIRCBot):
self.write_event("topic", e)
def connect_ftp():
print("Using FTP %s..." % (FTP_SERVER))
print "Using FTP %s..." % (FTP_SERVER)
f = ftplib.FTP(FTP_SERVER, FTP_USER, FTP_PASS)
f.cwd(FTP_FOLDER)
return f
@ -353,7 +350,6 @@ def main():
create_tables()
t = threading.Thread(target=flaskapp.run, kwargs={"host": "0.0.0.0"})
t.daemon = True
t.start()
# Create the logs directory
@ -372,6 +368,7 @@ def main():
except KeyboardInterrupt:
if FTP_SERVER: bot.ftp.quit()
bot.quit()
t.join()
if __name__ == "__main__":

View file

@ -69,7 +69,7 @@ class PullRequest:
for repo in self.repos:
r = requests.get(repo["uri"])
if r.status_code != 200:
print("Error fetching %s", repo["name"])
print "Error fetching %s", repo["name"]
break
prs = r.json()
@ -83,4 +83,4 @@ class PullRequest:
if __name__ == "__main__":
p = PullRequest()
for line in p.check_all():
print(line["message"])
print line["message"]