diff --git a/README.md b/README.md
index 6132129..c109097 100644
--- a/README.md
+++ b/README.md
@@ -556,13 +556,15 @@ Commands:
Tautulli instance and API Key:
-The module work with an existing installed instance of Tautulli accessible on the machine at the path defined by env variable `TAUTULLI_PATH`
+The module work with an instance of Tautulli accessible on URL defined by env variable `TAUTULLI_URL`
+In order to load art pictures you need to define an instance of Plex Media Server with its token as well by the two env variables `PLEX_MEDIA_SERVER_URL` and `PLEX_MEDIA_SERVER_TOKEN`
You have to provide an api key by using the relevant command.
Environ variables seen by command:
-* TAUTULLI_PATH: Path accessible from the machine to the installed instance of Tautulli
-* TAUTULLI_URL: Url accessible from the machine to the installed instance of Tautulli
+* PLEX_MEDIA_SERVER_URL: Url accessible from the machine to an instance of Plex Media Server
+* PLEX_MEDIA_SERVER_TOKEN: Plex Token for the instance of Plex Media Server
+* TAUTULLI_URL: Url accessible from the machine to an instance of Tautulli
* TAUTULLI_NOTIFIER_ADDR: Listening address for the Tautulli webhook handler target
* TAUTULLI_NOTIFIER_PORT: Listening port for the Tautulli webhook handler target
* BOT_OWNERS: Owner of the rooms in the list for the notification webhook
diff --git a/bot.py b/bot.py
index 9d1cb71..07e8b69 100755
--- a/bot.py
+++ b/bot.py
@@ -24,19 +24,7 @@ from PIL import Image
import requests
from nio import AsyncClient, InviteEvent, JoinError, RoomMessageText, MatrixRoom, LoginError, RoomMemberEvent, RoomVisibility, RoomPreset, RoomCreateError, RoomResolveAliasResponse, UploadError, UploadResponse, SyncError
-# Couple of custom exceptions
-
-
-class UploadFailed(Exception):
- pass
-
-class CommandRequiresAdmin(Exception):
- pass
-
-
-class CommandRequiresOwner(Exception):
- pass
-
+from modules.common.exceptions import CommandRequiresAdmin, CommandRequiresOwner, UploadFailed
class Bot:
@@ -115,7 +103,7 @@ class Bot:
except ValueError: # broken cache?
self.logger.warning(f"Image cache for {url} could not be unpacked, attempting to re-upload...")
try:
- matrix_uri, mimetype, w, h, size = await self.upload_image(url, event=event, blob=blob, no_cache=no_cache)
+ matrix_uri, mimetype, w, h, size = await self.upload_image(url, blob=blob, no_cache=no_cache)
except (UploadFailed, ValueError):
return await self.send_text(room, f"Sorry. Something went wrong fetching {url} and uploading the image to matrix server :(", event=event)
diff --git a/modules/apod.py b/modules/apod.py
index 05b92e1..cfd8a84 100644
--- a/modules/apod.py
+++ b/modules/apod.py
@@ -7,7 +7,7 @@ from nio import AsyncClient, UploadError
from nio import UploadResponse
from modules.common.module import BotModule
-from bot import UploadFailed
+from modules.common.exceptions import UploadFailed
class Apod:
diff --git a/modules/common/exceptions.py b/modules/common/exceptions.py
new file mode 100644
index 0000000..933eb20
--- /dev/null
+++ b/modules/common/exceptions.py
@@ -0,0 +1,14 @@
+# Couple of custom exceptions
+
+
+class UploadFailed(Exception):
+ pass
+
+class CommandRequiresAdmin(Exception):
+ pass
+
+
+class CommandRequiresOwner(Exception):
+ pass
+
+
diff --git a/modules/printing.py b/modules/printing.py
index 9fc5902..3e5dcc6 100644
--- a/modules/printing.py
+++ b/modules/printing.py
@@ -5,7 +5,6 @@ import sys
import traceback
import cups
import httpx
-import asyncio
import aiofiles
import os
diff --git a/modules/tautulli.py b/modules/tautulli.py
index 89c1e3c..1d3a2ad 100644
--- a/modules/tautulli.py
+++ b/modules/tautulli.py
@@ -1,81 +1,98 @@
+import time
import urllib.request
import urllib.parse
import urllib.error
-import datetime
-import pytz
+
+import aiohttp.web
+import requests
import os
-import sys
import json
-
-import importlib
-from importlib import reload
-
-from nio import MatrixRoom
-
-from aiohttp import web
import asyncio
-import nest_asyncio
-nest_asyncio.apply()
+from aiohttp import web
+from future.moves.urllib.parse import urlencode
+from nio import MatrixRoom
from modules.common.module import BotModule
-tautulli_path = os.getenv("TAUTULLI_PATH")
+import nest_asyncio
+nest_asyncio.apply()
-def load_tzlocal():
- try:
- global tautulli_path
+rooms = dict()
+global_bot = None
- sys.path.insert(0, tautulli_path)
- sys.path.insert(0, "{}/lib".format(tautulli_path))
- module = importlib.import_module("tzlocal")
- module = reload(module)
- return module
- except ModuleNotFoundError:
+
+async def send_entry(blob, content_type, fmt_params, rooms):
+ for room_id in rooms:
+ room = MatrixRoom(room_id=room_id, own_user_id=os.getenv("BOT_OWNERS"),
+ encrypted=rooms[room_id])
+ if blob and content_type:
+ await global_bot.upload_and_send_image(room, blob, text="", blob=True, blob_content_type=content_type)
+
+ await global_bot.send_html(room, msg_template_html.format(**fmt_params),
+ msg_template_plain.format(**fmt_params))
+
+
+def get_image(img=None, width=1000, height=1500):
+ """
+ Return image data as array.
+ Array contains the image content type and image binary
+
+ Parameters required: img { Plex image location }
+ Optional parameters: width { the image width }
+ height { the image height }
+ Output: array
+ """
+
+ pms_url = os.getenv("PLEX_MEDIA_SERVER_URL")
+ pms_token = os.getenv("PLEX_MEDIA_SERVER_TOKEN")
+ if not pms_url or not pms_token:
return None
-def load_tautulli():
- try:
- global tautulli_path
+ width = width or 1000
+ height = height or 1500
- sys.path.insert(0, tautulli_path)
- sys.path.insert(0, "{}/lib".format(tautulli_path))
- module = importlib.import_module("plexpy")
- module = reload(module)
- return module
- except ModuleNotFoundError:
- return None
+ if img:
+ params = {'url': 'http://127.0.0.1:32400%s' % (img), 'width': width, 'height': height, 'format': "png"}
-plexpy = load_tautulli()
-tzlocal = load_tzlocal()
+ uri = pms_url + '/photo/:/transcode?%s' % urlencode(params)
-send_entry_lock = asyncio.Lock()
+ headers = {'X-Plex-Token': pms_token}
-async def send_entry(bot, room, entry):
- global send_entry_lock
- async with send_entry_lock:
- if "art" in entry:
- global plexpy
- if plexpy:
- pms = plexpy.pmsconnect.PmsConnect()
- pms_image = pms.get_image(entry["art"], 600, 300)
- if pms_image:
- (blob, content_type) = pms_image
- await bot.upload_and_send_image(room, blob, "", True, content_type)
+ session = requests.Session()
+ try:
+ r = session.request("GET", uri, headers=headers)
+ r.raise_for_status()
+ except Exception:
+ return None
- fmt_params = {
- "title": entry["title"],
- "year": entry["year"],
- "audience_rating": entry["audience_rating"],
- "directors": ", ".join(entry["directors"]),
- "actors": ", ".join(entry["actors"]),
- "summary": entry["summary"],
- "tagline": entry["tagline"],
- "genres": ", ".join(entry["genres"])
- }
+ response_status = r.status_code
+ response_content = r.content
+ response_headers = r.headers
+ if response_status in (200, 201):
+ return response_content, response_headers['Content-Type']
+
+
+def get_from_entry(entry):
+ blob = None
+ content_type = ""
+ if "art" in entry:
+ pms_image = get_image(entry["art"], 600, 300)
+ if pms_image:
+ (blob, content_type) = pms_image
+
+ fmt_params = {
+ "title": entry["title"],
+ "year": entry["year"],
+ "audience_rating": entry["audience_rating"],
+ "directors": ", ".join(entry["directors"]),
+ "actors": ", ".join(entry["actors"]),
+ "summary": entry["summary"],
+ "tagline": entry["tagline"],
+ "genres": ", ".join(entry["genres"])
+ }
+
+ return (blob, content_type, fmt_params)
- await bot.send_html(room,
- msg_template_html.format(**fmt_params),
- msg_template_plain.format(**fmt_params))
msg_template_html = """
{title} -({year})- Rating: {audience_rating}
@@ -94,14 +111,12 @@ msg_template_plain = """*{title} -({year})- Rating: {audience_rating}*
"""
-class WebServer:
- bot = None
- rooms = dict()
+class WebServer:
def __init__(self, host, port):
- self.app = web.Application()
self.host = host
self.port = port
+ self.app = web.Application()
self.app.router.add_post('/notify', self.notify)
async def run(self):
@@ -126,9 +141,9 @@ class WebServer:
if "directors" in data:
data["directors"] = data["directors"].split(",")
- for room_id in self.rooms:
- room = MatrixRoom(room_id=room_id, own_user_id=os.getenv("BOT_OWNERS"), encrypted=self.rooms[room_id])
- await send_entry(self.bot, room, data)
+ global rooms
+ (blob, content_type, fmt_params) = get_from_entry(data)
+ await send_entry(blob, content_type, fmt_params, rooms)
except Exception as exc:
message = str(exc)
@@ -136,6 +151,7 @@ class WebServer:
return web.Response()
+
class MatrixModule(BotModule):
httpd = None
rooms = dict()
@@ -143,29 +159,17 @@ class MatrixModule(BotModule):
def __init__(self, name):
super().__init__(name)
- global plexpy
- if plexpy:
- global tautulli_path
- plexpy.FULL_PATH = "{}/Tautulli.py".format(tautulli_path)
- plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
- plexpy.DATA_DIR = tautulli_path
- plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, plexpy.database.FILENAME)
-
- try:
- plexpy.SYS_TIMEZONE = tzlocal.get_localzone()
- except (pytz.UnknownTimeZoneError, LookupError, ValueError) as e:
- plexpy.SYS_TIMEZONE = pytz.UTC
-
- plexpy.SYS_UTC_OFFSET = datetime.datetime.now(plexpy.SYS_TIMEZONE).strftime('%z')
- plexpy.initialize("{}/config.ini".format(tautulli_path))
-
self.httpd = WebServer(os.getenv("TAUTULLI_NOTIFIER_ADDR"), os.getenv("TAUTULLI_NOTIFIER_PORT"))
- loop = asyncio.get_event_loop()
- loop.run_until_complete(self.httpd.run())
def matrix_start(self, bot):
super().matrix_start(bot)
- self.httpd.bot = bot
+ global global_bot
+ global_bot = bot
+ loop = asyncio.get_event_loop()
+ loop.run_until_complete(self.httpd.run())
+
+ def matrix_stop(self, bot):
+ super().matrix_stop(bot)
async def matrix_message(self, bot, room, event):
args = event.body.split()
@@ -183,7 +187,7 @@ class MatrixModule(BotModule):
try:
url = "{}/api/v2?apikey={}&cmd=get_recently_added&count=10".format(os.getenv("TAUTULLI_URL"), self.api_key)
- req = urllib.request.Request(url+"&media_type="+media_type)
+ req = urllib.request.Request(url + "&media_type=" + media_type)
connection = urllib.request.urlopen(req).read()
entries = json.loads(connection)
if "response" not in entries and "data" not in entries["response"] and "recently_added" not in entries["response"]["data"]:
@@ -191,7 +195,8 @@ class MatrixModule(BotModule):
return
for entry in entries["response"]["data"]["recently_added"]:
- await send_entry(bot, room, entry)
+ (blob, content_type, fmt_params) = get_from_entry(entry)
+ await send_entry(blob, content_type, fmt_params, {room.room_id: room})
except urllib.error.HTTPError as err:
raise ValueError(err.read())
@@ -210,7 +215,8 @@ class MatrixModule(BotModule):
await bot.send_text(room, f"Removed {room_id} to rooms notification list")
bot.save_settings()
- self.httpd.rooms = self.rooms
+ global rooms
+ rooms = self.rooms
else:
await bot.send_text(room, 'Usage: !tautulli | %room_id% %encrypted%')
else:
@@ -220,16 +226,19 @@ class MatrixModule(BotModule):
data = super().get_settings()
data["api_key"] = self.api_key
data["rooms"] = self.rooms
- self.httpd.rooms = self.rooms
+ global rooms
+ rooms = self.rooms
return data
def set_settings(self, data):
super().set_settings(data)
if data.get("rooms"):
self.rooms = data["rooms"]
- self.httpd.rooms = self.rooms
+ global rooms
+ rooms = self.rooms
if data.get("api_key"):
self.api_key = data["api_key"]
def help(self):
return ('Tautulli recently added bot')
+