Sfoglia il codice sorgente

bin/darkirc: add bots code to script/

Dastan-glitch 3 anni fa
parent
commit
ef27e9932b

+ 17 - 0
bin/darkirc/script/README.md

@@ -16,3 +16,20 @@ is done through `meetbot_cfg.py`.
 * Edit `meetbot_cfg.py` for your needs.
 * Navigate terminal to the folder where `meetbot.py` is.
 * Run the bot: `$ python meetbot.py`
+
+## `titlebot`
+`titlebot.py` is a bot used to print the title of a website provided by
+a link in a `PRIVMSG`.
+
+## `tweetifier`
+`tweetifier` is yet another bot that recognizes Twitter links, fetch
+the tweet text and print it out in irc.
+
+## Requirment
+ `git clone https://github.com/Dastan-glitch/tweety.git`\
+ `cd tweety && pip install .`
+
+## `tau-notifier`
+sends notifications about some `tau` commands (namely: adding a new 
+task, changing state, reassigning and new comments) to desired channels 
+in `ircd`. 

+ 144 - 0
bin/darkirc/script/bots/commitbot.py

@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+
+from http.server import BaseHTTPRequestHandler,HTTPServer
+import json
+import sys
+import irc
+
+# Attributes of the server this bot will run on
+SERVER_HOST = 'server.url.or.ip'
+SERVER_PORT = 11022
+
+# Attributes of the IRC connection
+IRC_SERVER = '127.0.0.1'
+IRC_PORT = 6667
+IRC_CHANNEL = ['#dev']
+IRC_NICK = 'commits-notifier'
+
+# Set the password for your registered empty, leave empty if not applicable
+# Note: freenode(and potentially other servers) want password to be of the form
+# "nick:pass", so for ex. IRC_PASS = 'WfTestBot:mypass123'
+IRC_PASS = ''
+
+# a dictionary of branches push-related events should be enabled for, or empty if all are enabled
+GH_PUSH_ENABLED_BRANCHES = [] # for example, ['master', 'testing', 'author/repo:branch']
+
+# a dictionary of branches push-related events should be ignored for, or empty if all are enabled
+GH_PUSH_IGNORE_BRANCHES = ['gh-pages']
+
+# a list of push-related events the bot should post notifications for
+GH_PUSH_ENABLED_EVENTS = ['push'] # no others supported for now
+
+# a list of PR-related events the bot should post notifications for
+# notice 'merged' is just a special case of 'closed'
+GH_PR_ENABLED_EVENTS = ['opened', 'closed', 'reopened'] # could also add 'synchronized', 'labeled', etc.
+
+# handle POST events from github server
+# We should also make sure to ignore requests from the IRC, which can clutter
+# the output with errors
+CONTENT_TYPE = 'content-type'
+CONTENT_LEN = 'content-length'
+EVENT_TYPE = 'x-github-event'
+
+ircc = irc.IRC()
+ircc.connect(IRC_SERVER, IRC_PORT, IRC_CHANNEL, IRC_NICK)
+
+def handle_push_event(irc, data):
+    if GH_PUSH_ENABLED_BRANCHES:
+        branch = get_branch_name_from_push_event(data)
+        repo = data['repository']['full_name']
+        repobranch = repo + ':' + branch
+        if not branch in GH_PUSH_ENABLED_BRANCHES:
+            if not repobranch in GH_PUSH_ENABLED_BRANCHES:
+                return
+    
+    if GH_PUSH_IGNORE_BRANCHES:
+        branch = get_branch_name_from_push_event(data)
+        if branch in GH_PUSH_IGNORE_BRANCHES:
+            return
+
+    if 'push' in GH_PUSH_ENABLED_EVENTS:
+        handle_forward_push(irc, data)
+
+def handle_pull_request(irc, data):
+    author = data['sender']['login']
+    if not data['action'] in GH_PR_ENABLED_EVENTS:
+        return
+
+    action = data['action']
+    merged = data['pull_request']['merged']
+    action = 'merged' if action == 'closed' and merged else action
+    pr_num = '#' + str(data['number'])
+    title = data['pull_request']['title']
+
+    print("PR event:")
+    print(f"@{author} {action} pull request {pr_num}: {title}")
+    print("==============================================")
+
+    irc.send("#dev", f"@{author} {action} pull request {pr_num}: {title}")
+
+def get_branch_name_from_push_event(data):
+    return data['ref'].split('/')[-1]
+
+def handle_forward_push(irc, data):
+    author = data['pusher']['name']
+
+    num_commits = len(data['commits'])
+    num_commits = str(num_commits) + " commit" + ('s' if num_commits > 1 else '')
+
+    branch = get_branch_name_from_push_event(data)
+
+    commits = list(map(fmt_commit, data['commits']))
+    for commit in commits:
+        print("Push event:")
+        print(f"@{author} pushed {num_commits} to {branch}: {commit}")
+        print("==============================================")
+        irc.send("#dev", f"@{author} pushed {num_commits} to {branch}: {commit}")
+
+def fmt_commit(cmt):
+    hsh = cmt['id'][:10]
+    # author = cmt['author']['name']
+    message = cmt['message'].split("\n")
+    message = message[0] \
+            + ('...' if len(message) > 1 else '')
+
+    return '{}: {}'.format(hsh, message)
+
+class MyHandler(BaseHTTPRequestHandler):
+    def do_GET(self):
+        pass
+    def do_CONNECT(self):
+        pass
+    def do_POST(self):
+        if not all(x in self.headers for x in [CONTENT_TYPE, CONTENT_LEN, EVENT_TYPE]):
+            return
+        content_type = self.headers['content-type']
+        content_len = int(self.headers['content-length'])
+        event_type = self.headers['x-github-event']
+
+        if content_type != "application/json":
+            self.send_error(400, "Bad Request", "Expected a JSON request")
+            return
+
+        data = self.rfile.read(content_len)
+        if sys.version_info < (3, 6):
+            data = data.decode()
+
+        self.send_response(200)
+        self.send_header('content-type', 'text/html')
+        self.end_headers()
+        self.wfile.write(bytes('OK', 'utf-8'))
+
+        if event_type == 'push':
+            handle_push_event(ircc, json.loads(data))
+        elif event_type == 'pull_request':
+            handle_pull_request(ircc, json.loads(data))
+        return
+
+# Run Github webhook handling server
+try:
+    server = HTTPServer((SERVER_HOST, SERVER_PORT), MyHandler)
+    server.serve_forever()
+except KeyboardInterrupt:
+    print("Exiting")
+    server.socket.close()

