You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

366 lines
14 KiB

#!/usr/bin/env python3
import regex
import ssl
import http
import http.client
import hmac
import json
import socket
import threading
import logging
from time import sleep
from flask import Flask, request
from pprint import pprint
from hashlib import sha256
from phabricator import Phabricator
from launchpadlib.launchpad import Launchpad
website = "https://phab.lubuntu.me"
phab = Phabricator(host=website + "/api/", token="API KEY")
username = "lugito"
password = ""
server = "irc.freenode.net"
port = 6697
channel = "#lubuntu-devel"
lp = Launchpad.login_with("lugito", "production", "devel")
bugmessage = ("This bug has been marked as fixed in the Git repository: LINK\n"
"The commit message is the following: COMMITMESSAGE\n\n"
"(Note: I am only a bot. If this message was received in error, "
"please contact my owners on the Lubuntu Team.)")
cursupportedrels = ["Cosmic", "Bionic", "Xenial", "Trusty"]
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)
logger = logging.getLogger(__name__)
app = Flask(__name__)
def connecttoirc():
global conn, username
rawconn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn = ssl.wrap_socket(rawconn)
conn.connect((server, port))
setup = False
usersuffix = 0
logger.info("Connecting to IRC.")
while not setup:
response = conn.recv(512).decode("utf-8")
logger.debug(response)
if "No Ident response" in response:
conn.send("NICK {}\r\n".format(username).encode("utf-8"))
conn.send("USER {} * * :{}\r\n".format(username, username).encode("utf-8"))
conn.send("PRIVMSG nickserv :identify {} {}\r\n".format(username, password).encode("utf-8"))
if "You are now identified" in response:
sleep(5)
conn.send("JOIN {}\r\n".format(channel).encode("utf-8"))
if "477" in response:
sleep(5)
conn.send("JOIN {}\r\n".format(channel).encode("utf-8"))
if "433" in response:
usersuffix = usersuffix + 1
username = username + str(usersuffix)
conn.send("NICK {}\r\n".format(username).encode("utf-8"))
conn.send("USER {} * * :{}\r\n".format(username, username).encode("utf-8"))
if "PING" in response:
conn.send("PONG :{}\r\n".format(response.split(":")[1]).encode("utf-8"))
if "366" in response:
setup = True
logger.info("Successfully connected to the IRC server.")
def isnewtask(task):
newtask = None
modified = None
for data in task:
if modified:
if data["dateCreated"] == data["dateModified"] and data["dateCreated"] == modified:
modified = data["dateCreated"]
newtask = True
else:
newtask = False
break
else:
modified = data["dateCreated"]
return newtask
def sendnotice(message):
conn.send("NOTICE {} :{}\r\n".format(channel, message).encode("utf-8"))
def ircmessage(objectstr, who, body, link):
# e.g. [T31: Better IRC integration]
message = "\x033[\x03\x0313" + objectstr + "\x03\x033]\x03 "
# e.g. tsimonq2 (Simon Quigley)
message = message + "\x0315" + who + "\x03 "
# e.g. commented on the task:
message = message + body + ": "
# e.g. https://phab.lubuntu.me/T40#779
message = message + "\x032" + link + "\x03"
# Make sure we can debug this if it goes haywire
logger.debug(message)
# Sleep for a fifth of a second, so when we have a bunch of messages we have a buffer
sleep(0.2)
# Aaaaand, send it off!
sendnotice(message)
def gettaskinfo(task):
sendmessage = ""
try:
# We only need the task number.
taskinfo = phab.maniphest.info(task_id=int(task.split("T")[1]))
sendmessage = sendmessage + "\x033[\x03"
# The color of the priority text should correspond to its value.
color = taskinfo["priorityColor"]
if color == "violet":
sendmessage = sendmessage + "\x036Needs Triage"
elif color == "pink":
sendmessage = sendmessage + "\x035Unbreak Now!"
elif color == "red":
sendmessage = sendmessage + "\x034High"
elif color == "orange":
sendmessage = sendmessage + "\x037Medium"
elif color == "yellow":
sendmessage = sendmessage + "\x038Low"
elif color == "sky":
sendmessage = sendmessage + "\x037Wishlist"
# Put the task status in the message.
sendmessage = sendmessage + ", " + taskinfo["statusName"] + "\x03"
sendmessage = sendmessage + "\x033]\x03 "
# Put the title in there as well.
sendmessage = sendmessage + taskinfo["title"].strip() + ": "
# And the link.
sendmessage = sendmessage + "\x032" + taskinfo["uri"] + "\x03"
# Send it off!
sendnotice(sendmessage)
# If someone wrote something like "Tblah", obviously that's not right.
except ValueError:
sendnotice("\x034Error: " + task.strip() + "is an invalid task reference.\x03")
return None
def ircbot(message, msgtype):
if msgtype == "info":
message = message.split(" :" + username + ": info")[1]
for item in message.split():
if item.startswith("T"):
gettaskinfo(item.strip())
elif msgtype == "link":
for item in message.split("https://phab.lubuntu.me/"):
if item.split()[0].strip().startswith("T"):
gettaskinfo(item.split()[0].strip())
else:
sendnotice("\x034Error: unknown command.\x03")
return None
def listenirc():
while True:
ircmsg = conn.recv(512)
if len(ircmsg) == 0:
# logger.warn is deprecated, use .warning.
logger.warning("Connection lost, reconnecting!")
connecttoirc()
continue
ircmsg = ircmsg.decode("UTF-8").strip('\n\r')
logger.debug(ircmsg)
if ircmsg.find("PING :") != -1:
conn.send(bytes("PONG :pingis\n", "UTF-8"))
elif ircmsg.find(" :" + username + ": info") != -1:
ircbot(ircmsg, "info")
elif ircmsg.find("https://phab.lubuntu.me/T") != -1:
ircbot(ircmsg, "link")
@app.route("/commithook", methods=["POST"])
def commithook():
data = request.data
# Use hash_ instead of hash to not shadow other globals/protecteds
hash_ = hmac.new(bytes(u"HMAC KEY", "utf-8"), data, sha256)
# We MUST ensure that the request came from Phab.
if hash_.hexdigest() == request.headers["X-Phabricator-Webhook-Signature"]:
data = json.loads(data)
logger.debug(data)
exists = True
try:
# Try to find the object.
search = phab.transaction.search(objectIdentifier=data["object"]["phid"])["data"]
# Find the author too.
userlookup = search[0]["authorPHID"]
who = str(dict(phab.phid.query(phids=[userlookup]))[userlookup]["fullName"])
# If the object exists, no worries, let's just return a good response.
except http.client.HTTPException:
exists = False
if exists:
if data["object"]["type"] == "CMIT":
logger.debug("It's a commit!")
commitphid = data["object"]["phid"]
phidquery = phab.phid.query(phids=[commitphid])[commitphid]
commitmessage = phidquery["fullName"].replace(phidquery["name"] + ": ", "")
# When we're messing with bug importances, we need to be
# absolutely 100% sure we aren't screwing with the rest of the bug.
# Since there's no super clean way to get source package names,
# we have to hardcode them.
lpname = None
if "rDEFAULTSETTINGS" in phidquery["name"]:
lpname = "lubuntu-default-settings"
elif "rART" in phidquery["name"]:
lpname = "lubuntu-artwork"
elif "rCALASETTINGS" in phidquery["name"]:
lpname = "calamares-settings-ubuntu"
elif "rQTERMINALPACKAGING" in phidquery["name"]:
lpname = "qterminal"
elif "rLXQTCONFIGPACKAGING" in phidquery["name"]:
lpname = "lxqt-config"
elif "rNMTRAYPACKAGING" in phidquery["name"]:
lpname = "nm-tray"
else:
logger.debug("Somehow, an unsupported repository showed up here.")
if lpname:
# https://help.launchpad.net/Code/Git#Linking_to_bugs
regexp = regex.compile(r"lp:\s+\#\d+(?:,\s*\#\d+)*")
regexpsearch = regexp.search(commitmessage.lower())
if regexpsearch:
lpbugs = regexpsearch.group(0).strip("lp: ").replace("#", "")
for bug in lpbugs.split(", "):
goodtask = None
lbug = lp.load("/bugs/" + str(bug).strip())
bug = lbug
for task in bug.bug_tasks:
for rel in cursupportedrels:
if lpname + " (Ubuntu " + rel + ")" in task.bug_target_display_name:
goodtask = task
break
if not goodtask:
if lpname + " (Ubuntu)" in task.bug_target_display_name:
goodtask = task
if goodtask:
message = bugmessage
message = message.replace("LINK", website + "/" + phidquery["name"])
message = message.replace("COMMITMESSAGE", commitmessage)
bug.newMessage(content=message)
goodtask.status = "Fix Committed"
goodtask.lp_save()
return "OK"
@app.route("/irc", methods=["POST"])
def main():
data = request.data
# Use hash_ instead of 'hash' to not shadow externally global values/variables/protecteds
hash_ = hmac.new(bytes(u"HMAC KEY", "utf-8"), data, sha256)
# We MUST ensure that the request came from Phab.
if hash_.hexdigest() == request.headers["X-Phabricator-Webhook-Signature"]:
data = json.loads(data)
logger.debug(data)
exists = True
try:
# Try to find the object.
search = phab.transaction.search(objectIdentifier=data["object"]["phid"])["data"]
# Find the author too.
userlookup = search[0]["authorPHID"]
who = str(dict(phab.phid.query(phids=[userlookup]))[userlookup]["fullName"])
# If the object exists, no worries, let's just return a good response.
except http.client.HTTPException:
exists = False
if exists:
logger.debug("Object exists, checking to see if it's a task or a commit.")
if data["object"]["type"] == "TASK":
logger.debug("This is a task. Checking if it's new.")
newtask = isnewtask(search)
if newtask:
logger.debug("Yes, it's a new task.")
else:
logger.debug("No, it's not a new task.")
# If it's not a new task, let's see if it's a comment, and if it's just an edit.
comment = None
commentid = None
edited = None
if not newtask:
commentid = None
edited = False
for task in search:
dataepoch = data["action"]["epoch"]
datemodified = task["dateModified"]
# All comments within ten seconds of the request are fair game.
if (dataepoch - 10) <= datemodified <= (dataepoch + 10) and task["comments"] != []:
logger.debug("It's a comment, yes.")
comment = True
commentid = task["id"]
if datemodified != task["dateCreated"]:
logger.debug("The comment was edited.")
edited = True
else:
logger.debug("The comment was NOT edited.")
edited = False
break
else:
comment = False
if comment or edited or newtask:
objectstr = phab.phid.query(phids=[data["object"]["phid"]])[data["object"]["phid"]]["fullName"]
body = ""
if comment:
body = "commented on the task"
elif edited:
body = "edited a message on the task"
elif newtask:
body = "just created this task"
# Assuming this is a comment, there should always be a URI associated with it.
link = phab.phid.query(phids=[data["object"]["phid"]])[data["object"]["phid"]]["uri"]
# Even though this can be off sometimes, let's include the comment ID in the link too.
# FIXME: Make this more accurate, and figure out why it's inaccurate at times.
if commentid:
link = link + "#" + str(commentid)
ircmessage(objectstr, who, body, link)
elif data["object"]["type"] == "CMIT":
logger.debug("It's a commit!")
commitphid = data["object"]["phid"]
objectstr = phab.phid.query(phids=[commitphid])[commitphid]["fullName"]
body = "committed"
# The URI for this one is waaaaaaay too long. Let's assemble it ourselves.
link = website + "/" + phab.phid.query(phids=[commitphid])[commitphid]["name"]
ircmessage(objectstr, who, body, link)
return "OK"
if __name__ == "__main__":
connecttoirc()
t = threading.Thread(target=listenirc)
t.daemon = True
t.start()
app.run(host="0.0.0.0", port=5000)