aboutsummaryrefslogtreecommitdiffstats
path: root/project
diff options
context:
space:
mode:
authorjantuomi <jans.tuomi@gmail.com>2016-07-19 14:50:22 +0300
committerjantuomi <jans.tuomi@gmail.com>2016-07-19 14:50:22 +0300
commit9d94a01b631676b54f680c2571ae4dbba9ccd037 (patch)
tree2cac58eba2cd5d29ae0c2192efc5ca365dde7f74 /project
parent080cfd6cfe946e243239af201dea8466abaee020 (diff)
Restructure project
Diffstat (limited to 'project')
-rw-r--r--project/__init__.py0
-rw-r--r--project/basecommunicator.py10
-rw-r--r--project/botmanager.py135
-rw-r--r--project/jsonfactory.py27
-rw-r--r--project/mockshoutbox.py79
-rw-r--r--project/shoutboxapicommunicator.py41
-rw-r--r--project/telegramapicommunicator.py47
7 files changed, 339 insertions, 0 deletions
diff --git a/project/__init__.py b/project/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/project/__init__.py
diff --git a/project/basecommunicator.py b/project/basecommunicator.py
new file mode 100644
index 0000000..f4f8337
--- /dev/null
+++ b/project/basecommunicator.py
@@ -0,0 +1,10 @@
+class BaseCommunicator:
+ """Parent class for Telegram and shoutbox API communicators"""
+
+ @staticmethod
+ def fetch():
+ ...
+
+ @staticmethod
+ def send(data):
+ ...
diff --git a/project/botmanager.py b/project/botmanager.py
new file mode 100644
index 0000000..805f20c
--- /dev/null
+++ b/project/botmanager.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+
+import logging
+import random
+import sys
+import time
+import traceback
+
+import telepot
+
+from project.jsonfactory import JSONFactory
+from project.shoutboxapicommunicator import ShoutboxCommunicator
+from project.telegramapicommunicator import TelegramCommunicator
+
+
+class BotManager(object):
+ """Manager class for the process"""
+
+ # Make a dict of ( message id : timestamp ) pairs to
+ # keep track of sent messages.
+ # Messages older than 2 * update interval will be forgotten.
+ last_message_timestamps = {}
+
+ def __init__(self, token):
+ """
+ Attempt to create a bot with telepot
+
+ If not possible, crash gracefully
+ """
+ TelegramCommunicator.token = token
+ try:
+ logging.info("Creating bot listener with token {}...".format(token))
+ TelegramCommunicator.spawn_bot(token)
+ logging.info("Bot succesfully created.")
+ except:
+ traceback.print_exc()
+ logging.error("Error creating bot listener. src will now exit...")
+ sys.exit(1)
+
+ def set_parameters(self, shoutbox_api_url, telegram_chat_id, api_call_interval):
+ """Store parameters in the manager instance"""
+ ShoutboxCommunicator.url = shoutbox_api_url
+ TelegramCommunicator.chat_id = telegram_chat_id
+ ShoutboxCommunicator.interval = api_call_interval
+
+ def start(self):
+ """Try fetching bot information from Telegram to check connection"""
+ TelegramCommunicator.start_listening(self.handle)
+
+ while True:
+ # Forward all new messages to the Telegram chat
+ # and add them to the message dict
+ self.forward_to_telegram(ShoutboxCommunicator.fetch())
+
+ # Wait for the update interval
+ time.sleep(ShoutboxCommunicator.interval)
+
+ # Remove obsolete messages from the dict to prevent it
+ # from bloating
+ self.clean_up_message_dict()
+
+ def clean_up_message_dict(self):
+ """Purge all obsolete messages from the message dict"""
+ new_message_dict = {}
+ for msg_id in self.last_message_timestamps:
+
+ # if the message is older than 2 * call interval,
+ # pop it from the buffer because there is no way
+ # it can be a duplicate anymore
+ timestamp = round(float(self.last_message_timestamps[msg_id]))
+ if not round(time.time()) - timestamp < 2 * ShoutboxCommunicator.interval:
+ new_message_dict[msg_id] = self.last_message_timestamps[msg_id]
+ else:
+ logging.info("Popped message from buffer.")
+ self.last_message_timestamps = new_message_dict
+
+ def forward_to_telegram(self, messages):
+ """Send all messages in the messages list to the Telegram chat"""
+ try:
+ for message in messages:
+ # Do not send messages that have already been sent
+ # This check is in place because of possible artifacts in API calls
+ if message["id"] not in self.last_message_timestamps:
+ TelegramCommunicator.send(message)
+ self.last_message_timestamps[message["id"]] = message["timestamp"]
+
+ except:
+ logging.warning("Failed to send message to Telegram!")
+
+ songs = ["Ace of Spades", "Mökkitie", "Alpha Russian XXL Night Mixtape", "teekkarihymni"]
+
+ def action_now_playing(self, msg):
+ """Placeholder action for testing commands"""
+ TelegramCommunicator.send_raw("Radiossa soi {}!".format(random.choice(BotManager.songs)))
+
+ def action_not_supported(self, msg):
+ """Notify the user that the given command is not supported"""
+ TelegramCommunicator.send_raw("Tätä toimintoa ei ole tuettu.")
+
+ def parse_message(self, msg):
+ """Determine which action to take for an incoming Telegram text message"""
+ t = msg["text"]
+ content_type, chat_type, chat_id = telepot.glance(msg)
+ if "/nowplaying" in t:
+ self.action_now_playing(msg)
+ elif "/start" in t:
+ self.action_not_supported(msg)
+ elif "/stop" in t:
+ self.action_not_supported(msg)
+ elif str(chat_id) == TelegramCommunicator.chat_id.strip():
+ self.action_send_to_shoutbox(msg)
+ else:
+ logging.info("Got non-forwarded message from chat_id: {}".format(chat_id))
+
+ def action_send_to_shoutbox(self, msg):
+ """Forward a Telegram message to shoutbox"""
+ try:
+ user_name = msg["from"]["first_name"]
+ text = msg["text"]
+ date = msg["date"]
+ except KeyError:
+ logging.warning("Malformed message from Telegram! Details:\n{}".format(msg))
+ logging.warning("Skipped sending message to shoutbox.")
+ return
+
+ data = JSONFactory.make_object(text, user_name, date, "null")
+ logging.info("Created JSON packet:\n{}".format(data))
+ ShoutboxCommunicator.send(data)
+
+ def handle(self, msg):
+ """Start parsing the incoming message if it is a text message"""
+ content_type, chat_type, chat_id = telepot.glance(msg)
+
+ if content_type == 'text':
+ self.parse_message(msg)
diff --git a/project/jsonfactory.py b/project/jsonfactory.py
new file mode 100644
index 0000000..c57b430
--- /dev/null
+++ b/project/jsonfactory.py
@@ -0,0 +1,27 @@
+import json
+
+
+class JSONFactory(object):
+ """Factory class to create JSON messages to be sent to the shoutbox API"""
+ running_id = 1
+
+ @staticmethod
+ def make_object(text, user, timestamp, ip):
+ """
+ Create a JSON object from the passed parameters and
+ add a running id
+ """
+ message = json.dumps({
+ "id": JSONFactory.running_id,
+ "text": text,
+ "user": user,
+ "timestamp": timestamp,
+ "ip": ip
+ })
+ JSONFactory.running_id += 1
+ return message
+
+ @staticmethod
+ def make_array(object_list):
+ """Create a JSON array from a list of JSON objects"""
+ return "[\n" + ",\n".join(object_list) + "\n]"
diff --git a/project/mockshoutbox.py b/project/mockshoutbox.py
new file mode 100644
index 0000000..4a64406
--- /dev/null
+++ b/project/mockshoutbox.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+
+import sys
+import http.server
+import traceback
+from http.server import SimpleHTTPRequestHandler
+import json
+
+import time
+
+
+class MyHandler(SimpleHTTPRequestHandler):
+ """
+ Mock shoutbox HTTP server to test
+ shoutbox API requests.
+ """
+ def do_HEAD(self):
+ self.send_response(200)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+
+ def do_GET(self):
+ """
+ Respond to a GET with a JSON array containing one object
+ that contains the following:
+
+ test user: test message
+ """
+
+ self.send_response(200)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+
+ timestamp = str(round(time.time()))
+ body = json.dumps([{
+ "user": "test user",
+ "text": "test message",
+ "timestamp": timestamp,
+ "id": timestamp,
+ "ip": "1.2.3.4"
+ }])
+ self.wfile.write(body.encode())
+
+ def do_POST(self):
+ """
+ Respond to a POST by printing the message contents to console
+ """
+ content_len = int(self.headers.get('Content-Length'))
+ post_data = self.rfile.read(content_len)
+
+ try:
+ message = json.loads(post_data.decode('utf-8'))
+ print("{}: {}".format(message["user"], message["text"]))
+ self.send_response(200)
+
+ except:
+ traceback.print_exc()
+ self.send_response(400)
+
+ self.end_headers()
+
+ return
+
+HandlerClass = MyHandler
+ServerClass = http.server.HTTPServer
+Protocol = "HTTP/1.0"
+
+if sys.argv[1:]:
+ port = int(sys.argv[1])
+else:
+ port = 8000
+server_address = ('127.0.0.1', port)
+
+HandlerClass.protocol_version = Protocol
+httpd = ServerClass(server_address, HandlerClass)
+
+sa = httpd.socket.getsockname()
+print("Serving HTTP on", sa[0], "port", sa[1], "...")
+httpd.serve_forever()
diff --git a/project/shoutboxapicommunicator.py b/project/shoutboxapicommunicator.py
new file mode 100644
index 0000000..7194c8f
--- /dev/null
+++ b/project/shoutboxapicommunicator.py
@@ -0,0 +1,41 @@
+import json
+import logging
+
+import requests
+
+from project.basecommunicator import BaseCommunicator
+
+
+class ShoutboxCommunicator(BaseCommunicator):
+ """Class for communication with the shoutbox API"""
+
+ url = "http://localhost:8000"
+ interval = 10
+
+ @staticmethod
+ def fetch():
+ """Get and return a list of new messages from the API"""
+ try:
+ r = requests.get(ShoutboxCommunicator.url)
+ logging.info("Response from API OK.")
+
+ except:
+ logging.warning("Failed to get response from API!")
+ return
+
+ content = json.loads(r.text)
+ logging.info("Messages:")
+ for msg in content:
+ logging.info("{}: {}".format(msg["user"], msg["text"]))
+
+ return content
+
+ @staticmethod
+ def send(data):
+ """Send a JSON message to the API"""
+ try:
+ requests.post(ShoutboxCommunicator.url, data)
+ user = json.loads(data)["user"]
+ logging.info("Sent message from {} to shoutbox.".format(user))
+ except:
+ logging.warning("Failed to send message to shoutbox API!")
diff --git a/project/telegramapicommunicator.py b/project/telegramapicommunicator.py
new file mode 100644
index 0000000..7fdb29c
--- /dev/null
+++ b/project/telegramapicommunicator.py
@@ -0,0 +1,47 @@
+import logging
+import sys
+
+import telepot
+
+from project.basecommunicator import BaseCommunicator
+
+
+class TelegramCommunicator(BaseCommunicator):
+ """Class for communicating with the Telegram API"""
+
+ chat_id = "default_id"
+ bot = None
+ token = "DEFAULT_TOKEN_123"
+
+ @staticmethod
+ def spawn_bot(token):
+ TelegramCommunicator.bot = telepot.Bot(token)
+
+ @staticmethod
+ def fetch():
+ raise Exception("""Attempted to fetch messages from Telegram manually.
+ Please use telepot bot functionality for listening to messages.""")
+
+ @staticmethod
+ def send(data):
+ TelegramCommunicator.send_raw("{}: {}".format(data["user"], data["text"]))
+
+ @staticmethod
+ def send_raw(message):
+ try:
+ TelegramCommunicator.bot.sendMessage(TelegramCommunicator.chat_id,
+ message)
+ except:
+ logging.warning("Could not send message to Telegram chat id {}!".format(TelegramCommunicator.chat_id))
+
+ @staticmethod
+ def start_listening(handle):
+ try:
+ logging.info("Bot info: {}".format(TelegramCommunicator.bot.getMe()))
+ except:
+ logging.error("Could not fetch bot info. Check your token and connectivity to Telegram!")
+ sys.exit(1)
+
+ TelegramCommunicator.bot.message_loop(handle)
+
+ logging.info("Listening for messages...")