+ 35 - 0
bin/darkirc/script/bots/irc.py

@@ -0,0 +1,35 @@
+import socket
+
+class IRC:
+    irc = socket.socket()
+  
+    def __init__(self):
+        # Define the socket
+        self.irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ 
+    def send(self, channel, msg):
+        # Transfer data
+        self.irc.send(bytes("PRIVMSG " + channel + " :" + msg + "\n", "UTF-8"))
+ 
+    def connect(self, server, port, channels, botnick):
+        # Connect to the server
+        print("Connecting to: " + server)
+        self.irc.connect((server, port))
+
+        # Perform user authentication
+        self.irc.send(bytes("USER " + botnick + " " + botnick +" " + botnick + " :python\n", "UTF-8"))
+        self.irc.send(bytes("NICK " + botnick + "\n", "UTF-8"))
+
+        # join the channel
+        for chan in channels:
+            self.irc.send(bytes("JOIN " + chan + "\n", "UTF-8"))
+ 
+    def get_response(self):
+        # Get the response
+        resp = self.irc.recv(2040).decode("UTF-8")
+        msg = resp.split(':')[-1]
+ 
+        if resp.find('PING') != -1:                      
+            self.irc.send(bytes('PONG ' + msg + '\r\n', "UTF-8")) 
+ 
+        return resp

+ 16 - 0
bin/darkirc/script/meetbot.py → bin/darkirc/script/bots/meetbot/meetbot.py

@@ -2,6 +2,7 @@
 import asyncio
 import logging
 import pickle
+from time import time
 
 from base58 import b58decode
 from nacl.public import PrivateKey, Box
@@ -42,6 +43,8 @@ async def channel_listen(host, port, nick, chan):
         writer.write(msg.encode("utf-8"))
         await writer.drain()
 
+        elapsed=0
+
         logging.info("%s: Listening to channel", chan)
         while True:
             msg = await reader.readline()
@@ -91,10 +94,16 @@ async def channel_listen(host, port, nick, chan):
                     CHANS[chan]["topics"] = topics
                     writer.write(reply.encode("utf-8"))
                     await writer.drain()
