main
  1#!/usr/bin/env python
  2# -*- coding: utf-8 -*-
  3import asyncio
  4
  5from httpx import AsyncHTTPTransport
  6from loguru import logger
  7
  8from emby.constant import DEVICE_ID, DEVICE_NAME, DEVICE_PROFILE, EMBY_PROXY, HEADERS, VERSION
  9from networking import hx_req
 10
 11
 12def default_headers(credentials: dict) -> dict:
 13    return HEADERS | {"Authorization": f'MediaBrowser Client="Hills Windows", Device="{DEVICE_NAME}", DeviceId="{DEVICE_ID}", Version="{VERSION}", Token="{credentials["AccessToken"]}"'}
 14
 15
 16def build_params(credentials: dict, params: dict | None = None, **kwargs) -> dict:
 17    if params is None:
 18        params = {}
 19    params |= {
 20        "X-Emby-Authorization": f'Emby Client="Hills Windows", Device="{DEVICE_NAME}", DeviceId="{DEVICE_ID}", Version="{VERSION}"',
 21        "X-Emby-Client": "Hills Windows",
 22        "X-Emby-Device-Name": DEVICE_NAME,
 23        "X-Emby-Device-Id": DEVICE_ID,
 24        "X-Emby-Client-Version": VERSION,
 25        "X-Emby-Language": "zh-cn",
 26    }
 27    if credentials.get("AccessToken"):
 28        params |= {"X-Emby-Token": credentials["AccessToken"]}
 29    return params | kwargs
 30
 31
 32async def get_user(credentials: dict) -> dict:
 33    uid = credentials["User"]["Id"]
 34    return await hx_req(
 35        f"{credentials['Server']}/emby/Users/{uid}",
 36        headers=default_headers(credentials),
 37        params=build_params(credentials),
 38        transport=AsyncHTTPTransport(),
 39        proxy=EMBY_PROXY,
 40        verify=False,
 41        timeout=5,
 42        check_kv={"Id": uid},
 43        silent=True,
 44        max_retry=0,
 45    )
 46
 47
 48async def get_resume(credentials: dict) -> dict:
 49    uid = credentials["User"]["Id"]
 50    params = {
 51        "Fields": "ProductionYear,EndDate,Status,ProviderIds",
 52        "ImageTypeLimit": 1,
 53        "MediaTypes": "Video",
 54        "ParentId": "",
 55        "Limit": 50,
 56        "StartIndex": 0,
 57        "X-Emby-Token": credentials["AccessToken"],
 58    }
 59    return await hx_req(
 60        f"{credentials['Server']}/emby/Users/{uid}/Items/Resume",
 61        headers=default_headers(credentials),
 62        params=build_params(credentials, params),
 63        transport=AsyncHTTPTransport(),
 64        proxy=EMBY_PROXY,
 65        verify=False,
 66        timeout=5,
 67        check_keys=["Items"],
 68        silent=True,
 69        max_retry=1,
 70    )
 71
 72
 73async def get_item(credentials: dict, item_id: str | int) -> dict:
 74    uid = credentials["User"]["Id"]
 75    params = {
 76        "EnableImageTypes": "Primary,Backdrop,Thumb,Logo",
 77        "ImageTypeLimit": 1,
 78        "Fields": "ProviderIds,ExternalUrls",
 79    }
 80    return await hx_req(
 81        f"{credentials['Server']}/emby/Users/{uid}/Items/{item_id}",
 82        headers=default_headers(credentials),
 83        params=build_params(credentials, params),
 84        transport=AsyncHTTPTransport(),
 85        proxy=EMBY_PROXY,
 86        verify=False,
 87        timeout=5,
 88        check_kv={"Id": item_id},
 89        silent=True,
 90        max_retry=1,
 91    )
 92
 93
 94async def get_items_count(credentials: dict) -> dict:
 95    return await hx_req(
 96        f"{credentials['Server']}/emby/Items/Counts",
 97        headers=default_headers(credentials),
 98        transport=AsyncHTTPTransport(),
 99        proxy=EMBY_PROXY,
100        verify=False,
101        timeout=5,
102        check_keys=["MovieCount"],
103        silent=True,
104        max_retry=1,
105    )
106
107
108async def get_similar(credentials: dict, item_id: str | int) -> dict:
109    params = {
110        "UserId": credentials["User"]["Id"],
111        "Fields": "ProductionYear,EndDate,Status,CommunityRating,PrimaryImageAspectRatio,RecursiveItemCount,ProviderIds",
112        "Limit": 20,
113        "ImageTypeLimit": 1,
114        "EnableImageTypes": "Primary,Backdrop,Thumb,Logo",
115    }
116    return await hx_req(
117        f"{credentials['Server']}/emby/Items/{item_id}/Similar",
118        headers=default_headers(credentials),
119        params=build_params(credentials, params),
120        transport=AsyncHTTPTransport(),
121        proxy=EMBY_PROXY,
122        verify=False,
123        timeout=5,
124        check_keys=["Items"],
125        silent=True,
126        max_retry=1,
127    )
128
129
130async def get_playback_info(credentials: dict, item_id: str | int) -> dict:
131    return await hx_req(
132        f"{credentials['Server']}/emby/Items/{item_id}/PlaybackInfo",
133        "POST",
134        headers=default_headers(credentials) | {"content-type": "application/json"},
135        params=build_params(credentials, {"IsPlayback": "true"}),
136        transport=AsyncHTTPTransport(),
137        proxy=EMBY_PROXY,
138        verify=False,
139        timeout=5,
140        json_data=DEVICE_PROFILE,
141        check_keys=["PlaySessionId"],
142        silent=True,
143        max_retry=1,
144    )
145
146
147async def remove_from_resume(credentials: dict, item_id: str | int) -> dict:
148    server = credentials["Server"]
149    uid = credentials["User"]["Id"]
150    return await hx_req(
151        f"{server}/emby/Users/{uid}/Items/{item_id}/HideFromResume",
152        "POST",
153        headers=default_headers(credentials) | {"content-length": "0", "content-type": "application/json"},
154        params=build_params(credentials, {"Hide": "true"}),
155        transport=AsyncHTTPTransport(),
156        proxy=EMBY_PROXY,
157        verify=False,
158        timeout=5,
159        check_keys=["PlayCount"],
160        silent=True,
161        max_retry=1,
162    )
163
164
165async def get_items(credentials: dict) -> dict:
166    """Get items from emby server.
167
168    Returns:
169        dict: {item_id: item_dict}
170    """
171    server = credentials["Server"]
172    uid = credentials["User"]["Id"]
173
174    views = await hx_req(
175        f"{server}/emby/Users/{uid}/Views",
176        headers=default_headers(credentials),
177        params=build_params(credentials),
178        transport=AsyncHTTPTransport(),
179        proxy=EMBY_PROXY,
180        verify=False,
181        timeout=5,
182        check_keys=["Items"],
183        silent=True,
184        max_retry=1,
185    )
186    if views.get("hx_error"):
187        logger.error(views.get("hx_error"))
188        return {}
189    parent_ids = [x["Id"] for x in views["Items"] if x.get("CollectionType", "").lower() in ["movies", "tvshows"]]
190    items = {}  # {item_id: item_dict}
191    for parent_id in parent_ids:
192        params = {
193            "ParentId": parent_id,
194            "Limit": 20,
195            "ImageTypeLimit": "1",
196            "EnableImageTypes": "Primary,Backdrop,Thumb,Logo",
197            "Fields": "ProductionYear,EndDate,Status,PrimaryImageAspectRatio,CommunityRating,RecursiveItemCount,ProviderIds",
198        }
199        resp: list[dict] = await hx_req(
200            f"{server}/emby/Users/{uid}/Items/Latest",
201            headers=default_headers(credentials),
202            params=build_params(credentials, params),
203            transport=AsyncHTTPTransport(),
204            proxy=EMBY_PROXY,
205            verify=False,
206            timeout=5,
207            check_keys=["0.Id"],
208            silent=True,
209            max_retry=1,
210        )  # type: ignore
211        if "hx_error" in resp:
212            continue
213        for item in resp:
214            if item.get("MediaType") != "Video" or not item.get("Id") or not item.get("RunTimeTicks"):
215                continue
216            items[item["Id"]] = item
217
218    if not items and parent_ids:
219        logger.warning("无法获取最新视频, 尝试从文件夹中读取.")
220        for parent_id in parent_ids:
221            await asyncio.sleep(4)
222            params = {
223                "EnableImageTypes": "Primary,Backdrop,Thumb,Logo",
224                "Fields": "BasicSyncInfo,CanDelete,PrimaryImageAspectRatio,ProductionYear",
225                "ImageTypeLimit": 1,
226                "IncludeItemTypes": "Movie",
227                "Limit": 50,
228                "ParentId": parent_id,
229                "Recursive": "true",
230                "SortBy": "SortName",
231                "SortOrder": "Ascending",
232                "StartIndex": 0,
233            }
234            resp: list[dict] = await hx_req(
235                f"{server}/emby/Users/{uid}/Items",
236                headers=default_headers(credentials),
237                params=build_params(credentials, params),
238                transport=AsyncHTTPTransport(),
239                proxy=EMBY_PROXY,
240                verify=False,
241                timeout=5,
242                silent=True,
243                max_retry=1,
244            )  # type: ignore
245            if "hx_error" in resp:
246                continue
247            for item in resp:
248                if item.get("MediaType") != "Video" or not item.get("Id") or not item.get("RunTimeTicks"):
249                    continue
250                items[item["Id"]] = item
251    return items