#!/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)