Fix: Emergency Push + Bookmark Feature (WIP)

This commit is contained in:
Youwes09
2026-03-30 00:02:21 -05:00
parent 581eb2adb0
commit fd9d216325
14 changed files with 749 additions and 678 deletions
+11 -21
View File
@@ -5,7 +5,7 @@ import type { Manga, Chapter } from "./types";
const APP_ID = "1487894643613106298";
const FALLBACK_IMAGE = "moku_logo";
let sessionStart: number | null = null; // ← captured once on init
let sessionStart: number | null = null;
function isPublicUrl(url: string | null | undefined): boolean {
return typeof url === "string" && url.startsWith("https://");
@@ -34,10 +34,8 @@ const BUTTONS = [
];
export async function initRpc(): Promise<void> {
sessionStart = Date.now(); // ← set once here
await start(APP_ID)
.then(() => console.log("[discord] RPC started"))
.catch((e) => console.error("[discord] initRpc failed:", e));
sessionStart = Date.now();
await start(APP_ID).catch(() => {});
}
export async function setReading(manga: Manga, chapter: Chapter): Promise<void> {
@@ -51,12 +49,10 @@ export async function setReading(manga: Manga, chapter: Chapter): Promise<void>
.setDetails(trunc(manga.title))
.setState(`${formatChapter(chapter)} · Reading`)
.setAssets(assets)
.setTimestamps(getTimestamps()); // ← reuses session start
.setTimestamps(getTimestamps());
activity.setButton(BUTTONS);
await setActivity(activity)
.then(() => console.log("[discord] reading →", manga.title, formatChapter(chapter)))
.catch((e) => console.error("[discord] setActivity failed:", e));
await setActivity(activity).catch(() => {});
}
export async function setIdle(): Promise<void> {
@@ -67,23 +63,17 @@ export async function setIdle(): Promise<void> {
const activity = new Activity()
.setDetails("Browsing")
.setAssets(assets)
.setTimestamps(getTimestamps()); // ← reuses session start
.setTimestamps(getTimestamps());
activity.setButton(BUTTONS);
await setActivity(activity)
.then(() => console.log("[discord] idle"))
.catch((e) => console.error("[discord] setActivity failed (idle):", e));
await setActivity(activity).catch(() => {});
}
export async function clearReading(): Promise<void> {
await clearActivity()
.then(() => console.log("[discord] activity cleared"))
.catch((e) => console.error("[discord] clearActivity failed:", e));
await clearActivity().catch(() => {});
}
export async function destroyRpc(): Promise<void> {
sessionStart = null; // ← clean up on stop
await stop()
.then(() => console.log("[discord] RPC stopped"))
.catch((e) => console.error("[discord] destroyRpc failed:", e));
}
sessionStart = null;
await stop().catch(() => {});
}
+3
View File
@@ -12,6 +12,7 @@ export interface Keybinds {
togglePageStyle: string;
toggleFullscreen: string;
openSettings: string;
toggleBookmark: string;
}
export const DEFAULT_KEYBINDS: Keybinds = {
@@ -26,6 +27,7 @@ export const DEFAULT_KEYBINDS: Keybinds = {
togglePageStyle: "q",
toggleFullscreen: "f",
openSettings: "o",
toggleBookmark: "m",
};
export const KEYBIND_LABELS: Record<keyof Keybinds, string> = {
@@ -40,6 +42,7 @@ export const KEYBIND_LABELS: Record<keyof Keybinds, string> = {
togglePageStyle: "Toggle page style",
toggleFullscreen: "Toggle fullscreen",
openSettings: "Open settings",
toggleBookmark: "Toggle bookmark",
};
export function eventToKeybind(e: KeyboardEvent): string {
+59 -11
View File
@@ -8,29 +8,77 @@ export function cn(...inputs: ClassValue[]) {
// ── NSFW genre filtering ──────────────────────────────────────────────────────
/**
* Genre tags that indicate adult/mature content.
* Checked case-insensitively against each manga's genre array.
* Extend this set if additional tags need to be covered.
* Default substrings used when no user-configured list is available.
* The Settings > Content tab lets users add/remove entries from this list,
* which is stored as settings.nsfwFilteredTags.
*/
const NSFW_GENRE_TAGS = new Set([
export const DEFAULT_NSFW_TAGS = [
"adult",
"mature",
"hentai",
"ecchi",
"erotica",
"pornographic",
"erotic", // catches "erotica", "erotic content", "erotic manga"
"pornograph", // catches "pornographic", "pornography"
"18+",
"smut",
"lemon",
"explicit",
]);
"sexual violence",
];
/**
* Returns true if the manga carries at least one genre tag that is considered
* adult/mature. Used to enforce the `showNsfw` setting across all views.
* Returns true if the manga carries at least one genre tag matching any of
* the provided substrings (case-insensitive). Pass settings.nsfwFilteredTags
* as the tag list; falls back to DEFAULT_NSFW_TAGS if omitted.
*/
export function isNsfwManga(manga: { genre?: string[] | null }): boolean {
return (manga.genre ?? []).some((g) => NSFW_GENRE_TAGS.has(g.toLowerCase().trim()));
export function isNsfwManga(
manga: { genre?: string[] | null },
tags: string[] = DEFAULT_NSFW_TAGS,
): boolean {
return (manga.genre ?? []).some((g) => {
const normalized = g.toLowerCase().trim();
return tags.some((sub) => normalized.includes(sub));
});
}
/**
* Single authoritative NSFW gate used by all views.
*
* Returns true when the manga should be HIDDEN. Checks in order:
* 1. showNsfw disabled globally → skip everything, hide by source flag or genre match.
* 2. Source is in blockedSourceIds → always hide regardless of showNsfw.
* 3. Source is in allowedSourceIds → always show (bypasses isNsfw flag only, genre tags still apply).
* 4. Source isNsfw flag → hide unless source is allowed.
* 5. Genre tag match → hide.
*
* Usage: items.filter(m => !shouldHideNsfw(m, settings))
*/
export function shouldHideNsfw(
manga: {
genre?: string[] | null;
source?: { id?: string; isNsfw?: boolean } | null;
},
settings: {
showNsfw: boolean;
nsfwFilteredTags: string[];
nsfwAllowedSourceIds: string[];
nsfwBlockedSourceIds: string[];
},
): boolean {
const srcId = manga.source?.id;
// Explicit block always wins, even when showNsfw is on
if (srcId && settings.nsfwBlockedSourceIds.includes(srcId)) return true;
// If NSFW is globally allowed, only explicit blocks apply
if (settings.showNsfw) return false;
// Source is explicitly allowed — skip the isNsfw flag check, but still filter genres
const sourceAllowed = !!(srcId && settings.nsfwAllowedSourceIds.includes(srcId));
if (!sourceAllowed && manga.source?.isNsfw) return true;
return isNsfwManga(manga, settings.nsfwFilteredTags);
}
// ── Source deduplication ──────────────────────────────────────────────────────