mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-08-05 12:56:42 +02:00
Integrate TrueAchievements
This commit is contained in:
@@ -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,
|
||||
|
@@ -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 = {
|
||||
|
@@ -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();
|
||||
|
@@ -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",
|
||||
|
81
src/utils/true-achievements.ts
Normal file
81
src/utils/true-achievements.ts
Normal 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();
|
||||
}
|
||||
}
|
@@ -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
25
src/utils/xbox-api.ts
Normal 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user