+                    elapsed=time()
                     continue
 
                 if msg_title == "!end":
                     logging.info("%s: Got !end", chan)
+                    reply = f"PRIVMSG {chan} :Elapsed time: {round((time() - elapsed)/60, 1)} min"
+                    elapsed=0
+                    logging.info("%s: Send: %s", chan, reply)
+                    writer.write((reply + "\r\n").encode("utf-8"))
+                    await writer.drain()
                     reply = f"PRIVMSG {chan} :Meeting ended"
                     logging.info("%s: Send: %s", chan, reply)
                     writer.write((reply + "\r\n").encode("utf-8"))
@@ -158,6 +167,13 @@ async def channel_listen(host, port, nick, chan):
                 if msg_title == "!next":
                     logging.info("%s: Got !next", chan)
                     topics = CHANS[chan]["topics"]
+
+                    reply = f"PRIVMSG {chan} :Elapsed time: {round((time() - elapsed)/60, 1)} min"
+                    logging.info("%s: Send: %s", chan, reply)
+                    writer.write((reply + "\r\n").encode("utf-8"))
+                    await writer.drain()
+                    elapsed=time()
+
                     if len(topics) == 0:
                         reply = f"PRIVMSG {chan} :No further topics"
                     else:

+ 0 - 0
bin/darkirc/script/meetbot_cfg.py → bin/darkirc/script/bots/meetbot/meetbot_cfg.py


+ 97 - 0
bin/darkirc/script/bots/taubot.py

@@ -0,0 +1,97 @@
+import argparse
+import irc
+import json
+import socket
+import sys
+
+# parse arguments
+parser = argparse.ArgumentParser(description='IRC bot to send a pipe to an IRC channel')
+parser.add_argument('--server',default='127.0.0.1', help='IRC server')
+parser.add_argument('--port', default=11066, help='port of the IRC server')
+parser.add_argument('--nickname', help='bot nickname in IRC')
+parser.add_argument('--channel', default="#dev", action='append', help='channel to join')
+parser.add_argument('--pipe', default="/tmp/tau_pipe" , help='pipe to read from')
+parser.add_argument('--skip', default="prv", help='Project or Tags to skip notifications for')
+parser.add_argument('--alt-chan', default="#test", required='--skip' in sys.argv, help='Alternative channel to send notifications to when there are skipped tasks')
+
+args = parser.parse_args()
+
+channels = [args.channel, args.alt_chan] if args.alt_chan is not None else args.channel
+
+ircc = irc.IRC()
+ircc.connect(args.server, int(args.port), channels, args.nickname)
+
+while True:
+    with open(args.pipe) as handle:
+        while True:
+            log_line = handle.readline()
+            if not log_line:
+                break
+            print(log_line)
+            print("======================================")
+            task = json.loads(log_line)
+            channel = args.channel
+
+            for event in task['events']:
+                cmd = event['action']
+                if cmd == "add_task":
+                    user = task['owner']
+                    id = task['id']
+                    title = task['title']
+                    assigned = ", ".join(task['assign'])
+
+                    project = task['project'] if task['project'] is not None else []
+                    if args.skip in project or args.skip in task['tags']:
+                        channel = args.alt_chan
+
+                    if len(assigned) > 0:
+                        notification = f"{user} added task ({id}): {title}. assigned to {assigned}"
+                    else:
+                        notification = f"{user} added task ({id}): {title}"
+                    # print(notification)
+                    ircc.send(channel, notification)
+                elif cmd == "state":
+                    user = event['author']
+                    state = event['content']
+                    id = task['id']
+                    title = task['title']
+
+                    project = task['project'] if task['project'] is not None else []
+                    if args.skip in project or args.skip in task['tags']:
+                        channel = args.alt_chan
+
+                    if state == "start":
+                        notification = f"{user} started task ({id}): {title}"
+                    elif state == "pause":
+                        notification = f"{user} paused task ({id}): {title}"
+                    elif state == "stop":
+                        notification = f"{user} stopped task ({id}): {title}"
+                    elif state == "cancel":
+                        notification = f"{user} canceled task ({id}): {title}"
+                    # print(notification)
+                    ircc.send(channel, notification)
+                elif cmd == "comment":
+                    user = event['author']
+                    id = task['id']
+                    title = task['title']
+                    
+                    project = task['project'] if task['project'] is not None else []
+                    if args.skip in project or args.skip in task['tags']:
+                        channel = args.alt_chan
+
+                    notification = f"{user} commented on task ({id}): {title}"
+                    # print(notification)
+                    ircc.send(channel, notification)
+                elif cmd == "assign":
+                    user = event['author']
+                    assignees = event['content']
+                    id = task['id']
+                    title = task['title']
+
+                    project = task['project'] if task['project'] is not None else []
+                    if args.skip in project or args.skip in task['tags']:
+                        channel = args.alt_chan
+
+                    notification = f"{user} reassigned task ({id}): {title} to {assignees}"
+                    # print(notification)
+                    ircc.send(channel, notification)

