Initial commit. Movies work, TV and music do not
This commit is contained in:
parent
393584bf8f
commit
43f0204a68
|
@ -0,0 +1,5 @@
|
|||
config.conf
|
||||
*.jpg
|
||||
*~
|
||||
#*
|
||||
bartonfink.png
|
|
@ -0,0 +1,215 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import slixmpp
|
||||
import requests
|
||||
import json
|
||||
import datetime
|
||||
import time
|
||||
import configparser
|
||||
import os
|
||||
import threading
|
||||
from jellyfin_apiclient_python import JellyfinClient
|
||||
import uuid
|
||||
|
||||
def json_to_key_value_string(json_data):
|
||||
# Parse JSON data
|
||||
data = json.loads(json_data)
|
||||
|
||||
# Initialize an empty list to store key-value pairs
|
||||
result_lines = []
|
||||
|
||||
# Recursively process the JSON data
|
||||
def process_item(item, prefix=''):
|
||||
if isinstance(item, dict):
|
||||
for key, value in item.items():
|
||||
if isinstance(value, (dict, list)):
|
||||
process_item(value, f"{prefix}{key}.")
|
||||
else:
|
||||
result_lines.append(f"{prefix}{key}: {value}")
|
||||
elif isinstance(item, list):
|
||||
for i, value in enumerate(item):
|
||||
process_item(value, f"{prefix}[{i}].")
|
||||
|
||||
process_item(data)
|
||||
|
||||
return "\n".join(result_lines)
|
||||
|
||||
class QBBot(slixmpp.ClientXMPP):
|
||||
def __init__(self, jid, password, room, nick, api_url, api_username, api_password):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.room = room
|
||||
self.nick = nick
|
||||
|
||||
|
||||
# Initialize Jellyfin caller
|
||||
self.jf_api = None
|
||||
self.jf_api_url = api_url
|
||||
self.jf_api_username = api_username
|
||||
self.jf_api_password = api_password
|
||||
self.jf_api_credentials = None
|
||||
self.jf_api_init()
|
||||
self.jf_api_login()
|
||||
|
||||
|
||||
|
||||
self.add_event_handler("session_start", self.session_start)
|
||||
self.add_event_handler("message", self.message)
|
||||
# The groupchat_message event is triggered whenever a message
|
||||
# stanza is received from any chat room. If you also also
|
||||
# register a handler for the 'message' event, MUC messages
|
||||
# will be processed by both handlers.
|
||||
self.add_event_handler("groupchat_message", self.muc_message)
|
||||
|
||||
# If you wanted more functionality, here's how to register plugins:
|
||||
# self.register_plugin('xep_0030') # Service Discovery
|
||||
# self.register_plugin('xep_0199') # XMPP Ping
|
||||
|
||||
# Here's how to access plugins once you've registered them:
|
||||
# self['xep_0030'].add_feature('echo_demo')
|
||||
|
||||
def jf_api_init(self):
|
||||
if self.jf_api is not None:
|
||||
print("Warning: API already initialized, doing nothing")
|
||||
return
|
||||
|
||||
self.jf_api = JellyfinClient()
|
||||
self.jf_api.config.app('xmpp-jellyfin-bot', '0.0.1', 'xmpp-jellyfin-bot', uuid.getnode())
|
||||
self.jf_api.config.data["auth.ssl"] = False
|
||||
|
||||
def jf_api_login(self):
|
||||
if self.jf_api_credentials is None:
|
||||
self.jf_api.auth.connect_to_address(self.jf_api_url)
|
||||
self.jf_api.auth.login(self.jf_api_url, self.jf_api_username, self.jf_api_password)
|
||||
self.jf_api_credentials = self.jf_api.auth.credentials.get_credentials()
|
||||
|
||||
else:
|
||||
self.jf_api.authenticate({"Servers": [self.jf_api_credentials]}, discover=False)
|
||||
|
||||
|
||||
|
||||
|
||||
async def session_start(self, event):
|
||||
await self.get_roster()
|
||||
self.send_presence()
|
||||
self.plugin['xep_0045'].join_muc(self.room, self.nick)
|
||||
avatar_file = "./bartonfink.png"
|
||||
try:
|
||||
avatar_file = open(avatar_file, 'rb')
|
||||
except IOError:
|
||||
logging.error("Could not find avatar file")
|
||||
return self.disconnect()
|
||||
|
||||
avatar = avatar_file.read()
|
||||
avatar_type = 'image/png'
|
||||
avatar_id = self['xep_0084'].generate_id(avatar)
|
||||
avatar_bytes = len(avatar)
|
||||
avatar_file.close()
|
||||
|
||||
used_xep84 = False
|
||||
|
||||
logging.info('Publish XEP-0084 avatar data')
|
||||
result = await self['xep_0084'].publish_avatar(avatar)
|
||||
if isinstance(result, slixmpp.exceptions.XMPPError):
|
||||
logging.warning('Could not publish XEP-0084 avatar')
|
||||
else:
|
||||
used_xep84 = True
|
||||
|
||||
logging.info('Update vCard with avatar')
|
||||
result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
||||
if isinstance(result, slixmpp.exceptions.XMPPError):
|
||||
print('Could not set vCard avatar')
|
||||
|
||||
if used_xep84:
|
||||
logging.info('Advertise XEP-0084 avatar metadata')
|
||||
result = await self['xep_0084'].publish_avatar_metadata([
|
||||
{'id': avatar_id,
|
||||
'type': avatar_type,
|
||||
'bytes': avatar_bytes}
|
||||
# We could advertise multiple avatars to provide
|
||||
# options in image type, source (HTTP vs pubsub),
|
||||
# size, etc.
|
||||
# {'id': ....}
|
||||
])
|
||||
if isinstance(result, slixmpp.exceptions.XMPPError):
|
||||
logging.warning('Could not publish XEP-0084 metadata')
|
||||
|
||||
logging.info('Wait for presence updates to propagate...')
|
||||
#self.schedule('end', 5, self.disconnect, kwargs={'wait': True})
|
||||
|
||||
# Most get_*/set_* methods from plugins use Iq stanzas, which
|
||||
# are sent asynchronously. You can almost always provide a
|
||||
# callback that will be executed when the reply is received.
|
||||
|
||||
def message(self, msg):
|
||||
if msg['type'] in ('chat', 'normal'):
|
||||
msg.reply("I was not designed to take direct messages, please use me in a MUC").send()
|
||||
|
||||
def muc_message(self, msg):
|
||||
# TODO
|
||||
if msg['mucnick'] != self.nick and msg['body'].startswith(self.nick):
|
||||
message = "```\n"
|
||||
tokens = msg['body'].split()
|
||||
match tokens[1]:
|
||||
case "help":
|
||||
message += "XMPP bot to query a Jellyfin server for content\n"
|
||||
message += "Please use this bot first if you want to download something but are not sure if it's on the server\n"
|
||||
message += "Commands\n"
|
||||
message += "help: Displays this help\n"
|
||||
message += "search CATEGORY SEARCH_QUERY: Search CATEGORY for SEARCH_QUERY on the Jellyfin server\n"
|
||||
case "search":
|
||||
category = tokens[2]
|
||||
category = category.lower()
|
||||
if category not in ["movie", "tv", "music"]:
|
||||
message += "Error: invalid category " + tokens[2]
|
||||
else:
|
||||
search_query = " ".join(tokens[3:])
|
||||
query = self.jf_api.jellyfin.search_media_items(term=search_query, media=category)
|
||||
items = query['Items']
|
||||
items = [i for i in items if i['MediaType'] != "Unknown"]
|
||||
if len(items) == 0:
|
||||
message += f"{search_query} not found in category {category}"
|
||||
match category:
|
||||
case "movie":
|
||||
for i in items:
|
||||
message += f"{i['Name']}, {i['ProductionYear']}, {i['OfficialRating'] if 'OfficialRating' in i else "unknown rating"}, {i['Container']}\n"
|
||||
|
||||
message += "```"
|
||||
|
||||
self.send_message(mto=msg['from'].bare,
|
||||
mbody=message,
|
||||
mtype='groupchat')
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Ideally use optparse or argparse to get JID,
|
||||
# password, and log level.
|
||||
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
if not os.path.exists("./config.conf"):
|
||||
logging.error("Could not find ./config.conf, please ensure it exists")
|
||||
exit
|
||||
|
||||
config.read('config.conf')
|
||||
config_default = config['DEFAULT']
|
||||
xmpp = QBBot(config_default['jf_jid'].strip('\"'),
|
||||
config_default['jf_pass'].strip('\"'),
|
||||
config_default['jf_muc'].strip('\"'),
|
||||
config_default['jf_nick'].strip('\"'),
|
||||
config_default['api_url'].strip('\"'),
|
||||
config_default['api_user'].strip('\"'),
|
||||
config_default['api_pass'].strip('\"'))
|
||||
|
||||
|
||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||
xmpp.register_plugin('xep_0045') # Multi-User Chat
|
||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
||||
xmpp.register_plugin('xep_0084') # vCard avatar
|
||||
xmpp.register_plugin('xep_0153') # Something required for vCard
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
|
@ -0,0 +1,8 @@
|
|||
[DEFAULT]
|
||||
qb_jid = # JID for the bot
|
||||
qb_pass = # Password for the bot
|
||||
qb_muc = # MUC the bot should connect to
|
||||
qb_nick = # Nickname for the bot in the MUC
|
||||
api_url = # URL for the qbittorrent api we are connecting to
|
||||
api_user = # Username for the qbittorrent api
|
||||
api_pass = # Password for the qbittorent api
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
Loading…
Reference in New Issue