implement plexamp headless claim endpoint
This commit is contained in:
parent
45f0756191
commit
a7492c152d
@ -5,9 +5,13 @@ subscription.
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- A router that can redirect traffic (i.e. OPNsense, pfSense, DD-WRT...)
|
- A router that **can redirect traffic** (i.e. OPNsense, pfSense, DD-WRT...)
|
||||||
- (alternative) a DNS server that can redirect traffic *(some apps won't work due to DNS pinning)*
|
- _(alternative) a DNS server that can redirect traffic (some apps won't work due to DNS pinning)_
|
||||||
- A reverse proxy (i.e. Traefik, Nginx, Caddy...)
|
- A reverse proxy (i.e. Traefik, Nginx, Caddy...)
|
||||||
|
- A Plex server (self-hosted)
|
||||||
|
|
||||||
|
### What works?
|
||||||
|
- PlexAmp mobile (download mode)
|
||||||
|
|
||||||
## How to setup ?
|
## How to setup ?
|
||||||
|
|
||||||
|
110
const.py
110
const.py
@ -1,4 +1,5 @@
|
|||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timezone, timedelta
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
OFFICIAL_API = "https://clients.plex.tv"
|
OFFICIAL_API = "https://clients.plex.tv"
|
||||||
|
|
||||||
@ -299,6 +300,113 @@ PLEXAMP_FEATURES = [
|
|||||||
"webhooks"
|
"webhooks"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
PLEXAMP_SUBSCRIPTION_XML = ET.fromstring("""
|
||||||
|
<subscription active="1" status="Active" plan="">
|
||||||
|
<feature id="guided-upgrade" />
|
||||||
|
<feature id="increase-password-complexity" />
|
||||||
|
<feature id="upgrade-3ds2" />
|
||||||
|
<feature id="ad-countdown-timer" />
|
||||||
|
<feature id="adaptive_bitrate" />
|
||||||
|
<feature id="amazon-loop-debug" />
|
||||||
|
<feature id="Android - Dolby Vision" />
|
||||||
|
<feature id="Android - PiP" />
|
||||||
|
<feature id="avod-ad-analysis" />
|
||||||
|
<feature id="avod-new-media" />
|
||||||
|
<feature id="blacklist_get_signin" />
|
||||||
|
<feature id="camera_upload" />
|
||||||
|
<feature id="CU Sunset" />
|
||||||
|
<feature id="client-radio-stations" />
|
||||||
|
<feature id="cloudsync" />
|
||||||
|
<feature id="cloudflare-turnstile-required" />
|
||||||
|
<feature id="common-sense-media-ratings-premium" />
|
||||||
|
<feature id="comments_and_replies_push_notifications" />
|
||||||
|
<feature id="friend_request_push_notifications" />
|
||||||
|
<feature id="community_access_plex_tv" />
|
||||||
|
<feature id="community_friends_group_notifications" />
|
||||||
|
<feature id="companions_sonos" />
|
||||||
|
<feature id="content_filter" />
|
||||||
|
<feature id="custom-home-removal" />
|
||||||
|
<feature id="grandfather-sync" />
|
||||||
|
<feature id="disable-facebook-auth" />
|
||||||
|
<feature id="disable_home_user_friendships" />
|
||||||
|
<feature id="disable_sharing_friendships" />
|
||||||
|
<feature id="downloads-gating" />
|
||||||
|
<feature id="drm_support" />
|
||||||
|
<feature id="dvr" />
|
||||||
|
<feature id="dvr-block-unsupported-countries" />
|
||||||
|
<feature id="le_isrg_root_x1" />
|
||||||
|
<feature id="epg-recent-channels" />
|
||||||
|
<feature id="federated-auth" />
|
||||||
|
<feature id="global-continue-watching" />
|
||||||
|
<feature id="hwtranscode" />
|
||||||
|
<feature id="hardware_transcoding" />
|
||||||
|
<feature id="home" />
|
||||||
|
<feature id="HRK_enable_EUR" />
|
||||||
|
<feature id="imagga-v2" />
|
||||||
|
<feature id="ios14-privacy-banner" />
|
||||||
|
<feature id="item_clusters" />
|
||||||
|
<feature id="iterable-notification-tokens" />
|
||||||
|
<feature id="keep-payment-method" />
|
||||||
|
<feature id="kevin-bacon" />
|
||||||
|
<feature id="lets_encrypt" />
|
||||||
|
<feature id="lightning-dvr-pivot" />
|
||||||
|
<feature id="livetv" />
|
||||||
|
<feature id="allow_dvr" />
|
||||||
|
<feature id="live-tv-support-incomplete-segments" />
|
||||||
|
<feature id="tuner-sharing" />
|
||||||
|
<feature id="lyrics" />
|
||||||
|
<feature id="metadata_search" />
|
||||||
|
<feature id="vod_cloudflare" />
|
||||||
|
<feature id="music_videos" />
|
||||||
|
<feature id="new_plex_pass_prices" />
|
||||||
|
<feature id="news-provider-sunset-modal" />
|
||||||
|
<feature id="nominatim" />
|
||||||
|
<feature id="pass" />
|
||||||
|
<feature id="photos-favorites" />
|
||||||
|
<feature id="photos-metadata-edition" />
|
||||||
|
<feature id="photosV6-edit" />
|
||||||
|
<feature id="photosV6-tv-albums" />
|
||||||
|
<feature id="pms_health" />
|
||||||
|
<feature id="premium-dashboard" />
|
||||||
|
<feature id="premium_music_metadata" />
|
||||||
|
<feature id="rate-limit-client-token" />
|
||||||
|
<feature id="reactions_push_notifications_settings" />
|
||||||
|
<feature id="shared_server_notification" />
|
||||||
|
<feature id="shared_source_notification" />
|
||||||
|
<feature id="redirect-subscription-to-account-page" />
|
||||||
|
<feature id="scrobbling-service-plex-tv" />
|
||||||
|
<feature id="album-types" />
|
||||||
|
<feature id="collections" />
|
||||||
|
<feature id="music-analysis" />
|
||||||
|
<feature id="radio" />
|
||||||
|
<feature id="session_bandwidth_restrictions" />
|
||||||
|
<feature id="session_kick" />
|
||||||
|
<feature id="exclude restrictions" />
|
||||||
|
<feature id="signin_notification" />
|
||||||
|
<feature id="signin_with_apple" />
|
||||||
|
<feature id="skip-data-licensing-consent" />
|
||||||
|
<feature id="sleep-timer" />
|
||||||
|
<feature id="spring_serve_ad_provider" />
|
||||||
|
<feature id="sync" />
|
||||||
|
<feature id="trailers" />
|
||||||
|
<feature id="transcoder_cache" />
|
||||||
|
<feature id="boost-voices" />
|
||||||
|
<feature id="TREBLE-show-features" />
|
||||||
|
<feature id="silence-removal" />
|
||||||
|
<feature id="sweet-fades" />
|
||||||
|
<feature id="visualizers" />
|
||||||
|
<feature id="volume-leveling" />
|
||||||
|
<feature id="two-factor-authentication" />
|
||||||
|
<feature id="unsupportedtuners" />
|
||||||
|
<feature id="plexpass_from_billing_context" />
|
||||||
|
<feature id="vod-schema" />
|
||||||
|
<feature id="watch-together-invite" />
|
||||||
|
<feature id="watchlist-rss" />
|
||||||
|
<feature id="web_server_dashboard" />
|
||||||
|
<feature id="webhooks" />
|
||||||
|
</subscription>
|
||||||
|
""")
|
||||||
|
|
||||||
ENTITLEMENTS = ["deprecated_google_iap_activation", "all", "roku", "android", "xbox_one", "xbox_360", "windows",
|
ENTITLEMENTS = ["deprecated_google_iap_activation", "all", "roku", "android", "xbox_one", "xbox_360", "windows",
|
||||||
"windows_phone", "ios"]
|
"windows_phone", "ios"]
|
||||||
|
|
||||||
@ -309,4 +417,4 @@ CURRENT_DATE_Z = (datetime.now(timezone.utc)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|||||||
CURRENT_DATE_PLUS_30MIN_Z = (datetime.now(timezone.utc) + timedelta(minutes=30)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
CURRENT_DATE_PLUS_30MIN_Z = (datetime.now(timezone.utc) + timedelta(minutes=30)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
TIMESTAMP_CURRENT = int(datetime.now().timestamp())
|
TIMESTAMP_CURRENT = int(datetime.now().timestamp())
|
||||||
TIMESTAMP_CURRENT_MINUS_30MIN = int((datetime.now() - timedelta(minutes=30)).timestamp())
|
TIMESTAMP_CURRENT_MINUS_30MIN = int((datetime.now() - timedelta(minutes=30)).timestamp())
|
||||||
TIMESTAMP_CURRENT_PLUS_30DAYS = int((datetime.now() + timedelta(days=30)).timestamp())
|
TIMESTAMP_CURRENT_PLUS_30DAYS = int((datetime.now() + timedelta(days=30)).timestamp())
|
||||||
|
39
main_app.py
39
main_app.py
@ -8,7 +8,7 @@ from starlette.responses import JSONResponse
|
|||||||
from client import AsyncCustomHost, NameSolver
|
from client import AsyncCustomHost, NameSolver
|
||||||
from const import OFFICIAL_API, FEATURES_DICT, ENTITLEMENTS, CURRENT_DATE_MINUS_ONE_DAY, FEATURES, \
|
from const import OFFICIAL_API, FEATURES_DICT, ENTITLEMENTS, CURRENT_DATE_MINUS_ONE_DAY, FEATURES, \
|
||||||
CURRENT_DATE_MINUS_ONE_DAY_Z, NEXT_MONTH_DATE_Z, PLEXAMP_FEATURES, TIMESTAMP_CURRENT_PLUS_30DAYS, \
|
CURRENT_DATE_MINUS_ONE_DAY_Z, NEXT_MONTH_DATE_Z, PLEXAMP_FEATURES, TIMESTAMP_CURRENT_PLUS_30DAYS, \
|
||||||
TIMESTAMP_CURRENT_MINUS_30MIN
|
TIMESTAMP_CURRENT_MINUS_30MIN, PLEXAMP_SUBSCRIPTION_XML
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
@ -266,6 +266,7 @@ async def fake_user_signin(request: Request):
|
|||||||
|
|
||||||
return await return_edited_response(upstream_response, data_override)
|
return await return_edited_response(upstream_response, data_override)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v2/user.json")
|
@app.get("/api/v2/user.json")
|
||||||
async def fake_get_user_json(request: Request):
|
async def fake_get_user_json(request: Request):
|
||||||
upstream_response = await call_official(request, request.url.path.lstrip("/"))
|
upstream_response = await call_official(request, request.url.path.lstrip("/"))
|
||||||
@ -317,6 +318,42 @@ async def fake_get_user_json(request: Request):
|
|||||||
return await return_edited_response(upstream_response, data_override)
|
return await return_edited_response(upstream_response, data_override)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/claim/exchange")
|
||||||
|
async def fake_claim_exchange(request: Request):
|
||||||
|
upstream_response = await call_official(request, request.url.path.lstrip("/"))
|
||||||
|
|
||||||
|
data_override = None
|
||||||
|
|
||||||
|
content_type = upstream_response.headers.get("content-type", "")
|
||||||
|
if content_type.startswith("application/xml"):
|
||||||
|
data = ET.fromstring(upstream_response.content)
|
||||||
|
|
||||||
|
subscription = data.find("subscription")
|
||||||
|
if subscription is not None:
|
||||||
|
parent = data
|
||||||
|
parent.remove(subscription)
|
||||||
|
parent.append(PLEXAMP_SUBSCRIPTION_XML)
|
||||||
|
|
||||||
|
# --- Replace <entitlements> ---
|
||||||
|
for ents in data.findall("entitlements"):
|
||||||
|
data.remove(ents)
|
||||||
|
entitlements = ET.SubElement(data, "entitlements", {"all": "1"})
|
||||||
|
for entitlement in ENTITLEMENTS:
|
||||||
|
ET.SubElement(entitlements, "entitlement", {"id": entitlement})
|
||||||
|
|
||||||
|
# --- Replace <roles> ---
|
||||||
|
for roles in data.findall("roles"):
|
||||||
|
data.remove(roles)
|
||||||
|
roles = ET.SubElement(data, "roles")
|
||||||
|
ET.SubElement(roles, "role", {"id": "plexpass"})
|
||||||
|
|
||||||
|
# Pretty print XML
|
||||||
|
ET.indent(data)
|
||||||
|
|
||||||
|
data_override = ET.tostring(data, encoding="utf-8", method="xml")
|
||||||
|
|
||||||
|
return await return_edited_response(upstream_response, data_override)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/hack")
|
@app.get("/api/hack")
|
||||||
async def hack_endpoint():
|
async def hack_endpoint():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user