+ 53 - 0
bin/darkirc/script/bots/titlebot.py

@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import re
+import irc
+import requests
+from bs4 import BeautifulSoup
+from urllib.parse import urlparse
+
+## IRC Config
+server = "127.0.0.1"
+port = 11070
+channels = ["#test", "#test1"]
+botnick = "website-title"
+ircc = irc.IRC()
+ircc.connect(server, port, channels, botnick)
+
+while True:
+    text = ircc.get_response()
+    if not len(text) > 0:
+        continue
+    print(text)
+    text_list = text.split(' ')
+    if text_list[1] == "PRIVMSG":
+        channel = text_list[2]
+        msg = ' '.join(text_list[3:])
+        url = re.findall(r'(https?://[^\s]+)', msg)
+
+        for i in url:
+            parsed_url = urlparse(i)
+            if parsed_url.netloc.lower() in ['twitter.com','t.co'] or parsed_url.scheme != 'https':
+                continue
+            try:
+                reqs = requests.get(i)
+            except requests.exceptions.SSLError:
+                print("SSLERROR: wrong signature type")
+                continue
+            soup = BeautifulSoup(reqs.text, 'html.parser')
+
+            try:
+                title_text = soup.find('title').get_text()
+            except:
+                print("Error: Title not found!")
+                continue
+            title_text = title_text.split('\n')
+            title_msg = []
+            # remove empty lines from title body
+            for line in title_text:
+                if not line.strip():
+                    continue
+                title_msg.append(line)
+            title_msg = " ".join(title_msg)
+            print(f"Title: {title_msg}")
+            ircc.send(channel, f"Title: {title_msg}")

+ 58 - 0
bin/darkirc/script/bots/tweetifier.py

@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import re
+import irc
+from tweety.bot import Twitter
+from urllib.parse import urlparse
+
+## IRC Config
+server = "127.0.0.1"
+port = 11069
+channels = ["#test", "#test1"]
+botnick = "tweetifier"
+ircc = irc.IRC()
+ircc.connect(server, port, channels, botnick)
+
+while True:
+    text = ircc.get_response()
+    if not len(text) > 0:
+        continue
+    print(text)
+    text_list = text.split(' ')
+    if text_list[1] == "PRIVMSG":
+        channel = text_list[2]
+        msg = ' '.join(text_list[3:])
+        url = re.findall(r'(https?://[^\s]+)', msg)
+        
+        for i in url:
+            parsed_url = urlparse(i)
+            if str(parsed_url.path).endswith("/"):
+                tweetId = str(parsed_url.path)[:-1].split("/")[-1]
+            else:
+                tweetId = str(parsed_url.path).split("/")[-1]
+            print(f"tweet id: {tweetId}")
+            if not (parsed_url.netloc.lower() in ['twitter.com','t.co'] and parsed_url.scheme == 'https'):
+                continue
+            app = Twitter()
+            try:
+                tweet_text = app.tweet_detail(tweetId)
+            except:
+                print("Error: The Identifier provided of the tweet is either invalid or the tweet is private")
+                continue
+
+            author_name = tweet_text.author.name
+            screen_name = tweet_text.author.screen_name
+
+            tt = tweet_text.text.split('\n')
+            tweet_msg = []
+            # remove empty lines from tweet body
+            for line in tt:
+                if not line.strip():
+                    continue
+                tweet_msg.append(line)
+            tweetify = str(' '.join(tweet_msg))
+            if tweetify.startswith("@"):
+                tweetify = f"Replying to {tweetify}"
+            print(tweetify)
+
+            ircc.send(channel, f"{author_name}(@{screen_name}): {tweetify}")