This commit is contained in:
lilezek 2024-05-03 22:09:10 +02:00
commit 62b5b12397
18 changed files with 188 additions and 344 deletions

View file

@ -69,6 +69,8 @@
"copied_link_to_clipboard": "Link copied",
"hours": "hours",
"minutes": "minutes",
"amount_hours": "{{amount}} hours",
"amount_minutes": "{{amount}} minutes",
"accuracy": "{{accuracy}}% accuracy",
"add_to_library": "Add to library",
"remove_from_library": "Remove from library",
@ -135,7 +137,9 @@
"enable_repack_list_notifications": "When a new repack is added",
"telemetry": "Telemetry",
"telemetry_description": "Enable anonymous usage statistics",
"real_debrid_api_token_description": "(Optional) Real Debrid API token"
"real_debrid_api_token_description": "(Optional) Real Debrid API token",
"behavior": "Behavior",
"quit_app_instead_hiding": "Close app instead of minimizing to tray"
},
"notifications": {
"download_complete": "Download complete",

View file

@ -61,6 +61,8 @@
"copied_link_to_clipboard": "Enlace copiado",
"hours": "horas",
"minutes": "minutos",
"amount_hours": "{{amount}} horas",
"amount_minutes": "{{amount}} minutos",
"accuracy": "{{accuracy}}% precisión",
"add_to_library": "Agregar a la biblioteca",
"remove_from_library": "Eliminar de la biblioteca",

View file

@ -61,6 +61,8 @@
"copied_link_to_clipboard": "Lien copié",
"hours": "heures",
"minutes": "minutes",
"amount_hours": "{{amount}} heures",
"amount_minutes": "{{amount}} minutes",
"accuracy": "{{accuracy}}% précision",
"add_to_library": "Ajouter à la bibliothèque",
"remove_from_library": "Supprimer de la bibliothèque",

View file

@ -66,6 +66,8 @@
"copied_link_to_clipboard": "Link másolva",
"hours": "óra",
"minutes": "perc",
"amount_hours": "{{amount}} óra",
"amount_minutes": "{{amount}} perc",
"accuracy": "{{accuracy}}% pontosság",
"add_to_library": "Hozzáadás a könyvtárhoz",
"remove_from_library": "Eltávolítás a könyvtárból",

View file

@ -69,6 +69,8 @@
"copied_link_to_clipboard": "Link copiato",
"hours": "ore",
"minutes": "minuti",
"amount_hours": "{{amount}} ore",
"amount_minutes": "{{amount}} minuti",
"accuracy": "{{accuratezza}}% di accuratezza",
"add_to_library": "Aggiungi alla libreria",
"remove_from_library": "Rimuovi dalla libreria",

View file

@ -64,6 +64,8 @@
"copied_link_to_clipboard": "Link copiado",
"hours": "horas",
"minutes": "minutos",
"amount_hours": "{{amount}} horas",
"amount_minutes": "{{amount}} minutos",
"accuracy": "{{accuracy}}% de precisão",
"add_to_library": "Adicionar à biblioteca",
"remove_from_library": "Remover da biblioteca",
@ -131,7 +133,9 @@
"enable_repack_list_notifications": "Quando a lista de repacks for atualizada",
"telemetry": "Telemetria",
"telemetry_description": "Habilitar estatísticas de uso anônimas",
"real_debrid_api_token_description": "(Opcional) Real Debrid API token"
"real_debrid_api_token_description": "(Opcional) Real Debrid API token",
"behavior": "Comportamento",
"quit_app_instead_hiding": "Fechar o aplicativo em vez de minimizá-lo"
},
"notifications": {
"download_complete": "Download concluído",

View file

@ -29,6 +29,9 @@ export class UserPreferences {
@Column("boolean", { default: true })
telemetryEnabled: boolean;
@Column("boolean", { default: false })
preferQuitInsteadOfHiding: boolean;
@CreateDateColumn()
createdAt: Date;

View file

@ -5,7 +5,7 @@ import {
getNewRepacksFromCPG,
getNewRepacksFromUser,
getNewRepacksFromXatab,
// getNewRepacksFromOnlineFix,
getNewRepacksFromOnlineFix,
readPipe,
startProcessWatcher,
writePipe,
@ -76,9 +76,9 @@ const checkForNewRepacks = async () => {
getNewRepacksFromCPG(
existingRepacks.filter((repack) => repack.repacker === "CPG")
),
// getNewRepacksFromOnlineFix(
// existingRepacks.filter((repack) => repack.repacker === "onlinefix")
// ),
getNewRepacksFromOnlineFix(
existingRepacks.filter((repack) => repack.repacker === "onlinefix")
),
track1337xUsers(existingRepacks),
]).then(() => {
repackRepository.count().then((count) => {

View file

@ -8,20 +8,18 @@ import { WindowManager } from "./window-manager";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export const startProcessWatcher = async () => {
const sleepTime = 300;
const sleepTime = 500;
const gamesPlaytime = new Map<number, number>();
// eslint-disable-next-line no-constant-condition
while (true) {
await sleep(sleepTime);
const games = await gameRepository.find({
where: {
executablePath: Not(IsNull()),
},
});
if (games.length == 0) {
if (games.length === 0) {
continue;
}
@ -71,5 +69,7 @@ export const startProcessWatcher = async () => {
}
}
}
await sleep(sleepTime);
}
};

View file

@ -20,3 +20,19 @@ export const requestWebPage = async (url: string) => {
},
}).then((response) => response.text());
};
export const decodeNonUtf8Response = async (res: Response) => {
const contentType = res.headers.get("content-type");
if (!contentType) return res.text();
const charset = contentType.substring(contentType.indexOf("charset=") + 8);
const text = await res.arrayBuffer().then((ab) => {
const dataView = new DataView(ab);
const decoder = new TextDecoder(charset);
return decoder.decode(dataView);
});
return text;
};

View file

@ -2,4 +2,4 @@ export * from "./1337x";
export * from "./xatab";
export * from "./cpg-repacks";
export * from "./gog";
// export * from "./online-fix";
export * from "./online-fix";

View file

@ -1,56 +1,42 @@
import { Repack } from "@main/entity";
import { savePage } from "./helpers";
import { decodeNonUtf8Response, savePage } from "./helpers";
import { logger } from "../logger";
import parseTorrent, {
toMagnetURI,
Instance as TorrentInstance,
} from "parse-torrent";
import { JSDOM } from "jsdom";
import { gotScraping } from "got-scraping";
import { CookieJar } from "tough-cookie";
import { format, parse, sub } from "date-fns";
import { ru } from "date-fns/locale";
import { decode } from "windows-1251";
import { onlinefixFormatter } from "@main/helpers";
import makeFetchCookie from "fetch-cookie";
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
export const getNewRepacksFromOnlineFix = async (
existingRepacks: Repack[] = [],
page = 1,
cookieJar = new CookieJar()
cookieJar = new makeFetchCookie.toughCookie.CookieJar()
): Promise<void> => {
const hasCredentials =
import.meta.env.MAIN_VITE_ONLINEFIX_USERNAME &&
import.meta.env.MAIN_VITE_ONLINEFIX_PASSWORD;
if (!hasCredentials) return;
const http = gotScraping.extend({
headerGeneratorOptions: {
browsers: [
{
name: "chrome",
minVersion: 87,
maxVersion: 89,
},
],
devices: ["desktop"],
locales: ["en-US"],
operatingSystems: ["windows", "linux"],
},
cookieJar: cookieJar,
});
const http = makeFetchCookie(fetch, cookieJar);
if (page === 1) {
await http.get("https://online-fix.me/");
await http("https://online-fix.me/");
const preLogin =
((await http
.get("https://online-fix.me/engine/ajax/authtoken.php", {
headers: {
"X-Requested-With": "XMLHttpRequest",
Referer: "https://online-fix.me/",
},
})
.json()) as {
((await http("https://online-fix.me/engine/ajax/authtoken.php", {
method: "GET",
headers: {
"X-Requested-With": "XMLHttpRequest",
Referer: "https://online-fix.me/",
},
}).then((res) => res.json())) as {
field: string;
value: string;
}) || undefined;
@ -64,27 +50,25 @@ export const getNewRepacksFromOnlineFix = async (
[preLogin.field]: preLogin.value,
});
await http
.post("https://online-fix.me/", {
encoding: "binary",
headers: {
Referer: "https://online-fix.me",
Origin: "https://online-fix.me",
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
})
.text();
await http("https://online-fix.me/", {
method: "POST",
headers: {
Referer: "https://online-fix.me",
Origin: "https://online-fix.me",
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
});
}
const pageParams = page > 1 ? `${`/page/${page}`}` : "";
const home = await http.get(`https://online-fix.me${pageParams}`, {
encoding: "binary",
});
const document = new JSDOM(home.body).window.document;
const home = await http(`https://online-fix.me${pageParams}`).then((res) =>
decodeNonUtf8Response(res)
);
const document = new JSDOM(home).window.document;
const repacks = [];
const repacks: QueryDeepPartialEntity<Repack>[] = [];
const articles = Array.from(document.querySelectorAll(".news"));
const totalPages = Number(
document.querySelector("nav > a:nth-child(13)")?.textContent
@ -93,25 +77,23 @@ export const getNewRepacksFromOnlineFix = async (
try {
await Promise.all(
articles.map(async (article) => {
const gameName = onlinefixFormatter(
decode(article.querySelector("h2.title")?.textContent?.trim())
);
const gameText = article.querySelector("h2.title")?.textContent?.trim();
if (!gameText) return;
const gameName = onlinefixFormatter(gameText);
const gameLink = article.querySelector("a")?.getAttribute("href");
if (!gameLink) return;
const gamePage = await http
.get(gameLink, {
encoding: "binary",
})
.text();
const gamePage = await http(gameLink).then((res) =>
decodeNonUtf8Response(res)
);
const gameDocument = new JSDOM(gamePage).window.document;
const uploadDateText = gameDocument.querySelector("time").textContent;
const uploadDateText = gameDocument.querySelector("time")?.textContent;
if (!uploadDateText) return;
let decodedDateText = decode(uploadDateText);
let decodedDateText = uploadDateText;
// "Вчера" means yesterday.
if (decodedDateText.includes("Вчера")) {
@ -141,14 +123,11 @@ export const getNewRepacksFromOnlineFix = async (
const torrentPrePage = torrentButtons[0]?.getAttribute("href");
if (!torrentPrePage) return;
const torrentPage = await http
.get(torrentPrePage, {
encoding: "binary",
headers: {
Referer: gameLink,
},
})
.text();
const torrentPage = await http(torrentPrePage, {
headers: {
Referer: gameLink,
},
}).then((res) => res.text());
const torrentDocument = new JSDOM(torrentPage).window.document;
@ -157,11 +136,9 @@ export const getNewRepacksFromOnlineFix = async (
?.getAttribute("href");
const torrentFile = Buffer.from(
await http
.get(`${torrentPrePage}/${torrentLink}`, {
responseType: "buffer",
})
.buffer()
await http(`${torrentPrePage}/${torrentLink}`).then((res) =>
res.arrayBuffer()
)
);
const torrent = parseTorrent(torrentFile) as TorrentInstance;
@ -170,6 +147,8 @@ export const getNewRepacksFromOnlineFix = async (
});
const torrentSizeInBytes = torrent.length;
if (!torrentSizeInBytes) return;
const fileSizeFormatted =
torrentSizeInBytes >= 1024 ** 3
? `${(torrentSizeInBytes / 1024 ** 3).toFixed(1)}GBs`

View file

@ -4,6 +4,7 @@ import { t } from "i18next";
import path from "node:path";
import icon from "@resources/icon.png?asset";
import trayIcon from "@resources/tray-icon.png?asset";
import { userPreferencesRepository } from "@main/repository";
export class WindowManager {
public static mainWindow: Electron.BrowserWindow | null = null;
@ -25,7 +26,7 @@ export class WindowManager {
}
}
public static createMainWindow() {
public static async createMainWindow() {
// Create the browser window.
this.mainWindow = new BrowserWindow({
width: 1200,
@ -49,7 +50,14 @@ export class WindowManager {
this.loadURL();
this.mainWindow.removeMenu();
const userPreferences = await userPreferencesRepository.findOne({
where: { id: 1 },
});
this.mainWindow.on("close", () => {
if (userPreferences?.preferQuitInsteadOfHiding) {
app.quit();
}
WindowManager.mainWindow?.setProgressBar(-1);
});
}

View file

@ -1,5 +1,5 @@
import { format } from "date-fns";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useDownload } from "@renderer/hooks";
@ -23,6 +23,8 @@ export interface HeroPanelProps {
getGame: () => void;
}
const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120;
export function HeroPanel({
game,
gameDetails,
@ -31,7 +33,7 @@ export function HeroPanel({
getGame,
isGamePlaying,
}: HeroPanelProps) {
const { t } = useTranslation("game_details");
const { t, i18n } = useTranslation("game_details");
const [showBinaryNotFoundModal, setShowBinaryNotFoundModal] = useState(false);
const [lastTimePlayed, setLastTimePlayed] = useState("");
@ -49,29 +51,36 @@ export function HeroPanel({
} = useDownload();
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
const updateLastTimePlayed = useCallback(() => {
setLastTimePlayed(
formatDistance(game.lastTimePlayed, new Date(), {
addSuffix: true,
})
);
}, [game?.lastTimePlayed, formatDistance]);
useEffect(() => {
if (game?.lastTimePlayed) {
updateLastTimePlayed();
setLastTimePlayed(
formatDistance(game.lastTimePlayed, new Date(), {
addSuffix: true,
})
);
}
}, [game?.lastTimePlayed, formatDistance]);
const interval = setInterval(() => {
updateLastTimePlayed();
}, 1000);
const numberFormatter = useMemo(() => {
return new Intl.NumberFormat(i18n.language, {
maximumFractionDigits: 1,
});
}, [i18n]);
return () => {
clearInterval(interval);
};
const formatPlayTime = () => {
const milliseconds = game?.playTimeInMilliseconds || 0;
const seconds = milliseconds / 1000;
const minutes = seconds / 60;
if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) {
return t("amount_minutes", {
amount: minutes.toFixed(0),
});
}
return () => {};
}, [game?.lastTimePlayed, updateLastTimePlayed]);
const hours = minutes / 60;
return t("amount_hours", { amount: numberFormatter.format(hours) });
};
const finalDownloadSize = useMemo(() => {
if (!game) return "N/A";
@ -140,7 +149,7 @@ export function HeroPanel({
<>
<p>
{t("play_time", {
amount: formatDistance(0, game.playTimeInMilliseconds),
amount: formatPlayTime(game.playTimeInMilliseconds),
})}
</p>

View file

@ -11,7 +11,8 @@ export function Settings() {
downloadNotificationsEnabled: false,
repackUpdatesNotificationsEnabled: false,
telemetryEnabled: false,
realDebridApiToken: null,
realDebridApiToken: null as string | null,
preferQuitInsteadOfHiding: false,
});
const { t } = useTranslation("settings");
@ -28,7 +29,9 @@ export function Settings() {
repackUpdatesNotificationsEnabled:
userPreferences?.repackUpdatesNotificationsEnabled ?? false,
telemetryEnabled: userPreferences?.telemetryEnabled ?? false,
realDebridApiToken: userPreferences.realDebridApiToken,
realDebridApiToken: userPreferences?.realDebridApiToken ?? null,
preferQuitInsteadOfHiding:
userPreferences?.preferQuitInsteadOfHiding ?? false,
});
});
}, []);
@ -110,13 +113,25 @@ export function Settings() {
}
/>
<h3>{t("behavior")}</h3>
<CheckboxField
label={t("quit_app_instead_hiding")}
checked={form.preferQuitInsteadOfHiding}
onChange={() =>
updateUserPreferences(
"preferQuitInsteadOfHiding",
!form.preferQuitInsteadOfHiding
)
}
/>
<TextField
label={t("real_debrid_api_token_description")}
value={form.realDebridApiToken ?? ""}
onChange={(event) => {
updateUserPreferences("realDebridApiToken", event.target.value);
}}
/>
}} />
</div>
</section>
);

View file

@ -109,6 +109,7 @@ export interface UserPreferences {
repackUpdatesNotificationsEnabled: boolean;
telemetryEnabled: boolean;
realDebridApiToken: string | null;
preferQuitInsteadOfHiding: boolean;
}
export interface HowLongToBeatCategory {