Skip to content

Commit 55efb83

Browse files
committed
feat: Implement mediahub to retrieve own screenshots/gameclips
1 parent f11e3e9 commit 55efb83

6 files changed

Lines changed: 290 additions & 0 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"values": [
3+
{
4+
"commentCount": 0,
5+
"contentId": "62f47208-b873-479b-a9ee-6b16126c43b20",
6+
"contentLocators": [
7+
{
8+
"expiration": "2022-11-11T11:05:59.3087637Z",
9+
"fileSize": 18825622,
10+
"locatorType": "Download",
11+
"uri": "https://gameclipscontent-d3023.media.xboxlive.com/xuid-2669321029139235-private/62f47208-b873-479b-a9ee-6b16126c43b20.MP4?sv=2015-12-11&sr=b&si=DefaultAccess&sig=OQMDiPhvEw0%2BpitWhQLlpEME%2B1A4hW1OkDI4SZpKvpM%3D&__gda__=1668164759_b54098d230229feb0eb2e06890d2c9ea"
12+
},
13+
{
14+
"locatorType": "Thumbnail_Small",
15+
"uri": "https://gameclipscontent-t3023.media.xboxlive.com/xuid-2669321029139235-public/62f47208-b873-479b-a9ee-6b16126c43b20_Thumbnail.PNG"
16+
},
17+
{
18+
"locatorType": "Thumbnail_Large",
19+
"uri": "https://gameclipscontent-t3023.media.xboxlive.com/xuid-2669321029139235-public/62f47208-b873-479b-a9ee-6b16126c43b20_Thumbnail.PNG"
20+
}
21+
],
22+
"contentSegments": [
23+
{
24+
"creationType": "UserGenerated",
25+
"creatorChannelId": null,
26+
"creatorXuid": 2669321029139235,
27+
"durationInSeconds": 27,
28+
"offset": 0,
29+
"recordDate": "2018-04-02T19:28:38Z",
30+
"secondaryTitleId": null,
31+
"segmentId": 1,
32+
"titleId": 1717113201
33+
}
34+
],
35+
"contentState": "Published",
36+
"creationType": "UserGenerated",
37+
"durationInSeconds": 27,
38+
"enforcementState": "None",
39+
"frameRate": 30,
40+
"greatestMomentId": "",
41+
"likeCount": 0,
42+
"localId": "67fac079-f793-4ea8-a448-bd7096a9fac30",
43+
"ownerXuid": 2669321029139235,
44+
"resolutionHeight": 720,
45+
"resolutionWidth": 1280,
46+
"safetyThreshold": "None",
47+
"sandboxId": "RETAIL",
48+
"sessions": [],
49+
"shareCount": 0,
50+
"sharedTo": [],
51+
"titleData": "",
52+
"titleId": 1717113201,
53+
"titleName": "Sea of Thieves",
54+
"tournaments": [],
55+
"uploadDate": "2018-04-02T19:46:26.1665177Z",
56+
"uploadDeviceType": "Edmonton",
57+
"uploadLanguage": "de-DE",
58+
"uploadRegion": "DE",
59+
"uploadTitleId": 49312658,
60+
"userCaption": "",
61+
"viewCount": 1
62+
}
63+
]
64+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"values": [
3+
{
4+
"captureDate": "2022-01-08T19:49:17Z",
5+
"contentId": "0b2a0559-a246-911a-9a06-e01f3d38ec5b",
6+
"contentLocators": [
7+
{
8+
"fileSize": 17030676,
9+
"locatorType": "Download",
10+
"uri": "https://screenshotscontent-d4002.media.xboxlive.com/xuid-2669321029139235-private/0b2a0559-a246-911a-9a06-e01f3d38ec5b.PNG?sv=2015-12-11&sr=b&si=DefaultAccess"
11+
},
12+
{
13+
"locatorType": "Thumbnail_Small",
14+
"uri": "https://screenshotscontent-t4002.media.xboxlive.com/xuid-2669321029139235-public/0b2a0559-a246-911a-9a06-e01f3d38ec5b_Thumbnail.PNG"
15+
},
16+
{
17+
"locatorType": "Thumbnail_Large",
18+
"uri": "https://screenshotscontent-t4002.media.xboxlive.com/xuid-2669321029139235-public/0b2a0559-a246-911a-9a06-e01f3d38ec5b_Thumbnail.PNG"
19+
},
20+
{
21+
"fileSize": 14637915,
22+
"locatorType": "Download_HDR",
23+
"uri": "https://screenshotscontent-d4002.media.xboxlive.com/xuid-2669321029139235-private/0b2a0559-a246-911a-9a06-e01f3d38ec5b.JXR?sv=2015-12-11&sr=b&si=DefaultAccess"
24+
}
25+
],
26+
"CreationType": "UserGenerated",
27+
"localId": "62f47208-b873-479b-a9ee-6b16126c43b20",
28+
"ownerXuid": 2669321029139235,
29+
"resolutionHeight": 2160,
30+
"resolutionWidth": 3840,
31+
"sandboxId": "RETAIL",
32+
"sharedTo": [],
33+
"titleId": 1777860928,
34+
"titleName": "Microsoft Flight Simulator",
35+
"dateUploaded": "2022-01-08T19:54:14.6516641Z",
36+
"uploadLanguage": "de-DE",
37+
"uploadRegion": "DE",
38+
"uploadTitleId": 49312658,
39+
"uploadDeviceType": "Scarlett",
40+
"commentCount": 0,
41+
"likeCount": 0,
42+
"shareCount": 0,
43+
"viewCount": 0,
44+
"contentState": "Published",
45+
"enforcementState": "None",
46+
"safetyThreshold": "Unscanned",
47+
"sessions": [],
48+
"tournaments": []
49+
}
50+
]
51+
}

