Skip to content

Instantly share code, notes, and snippets.

@andyshinn
Created November 29, 2023 17:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andyshinn/e2f5812d50a5417fb077f4109d5dd4d5 to your computer and use it in GitHub Desktop.
Save andyshinn/e2f5812d50a5417fb077f4109d5dd4d5 to your computer and use it in GitHub Desktop.
from flask import Flask, request, send_file, jsonify
import hashlib
import requests
import json
from PIL import Image
from io import BytesIO
from geolite2 import geolite2
app = Flask(__name__)
# Configuration
appUrl = 'https://plex-discord-webhook.herokuapp.com'
webhookKey = 'YOUR_DISCORD_WEBHOOK_KEY'
def format_title(metadata):
if metadata.get('grandparentTitle'):
return metadata['grandparentTitle']
else:
ret = metadata['title']
if metadata.get('year'):
ret += f" ({metadata['year']})"
return ret
def format_subtitle(metadata):
ret = ''
if metadata.get('grandparentTitle'):
if metadata['type'] == 'track':
ret = metadata['parentTitle']
elif metadata.get('index') and metadata.get('parentIndex'):
ret = f"S{metadata['parentIndex']} E{metadata['index']}"
elif metadata.get('originallyAvailableAt'):
ret = metadata['originallyAvailableAt']
if metadata.get('title'):
ret += f" - {metadata['title']}"
elif metadata['type'] == 'movie':
ret = metadata.get('tagline', '')
return ret
def format_summary(summary):
ret = ''
if summary and len(summary):
if len(summary) > 300:
ret += summary[:300] + '...'
else:
ret += summary
if ret:
ret = f"\r\n\r\n{ret}"
return ret
def notify_discord(image_url, payload, location, action):
location_text = ''
if location:
if location.get('city'):
location_text = f" near {location['city']}, {location.get('region_name', location['country_name'])}"
else:
location_text = f", {location.get('region_name', location['country_name'])}"
data = {
"content": "",
"username": "Plex",
"avatar_url": f"{appUrl}/plex-icon.png",
"embeds": [
{
"title": format_title(payload['Metadata']),
"description": f"{format_subtitle(payload['Metadata'])}{format_summary(payload['Metadata']['summary'])}",
"footer": {
"text": f"{action} by {payload['Account']['title']} on {payload['Player']['title']} from {payload['Server']['title']} {location_text}",
"icon_url": payload['Account']['thumb']
},
"thumbnail": {
"url": image_url,
"height": 200,
"width": 200
}
}
]
}
requests.post(f"https://discordapp.com/api/webhooks/{webhookKey}", json=data)
@app.route('/', methods=['POST'])
def handle_payload():
payload = json.loads(request.form['payload'])
is_video = payload['Metadata']['librarySectionType'] in ['movie', 'show']
is_audio = payload['Metadata']['librarySectionType'] == 'artist'
if payload.get('user') and payload.get('Metadata') and (is_audio or is_video):
key = hashlib.sha1((payload['Server']['uuid'] + payload['Metadata']['guid']).encode()).hexdigest()
if payload['event'] in ['media.play', 'media.rate']:
# Save the image
if 'thumb' in request.files:
image_data = request.files['thumb'].read()
image = Image.open(BytesIO(image_data))
image.thumbnail((75, 75))
buffer = BytesIO()
image.save(buffer, format="JPEG")
buffer.seek(0)
redis_client.setex(key, 7 * 24 * 60 * 60, buffer.read())
if (payload['event'] == 'media.scrobble' and is_video) or payload['event'] in ['media.rate', 'media.play']:
# Geolocate player
reader = geolite2.reader()
match = geolite2.lookup(payload['Player']['publicAddress'])
geolite2.close()
location = match.location if match else None
action = ''
if payload['event'] == 'media.scrobble' or payload['event'] == 'media.play':
action = 'played'
elif payload['event'] == 'media.rate':
if payload.get('rating', 0) > 0:
action = 'rated ' + '★' * (payload['rating'] // 2)
else:
action = 'unrated'
# Send the event to Discord
image_url = f"{appUrl}/images/{key}" if redis_client.get(key) else None
if not location or (location and location.get('city') and len(location['city']) > 1):
notify_discord(image_url, payload, location, action)
else:
print('Location city missing, trying OSM lat lng lookup')
response = requests.get(f"http://nominatim.openstreetmap.org/reverse?format=json&lat={location['latitude']}&lon={location['longitude']}&accept-language=en", headers={'User-Agent': 'hoglund.joakim@gmail.com'})
if response.status_code == 200:
location = response.json().get('address')
location['region_name'] = location.get('state')
location['country_name'] = location.get('country')
if not image_url:
notify_discord(None, payload, location, action)
else:
notify_discord(image_url, payload, location, action)
return '', 200
@app.route('/images/<key>')
def get_image(key):
image_data = redis_client.get(key)
if image_data:
return send_file(BytesIO(image_data), mimetype='image/jpeg')
else:
return '', 404
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 11000)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment