Integrate TrueAchievements

This commit is contained in:
redphx
2024-09-03 16:56:58 +07:00
parent 4b02fec8ac
commit a6e358479a
22 changed files with 344 additions and 71 deletions

View File

@@ -1,4 +1,5 @@
import iconBetterXcloud from "@assets/svg/better-xcloud.svg" with { type: "text" };
import iconTrueAchievements from "@assets/svg/true-achievements.svg" with { type: "text" };
import iconClose from "@assets/svg/close.svg" with { type: "text" };
import iconCommand from "@assets/svg/command.svg" with { type: "text" };
import iconController from "@assets/svg/controller.svg" with { type: "text" };
@@ -9,6 +10,7 @@ import iconDisplay from "@assets/svg/display.svg" with { type: "text" };
import iconHome from "@assets/svg/home.svg" with { type: "text" };
import iconNativeMkb from "@assets/svg/native-mkb.svg" with { type: "text" };
import iconNew from "@assets/svg/new.svg" with { type: "text" };
import iconPower from "@assets/svg/power.svg" with { type: "text" };
import iconQuestion from "@assets/svg/question.svg" with { type: "text" };
import iconRefresh from "@assets/svg/refresh.svg" with { type: "text" };
import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" };
@@ -37,6 +39,7 @@ import iconUpload from "@assets/svg/upload.svg" with { type: "text" };
export const BxIcon = {
BETTER_XCLOUD: iconBetterXcloud,
TRUE_ACHIEVEMENTS: iconTrueAchievements,
STREAM_SETTINGS: iconStreamSettings,
STREAM_STATS: iconStreamStats,
CLOSE: iconClose,
@@ -50,6 +53,7 @@ export const BxIcon = {
COPY: iconCopy,
TRASH: iconTrash,
CURSOR_TEXT: iconCursorText,
POWER: iconPower,
QUESTION: iconQuestion,
REFRESH: iconRefresh,
VIRTUAL_CONTROLLER: iconVirtualController,

View File

@@ -14,6 +14,7 @@ export enum ButtonStyle {
TALL = 256,
CIRCULAR = 512,
NORMAL_CASE = 1024,
NORMAL_LINK = 2048,
}
const ButtonStyleClass = {
@@ -28,6 +29,7 @@ const ButtonStyleClass = {
[ButtonStyle.TALL]: 'bx-tall',
[ButtonStyle.CIRCULAR]: 'bx-circular',
[ButtonStyle.NORMAL_CASE]: 'bx-normal-case',
[ButtonStyle.NORMAL_LINK]: 'bx-normal-link',
}
type BxButton = {

View File

@@ -71,7 +71,7 @@ export class Screenshot {
// Get data URL and pass to parent app
if (AppInterface) {
const data = $canvas.toDataURL('image/png').split(';base64,')[1];
AppInterface.saveScreenshot(currentStream.titleId, data);
AppInterface.saveScreenshot(currentStream.titleSlug, data);
// Free screenshot from memory
canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
@@ -84,7 +84,7 @@ export class Screenshot {
// Download screenshot
const now = +new Date;
const $anchor = CE<HTMLAnchorElement>('a', {
'download': `${currentStream.titleId}-${now}.png`,
'download': `${currentStream.titleSlug}-${now}.png`,
'href': URL.createObjectURL(blob!),
});
$anchor.click();

View File

@@ -321,6 +321,7 @@ const Texts = {
],
"touch-controller": "Touch controller",
"transparent-background": "Transparent background",
"true-achievements": "TrueAchievements",
"ui": "UI",
"unexpected-behavior": "May cause unexpected behavior",
"united-states": "United States",

View File

@@ -0,0 +1,81 @@
import { BX_FLAGS } from "./bx-flags";
import { AppInterface, STATES } from "./global";
import { ButtonStyle, CE, createButton, getReactProps } from "./html";
import { t } from "./translation";
export class TrueAchievements {
private static $taLink = createButton({
label: t('true-achievements'),
url: 'https://www.trueachievements.com',
style: ButtonStyle.FOCUSABLE | ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.NORMAL_LINK,
onClick: e => {
e.preventDefault();
const dataset = TrueAchievements.$taLink.dataset;
TrueAchievements.open(true, dataset.xboxTitleId, dataset.id);
},
}) as HTMLAnchorElement;
private static $hiddenLink = CE<HTMLAnchorElement>('a', {
target: '_blank',
});
static injectAchievementDetailPage($parent: HTMLElement) {
const props = getReactProps($parent);
if (!props) {
return;
}
try {
// Achievement list
const achievementList: XboxAchievement[] = props.children.props.data.data;
// Get current achievement name
const $header = $parent.querySelector('div[class*=AchievementDetailHeader]') as HTMLElement;
const achievementName = getReactProps($header).children[0].props.achievementName;
// Find achievement based on name
let id: string | undefined;
let xboxTitleId: string | undefined;
for (const achiev of achievementList) {
if (achiev.name === achievementName) {
id = achiev.id;
xboxTitleId = achiev.title.id;
break;
}
}
// Found achievement -> add TrueAchievements button
if (id) {
TrueAchievements.$taLink.dataset.xboxTitleId = xboxTitleId;
TrueAchievements.$taLink.dataset.id = id;
TrueAchievements.$taLink.href = `https://www.trueachievements.com/deeplink/${xboxTitleId}/${id}`;
$parent.appendChild(TrueAchievements.$taLink);
}
} catch (e) {};
}
static open(override: boolean, xboxTitleId?: number | string, id?: number | string) {
if (!xboxTitleId) {
xboxTitleId = STATES.currentStream.xboxTitleId || STATES.currentStream.titleInfo?.details.xboxTitleId;
}
if (AppInterface && AppInterface.openTrueAchievementsLink) {
AppInterface.openTrueAchievementsLink(override, xboxTitleId?.toString(), id?.toString());
return;
}
let url = 'https://www.trueachievements.com';
if (xboxTitleId) {
if (id) {
url += `/deeplink/${xboxTitleId}/${id}`;
} else {
url += `/deeplink/${xboxTitleId}`;
}
}
TrueAchievements.$hiddenLink.href = url;
TrueAchievements.$hiddenLink.click();
}
}

View File

@@ -110,3 +110,12 @@ export async function copyToClipboard(text: string, showToast=true): Promise<boo
return false;
}
export function productTitleToSlug(title: string): string {
return title.replace(/[;,/?:@&=+_`~$%#^*()!^\u2122\xae\xa9]/g, '')
.replace(/ {2,}/g, ' ')
.trim()
.substr(0, 50)
.replace(/ /g, '-')
.toLowerCase();
}

25
src/utils/xbox-api.ts Normal file
View File

@@ -0,0 +1,25 @@
import { NATIVE_FETCH } from "./bx-flags"
export class XboxApi {
private static CACHED_TITLES: Record<string, string> = {};
static async getProductTitle(xboxTitleId: number | string): Promise<string | null> {
xboxTitleId = xboxTitleId.toString();
if (XboxApi.CACHED_TITLES[xboxTitleId]) {
return XboxApi.CACHED_TITLES[xboxTitleId];
}
try {
const url = `https://displaycatalog.mp.microsoft.com/v7.0/products/lookup?market=US&languages=en&value=${xboxTitleId}&alternateId=XboxTitleId&fieldsTemplate=browse`;
const resp = await NATIVE_FETCH(url);
const json = await resp.json();
const productTitle = json['Products'][0]['LocalizedProperties'][0]['ProductTitle'];
XboxApi.CACHED_TITLES[xboxTitleId] = productTitle;
return productTitle;
} catch (e) {}
return null;
}
}