Merge pull request #213 from aspacca/fix_double_listening

fix Bot running twice and refactor tautulli module
This commit is contained in:
Ville Ranki 2022-08-16 22:43:55 +03:00 committed by GitHub
commit 06a242470b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 121 additions and 109 deletions

View File

@ -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

16
bot.py
View File

@ -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)

View File

@ -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:

View File

@ -0,0 +1,14 @@
# Couple of custom exceptions
class UploadFailed(Exception):
pass
class CommandRequiresAdmin(Exception):
pass
class CommandRequiresOwner(Exception):
pass

View File

@ -5,7 +5,6 @@ import sys
import traceback
import cups
import httpx
import asyncio
import aiofiles
import os

View File

@ -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 = """
<b>{title} -({year})- Rating: {audience_rating}</b><br>
@ -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 <movie|show|artist>|<add|remove> %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')