tests/test_mediahub.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from httpx import Response
2+
import pytest
3+
4+
from tests.common import get_response_json
5+
6+
7+
@pytest.mark.asyncio
8+
async def test_media_screenshots_own(respx_mock, xbl_client):
9+
route = respx_mock.post("https://mediahub.xboxlive.com/screenshots/search").mock(
10+
return_value=Response(200, json=get_response_json("mediahub_screenshots_own"))
11+
)
12+
ret = await xbl_client.mediahub.fetch_own_screenshots()
13+
14+
assert len(ret.values) == 1
15+
assert route.called
16+
17+
18+
@pytest.mark.asyncio
19+
async def test_media_gameclips_own(respx_mock, xbl_client):
20+
route = respx_mock.post("https://mediahub.xboxlive.com/gameclips/search").mock(
21+
return_value=Response(200, json=get_response_json("mediahub_gameclips_own"))
22+
)
23+
ret = await xbl_client.mediahub.fetch_own_clips()
24+
25+
assert len(ret.values) == 1
26+
assert route.called

xbox/webapi/api/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from xbox.webapi.api.provider.cqs import CQSProvider
1818
from xbox.webapi.api.provider.gameclips import GameclipProvider
1919
from xbox.webapi.api.provider.lists import ListsProvider
20+
from xbox.webapi.api.provider.mediahub import MediahubProvider
2021
from xbox.webapi.api.provider.message import MessageProvider
2122
from xbox.webapi.api.provider.people import PeopleProvider
2223
from xbox.webapi.api.provider.presence import PresenceProvider
@@ -121,6 +122,7 @@ def __init__(
121122
self.gameclips = GameclipProvider(self)
122123
self.people = PeopleProvider(self)
123124
self.presence = PresenceProvider(self)
125+
self.mediahub = MediahubProvider(self)
124126
self.message = MessageProvider(self)
125127
self.userstats = UserStatsProvider(self)
126128
self.screenshots = ScreenshotsProvider(self)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
Mediahub - Fetch screenshots and gameclips
3+
"""
4+
from xbox.webapi.api.provider.baseprovider import BaseProvider
5+
from xbox.webapi.api.provider.mediahub.models import (
6+
MediahubGameclips,
7+
MediahubScreenshots,
8+
)
9+
10+
11+
class MediahubProvider(BaseProvider):
12+
MEDIAHUB_URL = "https://mediahub.xboxlive.com"
13+
HEADERS = {"x-xbl-contract-version": "3"}
14+
15+
async def fetch_own_clips(
16+
self, skip: int = 0, count: int = 500, **kwargs
17+
) -> MediahubGameclips:
18+
"""
19+
Fetch own clips
20+
21+
Args:
22+
skip: Number of items to skip
23+
count: Max entries to fetch
24+
25+
Returns:
26+
:class:`MediahubGameclips`: Gameclips
27+
"""
28+
url = f"{self.MEDIAHUB_URL}/gameclips/search"
29+
post_data = {
30+
"max": count,
31+
"query": f"OwnerXuid eq {self.client.xuid}",
32+
"skip": skip,
33+
}
34+
resp = await self.client.session.post(
35+
url, json=post_data, headers=self.HEADERS, **kwargs
36+
)
37+
resp.raise_for_status()
38+
return MediahubGameclips(**resp.json())
39+
40+
async def fetch_own_screenshots(
41+
self, skip: int = 0, count: int = 500, **kwargs
42+
) -> MediahubScreenshots:
43+
"""
44+
Fetch own screenshots
45+
46+
Args:
47+
skip: Number of items to skip
48+
count: Max entries to fetch
49+
50+
Returns:
51+
:class:`MediahubScreenshots`: Screenshots
52+
"""
53+
url = f"{self.MEDIAHUB_URL}/screenshots/search"
54+
post_data = {
55+
"max": count,
56+
"query": f"OwnerXuid eq {self.client.xuid}",
57+
"skip": skip,
58+
}
59+
resp = await self.client.session.post(
60+
url, json=post_data, headers=self.HEADERS, **kwargs
61+
)
62+
resp.raise_for_status()
63+
return MediahubScreenshots(**resp.json())
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from typing import List, Optional
2+
3+
from xbox.webapi.common.models import CamelCaseModel
4+
5+
6+
class ContentSegment(CamelCaseModel):
7+
segment_id: int
8+
creation_type: str
9+
creator_channel_id: Optional[str]
10+
creator_xuid: int
11+
record_date: str
12+
duration_in_seconds: int
13+
offset: int
14+
secondary_title_id: Optional[int]
15+
title_id: int
16+
17+
18+
class ContentLocator(CamelCaseModel):
19+
expiration: Optional[str]
20+
file_size: Optional[int]
21+
locator_type: str
22+
uri: str
23+
24+
25+
class GameclipContent(CamelCaseModel):
26+
content_id: str
27+
content_locators: List[ContentLocator]
28+
content_segments: List[ContentSegment]
29+
creation_type: str
30+
duration_in_seconds: int
31+
local_id: str
32+
owner_xuid: int
33+
sandbox_id: str
34+
shared_to: List[int]
35+
title_id: int
36+
title_name: str
37+
upload_date: str
38+
upload_language: str
39+
upload_region: str
40+
upload_title_id: int
41+
upload_device_type: str
42+
comment_count: int
43+
like_count: int
44+
share_count: int
45+
view_count: int
46+
content_state: str
47+
enforcement_state: str
48+
sessions: List[str]
49+
tournaments: List[str]
50+
51+
52+
class MediahubGameclips(CamelCaseModel):
53+
values: List[GameclipContent]
54+
55+
56+
class ScreenshotContent(CamelCaseModel):
57+
content_id: str
58+
capture_date: str
59+
content_locators: List[ContentLocator]
60+
local_id: str
61+
owner_xuid: int
62+
resolution_height: int
63+
resolution_width: int
64+
date_uploaded: str
65+
sandbox_id: str
66+
shared_to: List[int]
67+
title_id: int
68+
title_name: str
69+
upload_language: str
70+
upload_region: str
71+
upload_title_id: int
72+
upload_device_type: str
73+
comment_count: int
74+
like_count: int
75+
share_count: int
76+
view_count: int
77+
content_state: str
78+
enforcement_state: str
79+
sessions: List[str]
80+
tournaments: List[str]
81+
82+
83+
class MediahubScreenshots(CamelCaseModel):
84+
values: List[ScreenshotContent]

0 commit comments

Comments
 (0)