animes & books page data is fetched auto now
This commit is contained in:
10
desktop/package-lock.json
generated
10
desktop/package-lock.json
generated
@@ -19,6 +19,7 @@
|
|||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"node-addon-api": "^8.5.0",
|
"node-addon-api": "^8.5.0",
|
||||||
|
"node-cron": "^4.2.1",
|
||||||
"playwright-chromium": "^1.57.0",
|
"playwright-chromium": "^1.57.0",
|
||||||
"sqlite3": "^5.1.7"
|
"sqlite3": "^5.1.7"
|
||||||
},
|
},
|
||||||
@@ -5446,6 +5447,15 @@
|
|||||||
"semver": "^7.3.5"
|
"semver": "^7.3.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-cron": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-gyp": {
|
"node_modules/node-gyp": {
|
||||||
"version": "12.1.0",
|
"version": "12.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.1.0.tgz",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"node-addon-api": "^8.5.0",
|
"node-addon-api": "^8.5.0",
|
||||||
|
"node-cron": "^4.2.1",
|
||||||
"playwright-chromium": "^1.57.0",
|
"playwright-chromium": "^1.57.0",
|
||||||
"sqlite3": "^5.1.7"
|
"sqlite3": "^5.1.7"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ const fastify = require("fastify")({
|
|||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
|
const cron = require("node-cron");
|
||||||
const { initHeadless } = require("./electron/shared/headless");
|
const { initHeadless } = require("./electron/shared/headless");
|
||||||
const { initDatabase } = require("./electron/shared/database");
|
const { initDatabase } = require("./electron/shared/database");
|
||||||
const { loadExtensions } = require("./electron/shared/extensions");
|
const { loadExtensions } = require("./electron/shared/extensions");
|
||||||
const { init } = require("./electron/api/rpc/rpc.controller");
|
const { init } = require("./electron/api/rpc/rpc.controller");
|
||||||
|
const {refreshTrendingAnime, refreshTopAiringAnime} = require("./electron/api/anime/anime.service");
|
||||||
|
const {refreshPopularBooks, refreshTrendingBooks} = require("./electron/api/books/books.service");
|
||||||
|
|
||||||
const dotenv = require("dotenv");
|
const dotenv = require("dotenv");
|
||||||
|
|
||||||
const isPackaged = process.env.IS_PACKAGED === "true";
|
const isPackaged = process.env.IS_PACKAGED === "true";
|
||||||
|
|
||||||
const envPath = isPackaged
|
const envPath = isPackaged
|
||||||
? path.join(process.resourcesPath, ".env")
|
? path.join(process.resourcesPath, ".env")
|
||||||
: path.join(__dirname, ".env");
|
: path.join(__dirname, ".env");
|
||||||
@@ -102,6 +104,8 @@ fastify.register(userRoutes, { prefix: "/api" });
|
|||||||
fastify.register(anilistRoute, { prefix: "/api" });
|
fastify.register(anilistRoute, { prefix: "/api" });
|
||||||
fastify.register(listRoutes, { prefix: "/api" });
|
fastify.register(listRoutes, { prefix: "/api" });
|
||||||
|
|
||||||
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
try {
|
try {
|
||||||
initDatabase("anilist");
|
initDatabase("anilist");
|
||||||
@@ -110,12 +114,31 @@ const start = async () => {
|
|||||||
initDatabase("userdata");
|
initDatabase("userdata");
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
const refreshAll = async () => {
|
||||||
|
await refreshTrendingAnime();
|
||||||
|
await sleep(300);
|
||||||
|
await refreshTopAiringAnime();
|
||||||
|
await sleep(300);
|
||||||
|
await refreshTrendingBooks();
|
||||||
|
await sleep(300);
|
||||||
|
await refreshPopularBooks();
|
||||||
|
};
|
||||||
|
|
||||||
|
cron.schedule("*/30 * * * *", async () => {
|
||||||
|
try {
|
||||||
|
await refreshAll();
|
||||||
|
console.log("cache refreshed");
|
||||||
|
} catch (e) {
|
||||||
|
console.error("refresh failed", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await loadExtensions();
|
await loadExtensions();
|
||||||
|
await initHeadless();
|
||||||
|
await refreshAll();
|
||||||
|
|
||||||
await fastify.listen({ port: 54322, host: "0.0.0.0" });
|
await fastify.listen({ port: 54322, host: "0.0.0.0" });
|
||||||
console.log(`Server running at http://localhost:54322`);
|
console.log(`Server running at http://localhost:54322`);
|
||||||
|
|
||||||
await initHeadless();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fastify.log.error(err);
|
fastify.log.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { queryAll, queryOne } from '../../shared/database';
|
|||||||
import {Anime, Episode, Extension, StreamData} from '../types';
|
import {Anime, Episode, Extension, StreamData} from '../types';
|
||||||
|
|
||||||
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
||||||
const TTL = 60 * 60 * 6;
|
|
||||||
|
|
||||||
const ANILIST_URL = "https://graphql.anilist.co";
|
const ANILIST_URL = "https://graphql.anilist.co";
|
||||||
|
|
||||||
@@ -79,6 +78,54 @@ const MEDIA_FIELDS = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export async function refreshTrendingAnime(): Promise<void> {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
Page(page: 1, perPage: 10) {
|
||||||
|
media(type: ANIME, sort: TRENDING_DESC) { ${MEDIA_FIELDS} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await fetchAniList(query, {});
|
||||||
|
const list = data?.Page?.media || [];
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
await queryOne("DELETE FROM trending");
|
||||||
|
|
||||||
|
let rank = 1;
|
||||||
|
for (const anime of list) {
|
||||||
|
await queryOne(
|
||||||
|
"INSERT INTO trending (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
|
||||||
|
[rank++, anime.id, JSON.stringify(anime), now]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refreshTopAiringAnime(): Promise<void> {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
Page(page: 1, perPage: 10) {
|
||||||
|
media(type: ANIME, status: RELEASING, sort: SCORE_DESC) { ${MEDIA_FIELDS} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await fetchAniList(query, {});
|
||||||
|
const list = data?.Page?.media || [];
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
await queryOne("DELETE FROM top_airing");
|
||||||
|
|
||||||
|
let rank = 1;
|
||||||
|
for (const anime of list) {
|
||||||
|
await queryOne(
|
||||||
|
"INSERT INTO top_airing (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
|
||||||
|
[rank++, anime.id, JSON.stringify(anime), now]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchAniList(query: string, variables: any) {
|
async function fetchAniList(query: string, variables: any) {
|
||||||
const res = await fetch(ANILIST_URL, {
|
const res = await fetch(ANILIST_URL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -119,76 +166,16 @@ export async function getAnimeById(id: string | number): Promise<Anime | { error
|
|||||||
|
|
||||||
export async function getTrendingAnime(): Promise<Anime[]> {
|
export async function getTrendingAnime(): Promise<Anime[]> {
|
||||||
const rows = await queryAll(
|
const rows = await queryAll(
|
||||||
"SELECT full_data, updated_at FROM trending ORDER BY rank ASC LIMIT 10"
|
"SELECT full_data FROM trending ORDER BY rank ASC LIMIT 10"
|
||||||
);
|
);
|
||||||
|
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||||
if (rows.length) {
|
|
||||||
const expired = (Date.now() / 1000 - rows[0].updated_at) > TTL;
|
|
||||||
if (!expired) {
|
|
||||||
return rows.map((r: { full_data: string }) => JSON.parse(r.full_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
Page(page: 1, perPage: 10) {
|
|
||||||
media(type: ANIME, sort: TRENDING_DESC) { ${MEDIA_FIELDS} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const data = await fetchAniList(query, {});
|
|
||||||
const list = data?.Page?.media || [];
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
|
|
||||||
await queryOne("DELETE FROM trending");
|
|
||||||
let rank = 1;
|
|
||||||
|
|
||||||
for (const anime of list) {
|
|
||||||
await queryOne(
|
|
||||||
"INSERT INTO trending (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
|
|
||||||
[rank++, anime.id, JSON.stringify(anime), now]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTopAiringAnime(): Promise<Anime[]> {
|
export async function getTopAiringAnime(): Promise<Anime[]> {
|
||||||
const rows = await queryAll(
|
const rows = await queryAll(
|
||||||
"SELECT full_data, updated_at FROM top_airing ORDER BY rank ASC LIMIT 10"
|
"SELECT full_data FROM top_airing ORDER BY rank ASC LIMIT 10"
|
||||||
);
|
);
|
||||||
|
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||||
if (rows.length) {
|
|
||||||
const expired = (Date.now() / 1000 - rows[0].updated_at) > TTL;
|
|
||||||
if (!expired) {
|
|
||||||
return rows.map((r: { full_data: string }) => JSON.parse(r.full_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
Page(page: 1, perPage: 10) {
|
|
||||||
media(type: ANIME, status: RELEASING, sort: SCORE_DESC) { ${MEDIA_FIELDS} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const data = await fetchAniList(query, {});
|
|
||||||
const list = data?.Page?.media || [];
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
|
|
||||||
await queryOne("DELETE FROM top_airing");
|
|
||||||
let rank = 1;
|
|
||||||
|
|
||||||
for (const anime of list) {
|
|
||||||
await queryOne(
|
|
||||||
"INSERT INTO top_airing (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
|
|
||||||
[rank++, anime.id, JSON.stringify(anime), now]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchAnimeLocal(query: string): Promise<Anime[]> {
|
export async function searchAnimeLocal(query: string): Promise<Anime[]> {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { getAllExtensions, getBookExtensionsMap } from '../../shared/extensions'
|
|||||||
import { Book, Extension, ChapterWithProvider, ChapterContent } from '../types';
|
import { Book, Extension, ChapterWithProvider, ChapterContent } from '../types';
|
||||||
|
|
||||||
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
||||||
const TTL = 60 * 60 * 6;
|
|
||||||
const ANILIST_URL = "https://graphql.anilist.co";
|
const ANILIST_URL = "https://graphql.anilist.co";
|
||||||
|
|
||||||
async function fetchAniList(query: string, variables: any) {
|
async function fetchAniList(query: string, variables: any) {
|
||||||
@@ -134,18 +133,7 @@ export async function getBookById(id: string | number): Promise<Book | { error:
|
|||||||
return { error: "Book not found" };
|
return { error: "Book not found" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTrendingBooks(): Promise<Book[]> {
|
export async function refreshTrendingBooks(): Promise<void> {
|
||||||
const rows = await queryAll(
|
|
||||||
"SELECT full_data, updated_at FROM trending_books ORDER BY rank ASC LIMIT 10"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (rows.length) {
|
|
||||||
const expired = (Date.now() / 1000 - rows[0].updated_at) > TTL;
|
|
||||||
if (!expired) {
|
|
||||||
return rows.map((r: { full_data: string }) => JSON.parse(r.full_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
Page(page: 1, perPage: 10) {
|
Page(page: 1, perPage: 10) {
|
||||||
@@ -167,23 +155,9 @@ export async function getTrendingBooks(): Promise<Book[]> {
|
|||||||
[rank++, book.id, JSON.stringify(book), now]
|
[rank++, book.id, JSON.stringify(book), now]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function refreshPopularBooks(): Promise<void> {
|
||||||
export async function getPopularBooks(): Promise<Book[]> {
|
|
||||||
const rows = await queryAll(
|
|
||||||
"SELECT full_data, updated_at FROM popular_books ORDER BY rank ASC LIMIT 10"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (rows.length) {
|
|
||||||
const expired = (Date.now() / 1000 - rows[0].updated_at) > TTL;
|
|
||||||
if (!expired) {
|
|
||||||
return rows.map((r: { full_data: string }) => JSON.parse(r.full_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
Page(page: 1, perPage: 10) {
|
Page(page: 1, perPage: 10) {
|
||||||
@@ -205,10 +179,21 @@ export async function getPopularBooks(): Promise<Book[]> {
|
|||||||
[rank++, book.id, JSON.stringify(book), now]
|
[rank++, book.id, JSON.stringify(book), now]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getTrendingBooks(): Promise<Book[]> {
|
||||||
|
const rows = await queryAll(
|
||||||
|
"SELECT full_data FROM trending_books ORDER BY rank ASC LIMIT 10"
|
||||||
|
);
|
||||||
|
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPopularBooks(): Promise<Book[]> {
|
||||||
|
const rows = await queryAll(
|
||||||
|
"SELECT full_data FROM popular_books ORDER BY rank ASC LIMIT 10"
|
||||||
|
);
|
||||||
|
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||||
|
}
|
||||||
|
|
||||||
export async function searchBooksLocal(query: string): Promise<Book[]> {
|
export async function searchBooksLocal(query: string): Promise<Book[]> {
|
||||||
if (!query || query.length < 2) {
|
if (!query || query.length < 2) {
|
||||||
|
|||||||
10
docker/package-lock.json
generated
10
docker/package-lock.json
generated
@@ -17,6 +17,7 @@
|
|||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"node-addon-api": "^8.5.0",
|
"node-addon-api": "^8.5.0",
|
||||||
|
"node-cron": "^4.2.1",
|
||||||
"playwright-chromium": "^1.57.0",
|
"playwright-chromium": "^1.57.0",
|
||||||
"sqlite3": "^5.1.7"
|
"sqlite3": "^5.1.7"
|
||||||
},
|
},
|
||||||
@@ -2230,6 +2231,15 @@
|
|||||||
"node": "^18 || ^20 || >= 21"
|
"node": "^18 || ^20 || >= 21"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-cron": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-gyp": {
|
"node_modules/node-gyp": {
|
||||||
"version": "12.1.0",
|
"version": "12.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.1.0.tgz",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"node-addon-api": "^8.5.0",
|
"node-addon-api": "^8.5.0",
|
||||||
|
"node-cron": "^4.2.1",
|
||||||
"playwright-chromium": "^1.57.0",
|
"playwright-chromium": "^1.57.0",
|
||||||
"sqlite3": "^5.1.7"
|
"sqlite3": "^5.1.7"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,16 +4,15 @@ const fastify = require("fastify")({
|
|||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
|
const cron = require("node-cron");
|
||||||
const { initHeadless } = require("./electron/shared/headless");
|
const { initHeadless } = require("./electron/shared/headless");
|
||||||
const { initDatabase } = require("./electron/shared/database");
|
const { initDatabase } = require("./electron/shared/database");
|
||||||
const { loadExtensions } = require("./electron/shared/extensions");
|
const { loadExtensions } = require("./electron/shared/extensions");
|
||||||
|
const {refreshTrendingAnime, refreshTopAiringAnime} = require("./electron/api/anime/anime.service");
|
||||||
|
const {refreshPopularBooks, refreshTrendingBooks} = require("./electron/api/books/books.service");
|
||||||
const dotenv = require("dotenv");
|
const dotenv = require("dotenv");
|
||||||
const envPath = process.resourcesPath
|
|
||||||
? path.join(process.resourcesPath, ".env")
|
|
||||||
: path.join(__dirname, ".env");
|
|
||||||
|
|
||||||
// Attempt to load it and log the result to be sure
|
dotenv.config();
|
||||||
dotenv.config({ path: envPath });
|
|
||||||
|
|
||||||
const viewsRoutes = require("./electron/views/views.routes");
|
const viewsRoutes = require("./electron/views/views.routes");
|
||||||
const animeRoutes = require("./electron/api/anime/anime.routes");
|
const animeRoutes = require("./electron/api/anime/anime.routes");
|
||||||
@@ -65,6 +64,8 @@ fastify.register(userRoutes, { prefix: "/api" });
|
|||||||
fastify.register(anilistRoute, { prefix: "/api" });
|
fastify.register(anilistRoute, { prefix: "/api" });
|
||||||
fastify.register(listRoutes, { prefix: "/api" });
|
fastify.register(listRoutes, { prefix: "/api" });
|
||||||
|
|
||||||
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
try {
|
try {
|
||||||
initDatabase("anilist");
|
initDatabase("anilist");
|
||||||
@@ -72,12 +73,31 @@ const start = async () => {
|
|||||||
initDatabase("cache");
|
initDatabase("cache");
|
||||||
initDatabase("userdata");
|
initDatabase("userdata");
|
||||||
|
|
||||||
|
const refreshAll = async () => {
|
||||||
|
await refreshTrendingAnime();
|
||||||
|
await sleep(300);
|
||||||
|
await refreshTopAiringAnime();
|
||||||
|
await sleep(300);
|
||||||
|
await refreshTrendingBooks();
|
||||||
|
await sleep(300);
|
||||||
|
await refreshPopularBooks();
|
||||||
|
};
|
||||||
|
|
||||||
|
cron.schedule("*/30 * * * *", async () => {
|
||||||
|
try {
|
||||||
|
await refreshAll();
|
||||||
|
console.log("cache refreshed");
|
||||||
|
} catch (e) {
|
||||||
|
console.error("refresh failed", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await loadExtensions();
|
await loadExtensions();
|
||||||
|
await initHeadless();
|
||||||
|
await refreshAll();
|
||||||
|
|
||||||
await fastify.listen({ port: 54322, host: "0.0.0.0" });
|
await fastify.listen({ port: 54322, host: "0.0.0.0" });
|
||||||
console.log(`Server is now running!`);
|
console.log(`Server running at http://localhost:54322`);
|
||||||
|
|
||||||
await initHeadless();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fastify.log.error(err);
|
fastify.log.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { queryAll, queryOne } from '../../shared/database';
|
|||||||
import {Anime, Episode, Extension, StreamData} from '../types';
|
import {Anime, Episode, Extension, StreamData} from '../types';
|
||||||
|
|
||||||
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
||||||
const TTL = 60 * 60 * 6;
|
|
||||||
|
|
||||||
const ANILIST_URL = "https://graphql.anilist.co";
|
const ANILIST_URL = "https://graphql.anilist.co";
|
||||||
|
|
||||||
@@ -79,6 +78,54 @@ const MEDIA_FIELDS = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export async function refreshTrendingAnime(): Promise<void> {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
Page(page: 1, perPage: 10) {
|
||||||
|
media(type: ANIME, sort: TRENDING_DESC) { ${MEDIA_FIELDS} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await fetchAniList(query, {});
|
||||||
|
const list = data?.Page?.media || [];
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
await queryOne("DELETE FROM trending");
|
||||||
|
|
||||||
|
let rank = 1;
|
||||||
|
for (const anime of list) {
|
||||||
|
await queryOne(
|
||||||
|
"INSERT INTO trending (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
|
||||||
|
[rank++, anime.id, JSON.stringify(anime), now]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refreshTopAiringAnime(): Promise<void> {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
Page(page: 1, perPage: 10) {
|
||||||
|
media(type: ANIME, status: RELEASING, sort: SCORE_DESC) { ${MEDIA_FIELDS} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const data = await fetchAniList(query, {});
|
||||||
|
const list = data?.Page?.media || [];
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
await queryOne("DELETE FROM top_airing");
|
||||||
|
|
||||||
|
let rank = 1;
|
||||||
|
for (const anime of list) {
|
||||||
|
await queryOne(
|
||||||
|
"INSERT INTO top_airing (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
|
||||||
|
[rank++, anime.id, JSON.stringify(anime), now]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchAniList(query: string, variables: any) {
|
async function fetchAniList(query: string, variables: any) {
|
||||||
const res = await fetch(ANILIST_URL, {
|
const res = await fetch(ANILIST_URL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -119,76 +166,16 @@ export async function getAnimeById(id: string | number): Promise<Anime | { error
|
|||||||
|
|
||||||
export async function getTrendingAnime(): Promise<Anime[]> {
|
export async function getTrendingAnime(): Promise<Anime[]> {
|
||||||
const rows = await queryAll(
|
const rows = await queryAll(
|
||||||
"SELECT full_data, updated_at FROM trending ORDER BY rank ASC LIMIT 10"
|
"SELECT full_data FROM trending ORDER BY rank ASC LIMIT 10"
|
||||||
);
|
);
|
||||||
|
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||||
if (rows.length) {
|
|
||||||
const expired = (Date.now() / 1000 - rows[0].updated_at) > TTL;
|
|
||||||
if (!expired) {
|
|
||||||
return rows.map((r: { full_data: string }) => JSON.parse(r.full_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
Page(page: 1, perPage: 10) {
|
|
||||||
media(type: ANIME, sort: TRENDING_DESC) { ${MEDIA_FIELDS} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const data = await fetchAniList(query, {});
|
|
||||||
const list = data?.Page?.media || [];
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
|
|
||||||
await queryOne("DELETE FROM trending");
|
|
||||||
let rank = 1;
|
|
||||||
|
|
||||||
for (const anime of list) {
|
|
||||||
await queryOne(
|
|
||||||
"INSERT INTO trending (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
|
|
||||||
[rank++, anime.id, JSON.stringify(anime), now]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTopAiringAnime(): Promise<Anime[]> {
|
export async function getTopAiringAnime(): Promise<Anime[]> {
|
||||||
const rows = await queryAll(
|
const rows = await queryAll(
|
||||||
"SELECT full_data, updated_at FROM top_airing ORDER BY rank ASC LIMIT 10"
|
"SELECT full_data FROM top_airing ORDER BY rank ASC LIMIT 10"
|
||||||
);
|
);
|
||||||
|
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||||
if (rows.length) {
|
|
||||||
const expired = (Date.now() / 1000 - rows[0].updated_at) > TTL;
|
|
||||||
if (!expired) {
|
|
||||||
return rows.map((r: { full_data: string }) => JSON.parse(r.full_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
query {
|
|
||||||
Page(page: 1, perPage: 10) {
|
|
||||||
media(type: ANIME, status: RELEASING, sort: SCORE_DESC) { ${MEDIA_FIELDS} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const data = await fetchAniList(query, {});
|
|
||||||
const list = data?.Page?.media || [];
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
|
|
||||||
await queryOne("DELETE FROM top_airing");
|
|
||||||
let rank = 1;
|
|
||||||
|
|
||||||
for (const anime of list) {
|
|
||||||
await queryOne(
|
|
||||||
"INSERT INTO top_airing (rank, id, full_data, updated_at) VALUES (?, ?, ?, ?)",
|
|
||||||
[rank++, anime.id, JSON.stringify(anime), now]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function searchAnimeLocal(query: string): Promise<Anime[]> {
|
export async function searchAnimeLocal(query: string): Promise<Anime[]> {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { getAllExtensions, getBookExtensionsMap } from '../../shared/extensions'
|
|||||||
import { Book, Extension, ChapterWithProvider, ChapterContent } from '../types';
|
import { Book, Extension, ChapterWithProvider, ChapterContent } from '../types';
|
||||||
|
|
||||||
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
||||||
const TTL = 60 * 60 * 6;
|
|
||||||
const ANILIST_URL = "https://graphql.anilist.co";
|
const ANILIST_URL = "https://graphql.anilist.co";
|
||||||
|
|
||||||
async function fetchAniList(query: string, variables: any) {
|
async function fetchAniList(query: string, variables: any) {
|
||||||
@@ -134,18 +133,7 @@ export async function getBookById(id: string | number): Promise<Book | { error:
|
|||||||
return { error: "Book not found" };
|
return { error: "Book not found" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTrendingBooks(): Promise<Book[]> {
|
export async function refreshTrendingBooks(): Promise<void> {
|
||||||
const rows = await queryAll(
|
|
||||||
"SELECT full_data, updated_at FROM trending_books ORDER BY rank ASC LIMIT 10"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (rows.length) {
|
|
||||||
const expired = (Date.now() / 1000 - rows[0].updated_at) > TTL;
|
|
||||||
if (!expired) {
|
|
||||||
return rows.map((r: { full_data: string }) => JSON.parse(r.full_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
Page(page: 1, perPage: 10) {
|
Page(page: 1, perPage: 10) {
|
||||||
@@ -167,23 +155,9 @@ export async function getTrendingBooks(): Promise<Book[]> {
|
|||||||
[rank++, book.id, JSON.stringify(book), now]
|
[rank++, book.id, JSON.stringify(book), now]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function refreshPopularBooks(): Promise<void> {
|
||||||
export async function getPopularBooks(): Promise<Book[]> {
|
|
||||||
const rows = await queryAll(
|
|
||||||
"SELECT full_data, updated_at FROM popular_books ORDER BY rank ASC LIMIT 10"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (rows.length) {
|
|
||||||
const expired = (Date.now() / 1000 - rows[0].updated_at) > TTL;
|
|
||||||
if (!expired) {
|
|
||||||
return rows.map((r: { full_data: string }) => JSON.parse(r.full_data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
Page(page: 1, perPage: 10) {
|
Page(page: 1, perPage: 10) {
|
||||||
@@ -205,10 +179,21 @@ export async function getPopularBooks(): Promise<Book[]> {
|
|||||||
[rank++, book.id, JSON.stringify(book), now]
|
[rank++, book.id, JSON.stringify(book), now]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getTrendingBooks(): Promise<Book[]> {
|
||||||
|
const rows = await queryAll(
|
||||||
|
"SELECT full_data FROM trending_books ORDER BY rank ASC LIMIT 10"
|
||||||
|
);
|
||||||
|
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPopularBooks(): Promise<Book[]> {
|
||||||
|
const rows = await queryAll(
|
||||||
|
"SELECT full_data FROM popular_books ORDER BY rank ASC LIMIT 10"
|
||||||
|
);
|
||||||
|
return rows.map((r: { full_data: string; }) => JSON.parse(r.full_data));
|
||||||
|
}
|
||||||
|
|
||||||
export async function searchBooksLocal(query: string): Promise<Book[]> {
|
export async function searchBooksLocal(query: string): Promise<Book[]> {
|
||||||
if (!query || query.length < 2) {
|
if (!query || query.length < 2) {
|
||||||
|
|||||||
Reference in New Issue
Block a user