From deac2f422f41244704a084d8da2478e337f0e9d2 Mon Sep 17 00:00:00 2001 From: Tachibana Shin Date: Sun, 23 Apr 2023 07:15:40 +0000 Subject: [PATCH 1/6] add fn search myanimelist --- src/apis/parser/myanimelist/episodes.ts | 1 + src/apis/parser/myanimelist/search.ts | 37 ++ src/apis/runs/myanimelist/search.spec.ts | 457 +++++++++++++++++++++++ src/apis/runs/myanimelist/search.ts | 198 ++++++++++ src/apis/workers/myanimelist/search.ts | 4 + 5 files changed, 697 insertions(+) create mode 100644 src/apis/parser/myanimelist/episodes.ts create mode 100644 src/apis/parser/myanimelist/search.ts create mode 100644 src/apis/runs/myanimelist/search.spec.ts create mode 100644 src/apis/runs/myanimelist/search.ts create mode 100644 src/apis/workers/myanimelist/search.ts diff --git a/src/apis/parser/myanimelist/episodes.ts b/src/apis/parser/myanimelist/episodes.ts new file mode 100644 index 00000000..a6b1ee63 --- /dev/null +++ b/src/apis/parser/myanimelist/episodes.ts @@ -0,0 +1 @@ +export function AnimeEpisodes(html) {} diff --git a/src/apis/parser/myanimelist/search.ts b/src/apis/parser/myanimelist/search.ts new file mode 100644 index 00000000..5e1bad69 --- /dev/null +++ b/src/apis/parser/myanimelist/search.ts @@ -0,0 +1,37 @@ +import { parserDom } from "../__helpers__/parserDom" + +interface AnimeItem { + name_lower: string + search_name: string + id: number + type: string + name: string + url: string + image_url: string +} + +export default function AnimeSearch(html: string) { + const $ = parserDom(html) + + return $(".js-categories-seasonal tr") + .toArray() + .map((tr) => { + const url = $(tr).find(".hoverinfo_trigger").attr("href") + if (!url) return null + + const indexParamAnime = url.indexOf("/anime/") + 7 + + if (indexParamAnime === -1) return null + + const id = parseInt( + url.slice(indexParamAnime, url.indexOf("/", indexParamAnime)) + ) + const name = $(tr).find("strong").text() + // eslint-disable-next-line camelcase + const image_url = $(tr).find("img").attr("src") + + // eslint-disable-next-line camelcase + return { id, type: "anime", name, url, image_url } + }) + .filter(Boolean) as AnimeItem[] +} diff --git a/src/apis/runs/myanimelist/search.spec.ts b/src/apis/runs/myanimelist/search.spec.ts new file mode 100644 index 00000000..f13922c9 --- /dev/null +++ b/src/apis/runs/myanimelist/search.spec.ts @@ -0,0 +1,457 @@ +import { getAmimeMyAnimeList } from "./search" + +describe("episodes", () => { + describe("getAmimeMyAnimeList", () => { + test("Gate: Jieitai Kanochi nite, Kaku Tatakaeri", async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const anime = ( + await getAmimeMyAnimeList( + "Cổng chiến tranh", + "GATE, Gate: Thus the JSDF Fought There!, Gate: Jieitai Kanochi nite, Kaku Tatakaeri" + ) + )! + + expect(anime.id).toBe(28907) + expect(anime.type).toBe("anime") + expect(anime.url).toBe("https://myanimelist.net/anime/28907/Gate__Jieitai_Kanochi_nite_Kaku_Tatakaeri") + expect(anime.name).toBe("Gate: Jieitai Kanochi nite, Kaku Tatakaeri") + }) + + test("Gate: Jieitai Kanochi nite, Kaku Tatakaeri 2nd Season", async () => { + const anime = await getAmimeMyAnimeList( + "Cổng chiến tranh SS2", + "GATE, Gate: Thus the JSDF Fought There! Fire Dragon Arc, Gate: Jieitai Kanochi nite, Kaku Tatakaeri - Enryuu-hen, Gate: Jieitai Kanochi nite, Kaku Tatakaeri 2nd Season" + ) + + expect(anime.id).toBe(31637) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/31637/Gate__Jieitai_Kanochi_nite_Kaku_Tatakaeri_Part_2" + ) + expect(anime.name).toBe( + "Gate: Jieitai Kanochi nite, Kaku Tatakaeri Part 2" + ) + }) + + test("Tonikaku Kawaii", async () => { + const anime = await getAmimeMyAnimeList( + "Tonikaku Kawaii", + "TONIKAWA: Over the Moon For You:, Generally Cute, Fly Me to the Moon" + ) + + expect(anime.id).toBe(41389) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/41389/Tonikaku_Kawaii" + ) + expect(anime.name).toBe("Tonikaku Kawaii") + }) + + test("Tonikaku Kawaii: SNS", async () => { + const anime = await getAmimeMyAnimeList( + "Tonikaku Kawaii: SNS", + "Tonikaku Kawaii OVA, Tonikawa" + ) + + expect(anime.id).toBe(44931) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/44931/Tonikaku_Kawaii__SNS" + ) + expect(anime.name).toBe("Tonikaku Kawaii: SNS") + }) + + test("Tonikaku Kawaii: Seifuku", async () => { + const anime = await getAmimeMyAnimeList( + "Tonikaku Kawaii: Seifuku", + "Tonikawa: Over the Moon for You - Uniform" + ) + + expect(anime.id).toBe(51533) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/51533/Tonikaku_Kawaii__Seifuku" + ) + expect(anime.name).toBe("Tonikaku Kawaii: Seifuku") + }) + + test("Tonikaku Kawaii 2nd Season", async () => { + const anime = await getAmimeMyAnimeList( + "Tonikaku Kawaii 2nd Season", + "Tonikawa: Over the Moon for You 2nd Season, Tonikawa: Over The Moon For You Season 2" + ) + + expect(anime.id).toBe(50307) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/50307/Tonikaku_Kawaii_2nd_Season" + ) + expect(anime.name).toBe("Tonikaku Kawaii 2nd Season") + }) + + test("86", async () => { + const anime = await getAmimeMyAnimeList("86", "Eighty Six") + + expect(anime.id).toBe(41457) + expect(anime.type).toBe("anime") + expect(anime.url).toBe("https://myanimelist.net/anime/41457/86") + expect(anime.name).toBe("86") + }) + + test("86 Special Edition: Senya ni Akaku Hinageshi no Saku", async () => { + const anime = await getAmimeMyAnimeList( + "86 Special Edition: Senya ni Akaku Hinageshi no Saku", + "86-Eighty Six- Special Edition - Coquelicots Blooming Across the Battlefield, Eighty Six Special Edition" + ) + + expect(anime.id).toBe(49235) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/49235/86_Special_Edition__Senya_ni_Akaku_Hinageshi_no_Saku" + ) + expect(anime.name).toBe( + "86 Special Edition: Senya ni Akaku Hinageshi no Saku" + ) + }) + + test("86 2nd Season", async () => { + const anime = await getAmimeMyAnimeList( + "86 2nd Season", + "86 Eighty-Six, Eighty Six 2nd Season" + ) + + expect(anime.id).toBe(48569) + expect(anime.type).toBe("anime") + expect(anime.url).toBe("https://myanimelist.net/anime/48569/86_Part_2") + expect(anime.name).toBe("86 Part 2") + }) + + test("Mahoutsukai no Yome: Hoshi Matsu Hito", async () => { + const anime = await getAmimeMyAnimeList( + "Mahoutsukai no Yome: Hoshi Matsu Hito", + "The Ancient Magus' Bride: Those Awaiting a Star, The Magician's Bride" + ) + + expect(anime.id).toBe(32902) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/32902/Mahoutsukai_no_Yome__Hoshi_Matsu_Hito" + ) + expect(anime.name).toBe("Mahoutsukai no Yome: Hoshi Matsu Hito") + }) + + test("Mahoutsukai no Yome", async () => { + const anime = await getAmimeMyAnimeList( + "Mahoutsukai no Yome", + "The Ancient Magus' Bride, The Magician's Bride, Cô Dâu Pháp Sư" + ) + + expect(anime.id).toBe(35062) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/35062/Mahoutsukai_no_Yome" + ) + expect(anime.name).toBe("Mahoutsukai no Yome") + }) + + test("Mahoutsukai no Yome: Nishi no Shounen to Seiran no Kishi", async () => { + const anime = await getAmimeMyAnimeList( + "Mahoutsukai no Yome: Nishi no Shounen to Seiran no Kishi", + "The Ancient Magus' Bride: The Boy from the West and the Knight of the Blue Storm, The Ancient Magus' Bride OVA, Mahoutsukai no Yome OVA, Mahoutsuaki no Yome OAD" + ) + + expect(anime.id).toBe(48438) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/48438/Mahoutsukai_no_Yome__Nishi_no_Shounen_to_Seiran_no_Kishi" + ) + expect(anime.name).toBe( + "Mahoutsukai no Yome: Nishi no Shounen to Seiran no Kishi" + ) + }) + + test("Mahoutsukai no Yome Season 2", async () => { + const anime = await getAmimeMyAnimeList( + "Cô Dâu Pháp Sư Mùa 2", + "Mahoutsukai no Yome Season 2, The Ancient Magus' Bride Season 2, The Ancient Magus Bride 2, Mahoutsukai no Yome 2, Mahoyome" + ) + + + expect(anime.id).toBe(52955) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/52955/Mahoutsukai_no_Yome_Season_2" + ) + expect(anime.name).toBe("Mahoutsukai no Yome Season 2") + }) + + test("Anh Thợ Saitou Đa Năng Ở Dị Giới", async () => { + const anime = await getAmimeMyAnimeList( + "Anh Thợ Saitou Đa Năng Ở Dị Giới", + "Benriya Saitou-san, Isekai ni Iku, Handyman Saitou in Another Worlde" + ) + + expect(anime.id).toBe(50854) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/50854/Benriya_Saitou-san_Isekai_ni_Iku" + ) + expect(anime.name).toBe("Benriya Saitou-san, Isekai ni Iku") + }) + + test("Naruto", async () => { + const anime = await getAmimeMyAnimeList("Naruto", "Naruto, NARUTO") + + expect(anime.id).toBe(20) + expect(anime.type).toBe("anime") + expect(anime.url).toBe("https://myanimelist.net/anime/20/Naruto") + expect(anime.name).toBe("Naruto") + }) + + test("Naruto: Sức Mạnh Vĩ Thú", async () => { + const anime = await getAmimeMyAnimeList( + "Naruto: Sức Mạnh Vĩ Thú", + "Naruto Shippuden, Naruto Hurricane Chronicles, Naruto: Shippuuden" + ) + + expect(anime.id).toBe(1735) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/1735/Naruto__Shippuuden" + ) + expect(anime.name).toBe("Naruto: Shippuuden") + }) + + test("Thám Tử Lừng Danh Conan", async () => { + const anime = await getAmimeMyAnimeList( + "Thám Tử Lừng Danh Conan", + "Detective Conan, Case Closed, Meitantei Conan" + ) + + expect(anime.id).toBe(235) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/235/Detective_Conan" + ) + expect(anime.name).toBe("Detective Conan") + }) + + test("Detective Conan Movie 26: Kurogane no Submarine", async () => { + const anime = await getAmimeMyAnimeList( + "Detective Conan Movie 26: Kurogane no Submarine", + "Meitantei Conan: Kurogane no Submarine" + ) + + expect(anime.id).toBe(53540) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/53540/Detective_Conan_Movie_26__Kurogane_no_Submarine" + ) + expect(anime.name).toBe("Detective Conan Movie 26: Kurogane no Submarine") + }) + + test("Mở Ra Một Thế Giới Tuyệt Vời", async () => { + const anime = await getAmimeMyAnimeList( + "Mở Ra Một Thế Giới Tuyệt Vời", + "KonoSuba: God's Blessing on This Wonderful World!, Give Blessings to This Wonderful World!,Kono Subarashii Sekai ni Shukufuku wo!" + ) + + expect(anime.id).toBe(30831) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/30831/Kono_Subarashii_Sekai_ni_Shukufuku_wo" + ) + expect(anime.name).toBe("Kono Subarashii Sekai ni Shukufuku wo!") + }) + + test("Mở Ra Một Thế Giới Tuyệt Vời OVA", async () => { + const anime = await getAmimeMyAnimeList( + "Mở Ra Một Thế Giới Tuyệt Vời OVA", + "KonoSuba OVA, A Blessing to this Wonderful Choker!, Kono Subarashii Choker ni Shufuku wo!, Kono Subarashii Sekai ni Shukufuku wo! OVA" + ) + + + + expect(anime.id).toBe(32380) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/32380/Kono_Subarashii_Sekai_ni_Shukufuku_wo_Kono_Subarashii_Choker_ni_Shukufuku_wo" + ) + expect(anime.name).toBe("Kono Subarashii Sekai ni Shukufuku wo! Kono Subarashii Choker ni Shukufuku wo!") + }) + + test("Mở Ra Một Thế Giới Tuyệt Vời 2", async () => { + const anime = await getAmimeMyAnimeList( + "Mở Ra Một Thế Giới Tuyệt Vời 2", + "KonoSuba: God's Blessing on This Wonderful World! 2, Give Blessings to This Wonderful World! 2, Kono Subarashii Sekai ni Shukufuku wo! 2" + ) + + + + expect(anime.id).toBe(32937) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/32937/Kono_Subarashii_Sekai_ni_Shukufuku_wo_2" + ) + expect(anime.name).toBe( + "Kono Subarashii Sekai ni Shukufuku wo! 2" + ) + }) + + test("Mở Ra Một Thế Giới Tuyệt Vời OVA 2", async () => { + const anime = await getAmimeMyAnimeList( + "Mở Ra Một Thế Giới Tuyệt Vời OVA 2", + "KonoSuba: God's Blessing on This Wonderful World! Second Season OVA, KonoSuba: God's Blessing on This Wonderful World! Second Season OVA, Kono Subarashii Sekai ni Shukufuku wo! 2 OVA" + ) + + + + expect(anime.id).toBe(34626) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/34626/Kono_Subarashii_Sekai_ni_Shukufuku_wo_2__Kono_Subarashii_Geijutsu_ni_Shukufuku_wo" + ) + expect(anime.name).toBe( + "Kono Subarashii Sekai ni Shukufuku wo! 2: Kono Subarashii Geijutsu ni Shukufuku wo!" + ) + }) + + test("Kono Subarashii Sekai ni Bakuen wo!", async () => { + const anime = await getAmimeMyAnimeList( + "Kono Subarashii Sekai ni Bakuen wo!", + "Konosuba: An Explosion on This Wonderful World!" + ) + + + + expect(anime.id).toBe(51958) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/51958/Kono_Subarashii_Sekai_ni_Bakuen_wo" + ) + expect(anime.name).toBe("Kono Subarashii Sekai ni Bakuen wo!") + }) + + test("Onegai☆Teacher", async () => { + const anime = await getAmimeMyAnimeList( + "Onegai☆Teacher", + "Please Teacher!, Onegai Sensei" + ) + + + + expect(anime.id).toBe(195) + expect(anime.type).toBe("anime") + expect(anime.url).toBe("https://myanimelist.net/anime/195/Onegai☆Teacher") + expect(anime.name).toBe("Onegai☆Teacher") + }) + + test("Karakai Jouzu no Takagi-san", async () => { + const anime = await getAmimeMyAnimeList( + "Karakai Jouzu no Takagi-san", + "Skilled Teaser Takagi-san" + ) + + + + expect(anime.id).toBe(35860) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/35860/Karakai_Jouzu_no_Takagi-san" + ) + expect(anime.name).toBe("Karakai Jouzu no Takagi-san") + }) + + test("Nhất Quỷ Nhì Ma, Thứ Ba Takagi Phần 3", async () => { + const anime = await getAmimeMyAnimeList( + "Nhất Quỷ Nhì Ma, Thứ Ba Takagi Phần 3", + "Karakai Jouzu no Takagi-san 3, Skilled Teaser Takagi-san 3rd Season, Karakai Jouzu no Takagi-san Third Season, Teasing Master Takagi-san" + ) + + + + expect(anime.id).toBe(49721) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/49721/Karakai_Jouzu_no_Takagi-san_3" + ) + expect(anime.name).toBe("Karakai Jouzu no Takagi-san 3") + }) + + test("Tokyo Mew Mew New ♡ 2nd Season", async () => { + const anime = await getAmimeMyAnimeList( + "Tokyo Mew Mew New ♡ 2nd Season", + "東京ミュウミュウ にゅ~♡" + ) + + + + expect(anime.id).toBe(53097) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/53097/Tokyo_Mew_Mew_New_♡_2nd_Season" + ) + expect(anime.name).toBe("Tokyo Mew Mew New ♡ 2nd Season") + }) + + test("Tokyo Mew Mew New ♡", async () => { + const anime = await getAmimeMyAnimeList( + "Tokyo Mew Mew New ♡", + "東京ミュウミュウ にゅ~♡" + ) + + + + expect(anime.id).toBe(41589) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/41589/Tokyo_Mew_Mew_New_♡" + ) + expect(anime.name).toBe("Tokyo Mew Mew New ♡") + }) + + test("Tokyo Mew Mew", async () => { + const anime = await getAmimeMyAnimeList( + "Tokyo Mew Mew", + "Tokyo Mew Mew, Mew Mew Power" + ) + + + + expect(anime.id).toBe(687) + expect(anime.type).toBe("anime") + expect(anime.url).toBe("https://myanimelist.net/anime/687/Tokyo_Mew_Mew") + expect(anime.name).toBe("Tokyo Mew Mew") + }) + + test("Isekai wa Smartphone to Tomo ni.", async () => { + const anime = await getAmimeMyAnimeList( + "Isekai wa Smartphone to Tomo ni.", + "TIn Another World With My Smartphone, In a Different World with a Smartphone" + ) + + + + expect(anime.id).toBe(35203) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/35203/Isekai_wa_Smartphone_to_Tomo_ni" + ) + expect(anime.name).toBe("Isekai wa Smartphone to Tomo ni.") + }) + + test("Isekai wa Smartphone to Tomo ni. 2nd Season", async () => { + const anime = await getAmimeMyAnimeList( + "Isekai wa Smartphone to Tomo ni. 2nd Season", + "In Another World With My Smartphone 2, In Another World With My Smartphone 2nd Season, In a Different World with a Smartphone." + ) + + expect(anime.id).toBe(51632) + expect(anime.type).toBe("anime") + expect(anime.url).toBe( + "https://myanimelist.net/anime/51632/Isekai_wa_Smartphone_to_Tomo_ni_2" + ) + expect(anime.name).toBe("Isekai wa Smartphone to Tomo ni. 2") + }) + }) +}) diff --git a/src/apis/runs/myanimelist/search.ts b/src/apis/runs/myanimelist/search.ts new file mode 100644 index 00000000..4c2d840b --- /dev/null +++ b/src/apis/runs/myanimelist/search.ts @@ -0,0 +1,198 @@ +// first in query id anime + +import MiniSearch from "minisearch" +import type MyAnimeListSearchParser from "src/apis/parser/myanimelist/search" + +import { useCache } from "../../useCache" +import Worker from "../../workers/myanimelist/search?worker" +import { PostWorker } from "../../wrap-worker" + +const PREFIX_SEASON = "mùa|season|part|ss" +const BEFORE_SEASON = "ss|\\wd" + +async function runRawSearch(query: string) { + return await useCache(`myanimelist/search/${query}`, async () => { + const html = await fetch( + `https://myanimelist.net/anime.php?cat=anime&q=${encodeURIComponent( + query + )}&type=0&score=0&status=0&p=0&r=0&sm=0&sd=0&sy=0&em=0&ed=0&ey=0&c%5B%5D=a&c%5B%5D=b&c%5B%5D=c&c%5B%5D=f` + ).then((res) => res.text()) + + if (import.meta.env.MODE === "test") { + return import("../../parser/myanimelist/search").then((res) => + res.default(html) + ) + } + + return PostWorker(Worker, "123") + }) +} + +const createRegExpGetSeason = (num: string | "\\d+") => + new RegExp( + "(?:" + + `(?:(?:${PREFIX_SEASON})\\s*(${num}))` + + "|" + + `(?:(${num})(?:${BEFORE_SEASON}))(?:\\s+(?:season))` + + ")$", + "i" + ) +const defRegExpGetSeason = createRegExpGetSeason("\\d+") + +const basicExactOthername = (name: string, othername: string): boolean => { + // console.log({ name, othername, e: name === othername }) + + // check in only + if (othername === name) return true + + // check in first other name + if (othername.startsWith(name + ",")) return true + + // check inside other name + if (othername.includes("," + name + ",")) return true + if (othername.includes(", " + name + ",")) return true + + // check in last other name + if (othername.endsWith(", " + name)) return true + if (othername.endsWith("," + name)) return true + + return false +} + +function sliceOthername(othername: string[], size: number, max: number) { + // eslint-disable-next-line functional/no-let + let index = 0 + // eslint-disable-next-line functional/no-let + let off = (othername.length - 1) * 2 // is space for ", " + while (size + off > max) { + size -= othername[index].length + off -= 2 + index++ + } + + return othername.slice(index) +} + +const rPrefixSeason = new RegExp(`${PREFIX_SEASON} (\\d+)`, "gi") +const rBeforeSeason = new RegExp(`(\\d+)${BEFORE_SEASON}\\s+(?:season)?`, "gi") +export async function getAmimeMyAnimeList( + name: string, + othername: string +): Promise[0]> { + const nameRmd = name + .replace(rPrefixSeason, " $1") + .replace(rBeforeSeason, "$1") + const otherRmd = othername + .replace(rPrefixSeason, " $1") + .replace(rBeforeSeason, "$1") + + const query = + nameRmd.slice(0, 100) + + (nameRmd.length > 100 + ? "" + : " " + + sliceOthername( + otherRmd.split(",").filter((item) => item.trim()), + otherRmd.length, + 100 - nameRmd.length - 1 /** 1 is val of space */ + ).join(",")) + + const keyword = query + console.log({ keyword }, keyword.length) + try { + return runRawSearch(keyword).then((anime) => { + // console.log(keyword) + // console.dir(data, { depth: null }) + // eslint-disable-next-line functional/no-throw-statement + if (anime.length === 0) throw new Error("not_found") + + const ss = name.match(defRegExpGetSeason)?.slice(1).filter(Boolean).at(-1) + const regexpSeasonSS = createRegExpGetSeason(ss ?? "\\d+") + + name = name.toLowerCase().trim() + const nameRmdSeason = ss + ? name.replace(defRegExpGetSeason, "").trim() + : name + const matchByName = anime.find((item) => { + const itemName = ss + ? item.name.replace(defRegExpGetSeason, "").toLowerCase().trim() + : item.name.toLowerCase() + + if (name === itemName || nameRmdSeason === itemName) { + return ss + ? !!item.name.match(regexpSeasonSS) + : !item.name.match(regexpSeasonSS) + } + + return false + }) + if (matchByName) return matchByName + + const othernameRmdSeason = ss + ? othername + .split(",") + .map((name) => { + return name + .trim() + .replace(defRegExpGetSeason, "") + .toLowerCase() + .trim() + }) + .join(",") + : othername.toLowerCase().trim() + const matchByOthername = anime.find((item, id) => { + const itemName = ss + ? item.name.replace(defRegExpGetSeason, "").toLowerCase().trim() + : item.name.toLowerCase() + // console.log(item.name, defRegExpGetSeason, itemName) + + if (basicExactOthername(itemName, othernameRmdSeason)) { + return ss + ? !!item.name.match(regexpSeasonSS) + : !item.name.match(regexpSeasonSS) + } + + return false + }) + + if (matchByOthername) return matchByOthername + + const miniSearch = new MiniSearch({ + fields: ["name_lower"], // fields to index for full-text search + storeFields: Object.keys(anime[0]), // fields to return with search results + }) + + anime.forEach((item) => { + item.name_lower = item.name.trim().toLowerCase() + }) + // Index all documents + miniSearch.addAll(anime) + + const result = miniSearch.search(otherRmd)[0] as unknown as Awaited>[0] + console.log(otherRmd, result) + // console.log(otherRmd, result.slice(0, 10)) + // .filter((item) => { + // return ss + // ? !!item.name.match(regexpSeasonSS) + // : !item.name.match(regexpSeasonSS) + // }) as unknown as Awaited>[0] + if (result) return result + + // eslint-disable-next-line functional/no-throw-statement + throw new Error("not_found") + }) + } catch (err) { + if ((err as Error | null)?.message?.startsWith("Unexpected token ")) { + // limit request + await new Promise((resolve) => setTimeout(resolve, 5000)) + return getAmimeMyAnimeList(name, othername) + } + + // eslint-disable-next-line functional/no-throw-statement + throw err + } +} + +export function getEpisodesAnimeMyAnimeList(url: string) { + return fetch(`${url}/episode`).then((res) => res.text()) +} diff --git a/src/apis/workers/myanimelist/search.ts b/src/apis/workers/myanimelist/search.ts new file mode 100644 index 00000000..86ea8cd0 --- /dev/null +++ b/src/apis/workers/myanimelist/search.ts @@ -0,0 +1,4 @@ +import { WrapWorker } from "../..//wrap-worker" +import Search from "../../parser/myanimelist/search" + +WrapWorker(Search) From 9ee2df9a15e06c308a4774d001912661c7fb4b0d Mon Sep 17 00:00:00 2001 From: Tachibana Shin Date: Sun, 23 Apr 2023 16:24:10 +0000 Subject: [PATCH 2/6] feat, add episodes myanimelist --- package.json | 1 + pnpm-lock.yaml | 7 ++ setup.vitest.ts | 24 +++++ src/apis/parser/myanimelist/episodes.ts | 19 +++- src/apis/runs/myanimelist/episodes.spec.ts | 106 +++++++++++++++++++++ src/apis/runs/myanimelist/episodes.ts | 27 ++++++ src/apis/runs/myanimelist/search.ts | 13 ++- src/apis/workers/myanimelist/episodes.ts | 4 + src/boot/installed-extension.ts | 4 +- vitest.config.ts | 8 ++ 10 files changed, 203 insertions(+), 10 deletions(-) create mode 100644 setup.vitest.ts create mode 100644 src/apis/runs/myanimelist/episodes.spec.ts create mode 100644 src/apis/runs/myanimelist/episodes.ts create mode 100644 src/apis/workers/myanimelist/episodes.ts diff --git a/package.json b/package.json index b1d1ff40..58b48f15 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "js-cookie": "^3.0.1", "lz-string": "^1.4.4", "message-port-api": "^0.0.5", + "minisearch": "^6.0.1", "pinia": "^2.0.29", "pinia-plugin-persistedstate": "^3.0.2", "pinia-plugin-persistedstate-2": "^2.0.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2dee8c6..5111f0da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,6 +70,9 @@ dependencies: message-port-api: specifier: ^0.0.5 version: 0.0.5 + minisearch: + specifier: ^6.0.1 + version: 6.0.1 pinia: specifier: ^2.0.29 version: 2.0.29(typescript@4.9.4)(vue@3.2.45) @@ -6744,6 +6747,10 @@ packages: resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} dev: true + /minisearch@6.0.1: + resolution: {integrity: sha512-Ly1w0nHKnlhAAh6/BF/+9NgzXfoJxaJ8nhopFhQ3NcvFJrFIL+iCg9gw9e9UMBD+XIsp/RyznJ/o5UIe5Kw+kg==} + dev: false + /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: true diff --git a/setup.vitest.ts b/setup.vitest.ts new file mode 100644 index 00000000..dc69b2bd --- /dev/null +++ b/setup.vitest.ts @@ -0,0 +1,24 @@ +window.Http = { + version: "0.0.21", + get(options) { + return fetch(options.url) + .then((res) => res.text()) + .then((text) => { + return { + data: text, + } + }) + }, + post(options) { + return fetch(options.url, { + method: "POST", + body: JSON.stringify(options.data), + }) + .then((res) => res.text()) + .then((text) => { + return { + data: text, + } + }) + }, +} diff --git a/src/apis/parser/myanimelist/episodes.ts b/src/apis/parser/myanimelist/episodes.ts index a6b1ee63..f6404b8f 100644 --- a/src/apis/parser/myanimelist/episodes.ts +++ b/src/apis/parser/myanimelist/episodes.ts @@ -1 +1,18 @@ -export function AnimeEpisodes(html) {} +import { parserDom } from "../__helpers__/parserDom" + +export default function AnimeEpisodes(html: string) { + const $ = parserDom(html) + + return $(".episode-list-data") + .toArray() + .map((ep) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const number = $(ep).find(".episode-number").attr("data-raw")! + const name = $(ep).find(".episode-title > a").text().trim() + const japanese = $(ep).find(".di-ib").text().trim() + const time = $(ep).find(".episode-aired").text().trim() + const average = $(ep).find(".episode-aired").text() + + return { number, name, japanese, time, average } + }) +} diff --git a/src/apis/runs/myanimelist/episodes.spec.ts b/src/apis/runs/myanimelist/episodes.spec.ts new file mode 100644 index 00000000..546ed479 --- /dev/null +++ b/src/apis/runs/myanimelist/episodes.spec.ts @@ -0,0 +1,106 @@ +import { getEpisodesMyAnimeList } from "./episodes" + +describe("episodes", () => { + describe("getEpisodesMyAnimeList", () => { + test("should work in all of one page", async () => { + expect( + await getEpisodesMyAnimeList( + "https://myanimelist.net/anime/41389/Tonikaku_Kawaii" + ).then((items) => + items.map((item) => { + delete item.japanese + return item + }) + ) + ).toEqual([ + { + number: "1", + name: "Marriage", + time: "Oct 3, 2020", + average: "Oct 3, 2020", + }, + { + number: "2", + name: "The First Night", + time: "Oct 10, 2020", + average: "Oct 10, 2020", + }, + { + number: "3", + name: "Sisters", + time: "Oct 17, 2020", + average: "Oct 17, 2020", + }, + { + number: "4", + name: "Promise", + time: "Oct 24, 2020", + average: "Oct 24, 2020", + }, + { + number: "5", + name: "Rings", + time: "Oct 31, 2020", + average: "Oct 31, 2020", + }, + { + number: "6", + name: "News", + time: "Nov 7, 2020", + average: "Nov 7, 2020", + }, + { + number: "7", + name: "Trip", + time: "Nov 14, 2020", + average: "Nov 14, 2020", + }, + { + number: "8", + name: "Parents", + time: "Nov 21, 2020", + average: "Nov 21, 2020", + }, + { + number: "9", + name: "Daily Life", + time: "Nov 28, 2020", + average: "Nov 28, 2020", + }, + { + number: "10", + name: "The Way Home", + time: "Dec 5, 2020", + average: "Dec 5, 2020", + }, + { + number: "11", + name: "Friends", + time: "Dec 12, 2020", + average: "Dec 12, 2020", + }, + { + number: "12", + name: "Husband and Wife", + time: "Dec 19, 2020", + average: "Dec 19, 2020", + }, + { + number: "13", + name: "SNS", + time: "N/A", + average: "N/A", + }, + ]) + }) + + test("should search by offset", async () => { + const diff = await getEpisodesMyAnimeList( + "https://myanimelist.net/anime/235/Detective_Conan", + 100 + ) + + expect(diff.length).toBe(100) + }) + }) +}) diff --git a/src/apis/runs/myanimelist/episodes.ts b/src/apis/runs/myanimelist/episodes.ts new file mode 100644 index 00000000..bf6f2b40 --- /dev/null +++ b/src/apis/runs/myanimelist/episodes.ts @@ -0,0 +1,27 @@ +// first in query id anime + +import type MyAnimeListEpisodesParser from "src/apis/parser/myanimelist/episodes" +import { get } from "src/logic/http" + +import { useCache } from "../../useCache" +import Worker from "../../workers/myanimelist/episodes" +import { PostWorker } from "../../wrap-worker" + +export async function getEpisodesMyAnimeList(url: string, offset: number = 0) { + return await useCache(`${url}/episode`, async () => { + const html = + import.meta.env.MODE === "test" + ? await fetch(`${url}/episode?offset=${offset}`).then((res) => res.text()) + : await ( + await get(`${url}/episode?offset=${offset}`) + ).data + + if (import.meta.env.MODE === "test") { + return import("../../parser/myanimelist/episodes").then((res) => + res.default(html) + ) + } + + return PostWorker(Worker, html) + }) +} diff --git a/src/apis/runs/myanimelist/search.ts b/src/apis/runs/myanimelist/search.ts index 4c2d840b..a7775302 100644 --- a/src/apis/runs/myanimelist/search.ts +++ b/src/apis/runs/myanimelist/search.ts @@ -2,6 +2,7 @@ import MiniSearch from "minisearch" import type MyAnimeListSearchParser from "src/apis/parser/myanimelist/search" +import { get } from "src/logic/http" import { useCache } from "../../useCache" import Worker from "../../workers/myanimelist/search?worker" @@ -12,11 +13,11 @@ const BEFORE_SEASON = "ss|\\wd" async function runRawSearch(query: string) { return await useCache(`myanimelist/search/${query}`, async () => { - const html = await fetch( + const { data: html } = await get( `https://myanimelist.net/anime.php?cat=anime&q=${encodeURIComponent( query )}&type=0&score=0&status=0&p=0&r=0&sm=0&sd=0&sy=0&em=0&ed=0&ey=0&c%5B%5D=a&c%5B%5D=b&c%5B%5D=c&c%5B%5D=f` - ).then((res) => res.text()) + ) if (import.meta.env.MODE === "test") { return import("../../parser/myanimelist/search").then((res) => @@ -168,7 +169,9 @@ export async function getAmimeMyAnimeList( // Index all documents miniSearch.addAll(anime) - const result = miniSearch.search(otherRmd)[0] as unknown as Awaited>[0] + const result = miniSearch.search(otherRmd)[0] as unknown as Awaited< + ReturnType + >[0] console.log(otherRmd, result) // console.log(otherRmd, result.slice(0, 10)) // .filter((item) => { @@ -192,7 +195,3 @@ export async function getAmimeMyAnimeList( throw err } } - -export function getEpisodesAnimeMyAnimeList(url: string) { - return fetch(`${url}/episode`).then((res) => res.text()) -} diff --git a/src/apis/workers/myanimelist/episodes.ts b/src/apis/workers/myanimelist/episodes.ts new file mode 100644 index 00000000..672d3861 --- /dev/null +++ b/src/apis/workers/myanimelist/episodes.ts @@ -0,0 +1,4 @@ +import { WrapWorker } from "../..//wrap-worker" +import Episodes from "../../parser/myanimelist/episodes" + +WrapWorker(Episodes) diff --git a/src/boot/installed-extension.ts b/src/boot/installed-extension.ts index cc6ee22d..49f5cc1c 100644 --- a/src/boot/installed-extension.ts +++ b/src/boot/installed-extension.ts @@ -1,13 +1,13 @@ import { i18n } from "src/boot/i18n" import { ref, watch } from "vue" -const installed = ref() +const installed = ref(typeof window.Http === 'object') setTimeout(() => { if (!installed.value) installed.value = false }, 5_0000) // eslint-disable-next-line functional/no-let -let Http: Http +let Http: Http = window.Http Object.defineProperty(window, "Http", { get() { // console.log("================= set Http ==================") diff --git a/vitest.config.ts b/vitest.config.ts index cb3cf28b..f7761781 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,8 +1,16 @@ +import path from "path" import { defineConfig } from "vitest/config" export default defineConfig({ + resolve: { + alias: { + boot: path.resolve(__dirname, "src/boot"), + stores: path.resolve(__dirname, "src/stores"), + }, + }, test: { environment: "jsdom", globals: true, + setupFiles: ["./setup.vitest.ts"], }, }) From 798dcc810ad508cf81fe04208e9ecefef73aa268 Mon Sep 17 00:00:00 2001 From: Tachibana Shin Date: Tue, 25 Apr 2023 08:12:54 +0000 Subject: [PATCH 3/6] fix test --- setup.vitest.ts | 2 ++ src/apis/runs/myanimelist/episodes.ts | 7 ++----- vitest.config.ts | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/setup.vitest.ts b/setup.vitest.ts index dc69b2bd..ef6a6686 100644 --- a/setup.vitest.ts +++ b/setup.vitest.ts @@ -6,6 +6,7 @@ window.Http = { .then((text) => { return { data: text, + status: 200 } }) }, @@ -18,6 +19,7 @@ window.Http = { .then((text) => { return { data: text, + status: 200 } }) }, diff --git a/src/apis/runs/myanimelist/episodes.ts b/src/apis/runs/myanimelist/episodes.ts index bf6f2b40..c29b3d07 100644 --- a/src/apis/runs/myanimelist/episodes.ts +++ b/src/apis/runs/myanimelist/episodes.ts @@ -4,15 +4,12 @@ import type MyAnimeListEpisodesParser from "src/apis/parser/myanimelist/episodes import { get } from "src/logic/http" import { useCache } from "../../useCache" -import Worker from "../../workers/myanimelist/episodes" +import Worker from "../../workers/myanimelist/episodes?worker" import { PostWorker } from "../../wrap-worker" export async function getEpisodesMyAnimeList(url: string, offset: number = 0) { return await useCache(`${url}/episode`, async () => { - const html = - import.meta.env.MODE === "test" - ? await fetch(`${url}/episode?offset=${offset}`).then((res) => res.text()) - : await ( + const html = await ( await get(`${url}/episode?offset=${offset}`) ).data diff --git a/vitest.config.ts b/vitest.config.ts index 9f443060..1475dbe0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,14 +4,14 @@ import { defineConfig } from "vitest/config" export default defineConfig({ resolve: { alias: { + src: path.resolve(__dirname, "src"), boot: path.resolve(__dirname, "src/boot"), stores: path.resolve(__dirname, "src/stores"), }, }, test: { - setupFiles: ["@vitest/web-worker"], + setupFiles: ["@vitest/web-worker", "./setup.vitest.ts"], environment: "jsdom", globals: true, - setupFiles: ["./setup.vitest.ts"], }, }) From 68bf3edeb334750b48e3c16d8775977a40a50803 Mon Sep 17 00:00:00 2001 From: Tachibana Shin Date: Tue, 25 Apr 2023 08:14:10 +0000 Subject: [PATCH 4/6] format --- setup.vitest.ts | 4 +-- src/apis/runs/myanimelist/episodes.ts | 4 +-- src/apis/runs/myanimelist/search.spec.ts | 45 ++++++------------------ 3 files changed, 14 insertions(+), 39 deletions(-) diff --git a/setup.vitest.ts b/setup.vitest.ts index ef6a6686..33a6022e 100644 --- a/setup.vitest.ts +++ b/setup.vitest.ts @@ -6,7 +6,7 @@ window.Http = { .then((text) => { return { data: text, - status: 200 + status: 200, } }) }, @@ -19,7 +19,7 @@ window.Http = { .then((text) => { return { data: text, - status: 200 + status: 200, } }) }, diff --git a/src/apis/runs/myanimelist/episodes.ts b/src/apis/runs/myanimelist/episodes.ts index c29b3d07..c2b92fce 100644 --- a/src/apis/runs/myanimelist/episodes.ts +++ b/src/apis/runs/myanimelist/episodes.ts @@ -9,9 +9,7 @@ import { PostWorker } from "../../wrap-worker" export async function getEpisodesMyAnimeList(url: string, offset: number = 0) { return await useCache(`${url}/episode`, async () => { - const html = await ( - await get(`${url}/episode?offset=${offset}`) - ).data + const html = await (await get(`${url}/episode?offset=${offset}`)).data if (import.meta.env.MODE === "test") { return import("../../parser/myanimelist/episodes").then((res) => diff --git a/src/apis/runs/myanimelist/search.spec.ts b/src/apis/runs/myanimelist/search.spec.ts index f13922c9..a8ee9da8 100644 --- a/src/apis/runs/myanimelist/search.spec.ts +++ b/src/apis/runs/myanimelist/search.spec.ts @@ -4,16 +4,16 @@ describe("episodes", () => { describe("getAmimeMyAnimeList", () => { test("Gate: Jieitai Kanochi nite, Kaku Tatakaeri", async () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const anime = ( - await getAmimeMyAnimeList( - "Cổng chiến tranh", - "GATE, Gate: Thus the JSDF Fought There!, Gate: Jieitai Kanochi nite, Kaku Tatakaeri" - ) - )! + const anime = (await getAmimeMyAnimeList( + "Cổng chiến tranh", + "GATE, Gate: Thus the JSDF Fought There!, Gate: Jieitai Kanochi nite, Kaku Tatakaeri" + ))! expect(anime.id).toBe(28907) expect(anime.type).toBe("anime") - expect(anime.url).toBe("https://myanimelist.net/anime/28907/Gate__Jieitai_Kanochi_nite_Kaku_Tatakaeri") + expect(anime.url).toBe( + "https://myanimelist.net/anime/28907/Gate__Jieitai_Kanochi_nite_Kaku_Tatakaeri" + ) expect(anime.name).toBe("Gate: Jieitai Kanochi nite, Kaku Tatakaeri") }) @@ -176,7 +176,6 @@ describe("episodes", () => { "Mahoutsukai no Yome Season 2, The Ancient Magus' Bride Season 2, The Ancient Magus Bride 2, Mahoutsukai no Yome 2, Mahoyome" ) - expect(anime.id).toBe(52955) expect(anime.type).toBe("anime") expect(anime.url).toBe( @@ -270,14 +269,14 @@ describe("episodes", () => { "KonoSuba OVA, A Blessing to this Wonderful Choker!, Kono Subarashii Choker ni Shufuku wo!, Kono Subarashii Sekai ni Shukufuku wo! OVA" ) - - expect(anime.id).toBe(32380) expect(anime.type).toBe("anime") expect(anime.url).toBe( "https://myanimelist.net/anime/32380/Kono_Subarashii_Sekai_ni_Shukufuku_wo_Kono_Subarashii_Choker_ni_Shukufuku_wo" ) - expect(anime.name).toBe("Kono Subarashii Sekai ni Shukufuku wo! Kono Subarashii Choker ni Shukufuku wo!") + expect(anime.name).toBe( + "Kono Subarashii Sekai ni Shukufuku wo! Kono Subarashii Choker ni Shukufuku wo!" + ) }) test("Mở Ra Một Thế Giới Tuyệt Vời 2", async () => { @@ -286,16 +285,12 @@ describe("episodes", () => { "KonoSuba: God's Blessing on This Wonderful World! 2, Give Blessings to This Wonderful World! 2, Kono Subarashii Sekai ni Shukufuku wo! 2" ) - - expect(anime.id).toBe(32937) expect(anime.type).toBe("anime") expect(anime.url).toBe( "https://myanimelist.net/anime/32937/Kono_Subarashii_Sekai_ni_Shukufuku_wo_2" ) - expect(anime.name).toBe( - "Kono Subarashii Sekai ni Shukufuku wo! 2" - ) + expect(anime.name).toBe("Kono Subarashii Sekai ni Shukufuku wo! 2") }) test("Mở Ra Một Thế Giới Tuyệt Vời OVA 2", async () => { @@ -304,8 +299,6 @@ describe("episodes", () => { "KonoSuba: God's Blessing on This Wonderful World! Second Season OVA, KonoSuba: God's Blessing on This Wonderful World! Second Season OVA, Kono Subarashii Sekai ni Shukufuku wo! 2 OVA" ) - - expect(anime.id).toBe(34626) expect(anime.type).toBe("anime") expect(anime.url).toBe( @@ -322,8 +315,6 @@ describe("episodes", () => { "Konosuba: An Explosion on This Wonderful World!" ) - - expect(anime.id).toBe(51958) expect(anime.type).toBe("anime") expect(anime.url).toBe( @@ -338,8 +329,6 @@ describe("episodes", () => { "Please Teacher!, Onegai Sensei" ) - - expect(anime.id).toBe(195) expect(anime.type).toBe("anime") expect(anime.url).toBe("https://myanimelist.net/anime/195/Onegai☆Teacher") @@ -352,8 +341,6 @@ describe("episodes", () => { "Skilled Teaser Takagi-san" ) - - expect(anime.id).toBe(35860) expect(anime.type).toBe("anime") expect(anime.url).toBe( @@ -368,8 +355,6 @@ describe("episodes", () => { "Karakai Jouzu no Takagi-san 3, Skilled Teaser Takagi-san 3rd Season, Karakai Jouzu no Takagi-san Third Season, Teasing Master Takagi-san" ) - - expect(anime.id).toBe(49721) expect(anime.type).toBe("anime") expect(anime.url).toBe( @@ -384,8 +369,6 @@ describe("episodes", () => { "東京ミュウミュウ にゅ~♡" ) - - expect(anime.id).toBe(53097) expect(anime.type).toBe("anime") expect(anime.url).toBe( @@ -400,8 +383,6 @@ describe("episodes", () => { "東京ミュウミュウ にゅ~♡" ) - - expect(anime.id).toBe(41589) expect(anime.type).toBe("anime") expect(anime.url).toBe( @@ -416,8 +397,6 @@ describe("episodes", () => { "Tokyo Mew Mew, Mew Mew Power" ) - - expect(anime.id).toBe(687) expect(anime.type).toBe("anime") expect(anime.url).toBe("https://myanimelist.net/anime/687/Tokyo_Mew_Mew") @@ -430,8 +409,6 @@ describe("episodes", () => { "TIn Another World With My Smartphone, In a Different World with a Smartphone" ) - - expect(anime.id).toBe(35203) expect(anime.type).toBe("anime") expect(anime.url).toBe( From 0f19f59413cd35695360218562bc3f6ff8e733ad Mon Sep 17 00:00:00 2001 From: Tachibana Shin Date: Tue, 25 Apr 2023 09:13:03 +0000 Subject: [PATCH 5/6] split to shared search engine --- src/apis/runs/myanimelist/search.ts | 182 +++++++++++++++------------- 1 file changed, 95 insertions(+), 87 deletions(-) diff --git a/src/apis/runs/myanimelist/search.ts b/src/apis/runs/myanimelist/search.ts index a7775302..60d039cb 100644 --- a/src/apis/runs/myanimelist/search.ts +++ b/src/apis/runs/myanimelist/search.ts @@ -76,10 +76,11 @@ function sliceOthername(othername: string[], size: number, max: number) { const rPrefixSeason = new RegExp(`${PREFIX_SEASON} (\\d+)`, "gi") const rBeforeSeason = new RegExp(`(\\d+)${BEFORE_SEASON}\\s+(?:season)?`, "gi") -export async function getAmimeMyAnimeList( +export function searchShared( name: string, - othername: string -): Promise[0]> { + othername: string, + fetcher: (keyword: string) => Promise +): Promise { const nameRmd = name .replace(rPrefixSeason, " $1") .replace(rBeforeSeason, "$1") @@ -100,90 +101,98 @@ export async function getAmimeMyAnimeList( const keyword = query console.log({ keyword }, keyword.length) - try { - return runRawSearch(keyword).then((anime) => { - // console.log(keyword) - // console.dir(data, { depth: null }) - // eslint-disable-next-line functional/no-throw-statement - if (anime.length === 0) throw new Error("not_found") - - const ss = name.match(defRegExpGetSeason)?.slice(1).filter(Boolean).at(-1) - const regexpSeasonSS = createRegExpGetSeason(ss ?? "\\d+") - - name = name.toLowerCase().trim() - const nameRmdSeason = ss - ? name.replace(defRegExpGetSeason, "").trim() - : name - const matchByName = anime.find((item) => { - const itemName = ss - ? item.name.replace(defRegExpGetSeason, "").toLowerCase().trim() - : item.name.toLowerCase() - - if (name === itemName || nameRmdSeason === itemName) { - return ss - ? !!item.name.match(regexpSeasonSS) - : !item.name.match(regexpSeasonSS) - } - - return false - }) - if (matchByName) return matchByName - - const othernameRmdSeason = ss - ? othername - .split(",") - .map((name) => { - return name - .trim() - .replace(defRegExpGetSeason, "") - .toLowerCase() - .trim() - }) - .join(",") - : othername.toLowerCase().trim() - const matchByOthername = anime.find((item, id) => { - const itemName = ss - ? item.name.replace(defRegExpGetSeason, "").toLowerCase().trim() - : item.name.toLowerCase() - // console.log(item.name, defRegExpGetSeason, itemName) - - if (basicExactOthername(itemName, othernameRmdSeason)) { - return ss - ? !!item.name.match(regexpSeasonSS) - : !item.name.match(regexpSeasonSS) - } - - return false - }) - - if (matchByOthername) return matchByOthername - - const miniSearch = new MiniSearch({ - fields: ["name_lower"], // fields to index for full-text search - storeFields: Object.keys(anime[0]), // fields to return with search results - }) - - anime.forEach((item) => { - item.name_lower = item.name.trim().toLowerCase() - }) - // Index all documents - miniSearch.addAll(anime) - - const result = miniSearch.search(otherRmd)[0] as unknown as Awaited< - ReturnType - >[0] - console.log(otherRmd, result) - // console.log(otherRmd, result.slice(0, 10)) - // .filter((item) => { - // return ss - // ? !!item.name.match(regexpSeasonSS) - // : !item.name.match(regexpSeasonSS) - // }) as unknown as Awaited>[0] - if (result) return result - - // eslint-disable-next-line functional/no-throw-statement - throw new Error("not_found") + + return fetcher(keyword).then((anime) => { + // console.log(keyword) + // console.dir(data, { depth: null }) + // eslint-disable-next-line functional/no-throw-statements + if (anime.length === 0) throw new Error("not_found") + + const ss = name.match(defRegExpGetSeason)?.slice(1).filter(Boolean).at(-1) + const regexpSeasonSS = createRegExpGetSeason(ss ?? "\\d+") + + name = name.toLowerCase().trim() + const nameRmdSeason = ss + ? name.replace(defRegExpGetSeason, "").trim() + : name + const matchByName = anime.find((item) => { + const itemName = ss + ? item.name.replace(defRegExpGetSeason, "").toLowerCase().trim() + : item.name.toLowerCase() + + if (name === itemName || nameRmdSeason === itemName) { + return ss + ? !!item.name.match(regexpSeasonSS) + : !item.name.match(regexpSeasonSS) + } + + return false }) + if (matchByName) return matchByName + + const othernameRmdSeason = ss + ? othername + .split(",") + .map((name) => { + return name + .trim() + .replace(defRegExpGetSeason, "") + .toLowerCase() + .trim() + }) + .join(",") + : othername.toLowerCase().trim() + const matchByOthername = anime.find((item, id) => { + const itemName = ss + ? item.name.replace(defRegExpGetSeason, "").toLowerCase().trim() + : item.name.toLowerCase() + // console.log(item.name, defRegExpGetSeason, itemName) + + if (basicExactOthername(itemName, othernameRmdSeason)) { + return ss + ? !!item.name.match(regexpSeasonSS) + : !item.name.match(regexpSeasonSS) + } + + return false + }) + + if (matchByOthername) return matchByOthername + + const miniSearch = new MiniSearch({ + fields: ["name_lower"], // fields to index for full-text search + storeFields: Object.keys(anime[0]), // fields to return with search results + }) + + anime.forEach((item) => { + item.name_lower = item.name.trim().toLowerCase() + }) + // Index all documents + miniSearch.addAll(anime) + + const result = miniSearch.search( + otherRmd + )[0] as unknown as (typeof anime)[0] + console.log(otherRmd, result) + // console.log(otherRmd, result.slice(0, 10)) + // .filter((item) => { + // return ss + // ? !!item.name.match(regexpSeasonSS) + // : !item.name.match(regexpSeasonSS) + // }) as unknown as Awaited>[0] + if (result) return result + + // eslint-disable-next-line functional/no-throw-statements + throw new Error("not_found") + }) +} + +export async function getAmimeMyAnimeList( + name: string, + othername: string +): Promise[0]> { + try { + return searchShared(name, othername, runRawSearch) } catch (err) { if ((err as Error | null)?.message?.startsWith("Unexpected token ")) { // limit request @@ -191,7 +200,6 @@ export async function getAmimeMyAnimeList( return getAmimeMyAnimeList(name, othername) } - // eslint-disable-next-line functional/no-throw-statement throw err } } From d4bda3f346075391ff63e202dbb50486742cc8ca Mon Sep 17 00:00:00 2001 From: Tachibana Shin Date: Sun, 7 May 2023 14:03:11 +0000 Subject: [PATCH 6/6] try add api ep name --- src/apis/runs/myanimelist/search.ts | 2 +- src/apis/runs/translate.ts | 27 +++++++++++++++++++++++++++ src/logic/http.ts | 4 ++-- src/pages/phim/_season.vue | 22 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 src/apis/runs/translate.ts diff --git a/src/apis/runs/myanimelist/search.ts b/src/apis/runs/myanimelist/search.ts index 60d039cb..4f02b84d 100644 --- a/src/apis/runs/myanimelist/search.ts +++ b/src/apis/runs/myanimelist/search.ts @@ -25,7 +25,7 @@ async function runRawSearch(query: string) { ) } - return PostWorker(Worker, "123") + return PostWorker(Worker, html) }) } diff --git a/src/apis/runs/translate.ts b/src/apis/runs/translate.ts new file mode 100644 index 00000000..43a08998 --- /dev/null +++ b/src/apis/runs/translate.ts @@ -0,0 +1,27 @@ +import { post } from "src/logic/http"; + +export function translateText(text: string[], from: string, to: string) { + return post("https://translate.google.it/translate_a/single", + `client=gtx&dt=t&dt=bd&dj=1&source=input&q=${encodeURIComponent(text.join("\n"))}&sl=${from}&tl=${to}&hl=en`,{ + "content-type": "application/x-www-form-urlencoded", + }).then(res => JSON.parse(res.data )as { + sentences: { + trans: string; + orig: string; + backend: number; + model_specification: {}[]; + translation_engine_debug_info: ({ + model_tracking: { + checkpoint_md5: string; + launch_doc: string; + }; + has_untranslatable_chunk?: undefined; + } | { + has_untranslatable_chunk: boolean; + model_tracking?: undefined; + })[]; + }[]; + src: string; + spell: {}; +}).then(data => data.sentences.map(item => item.trans)) +} diff --git a/src/logic/http.ts b/src/logic/http.ts index 9bb5e710..7e2942f9 100644 --- a/src/logic/http.ts +++ b/src/logic/http.ts @@ -64,7 +64,7 @@ export async function get< export async function post( url: string, - data: Record, + data: Record | string, headers?: Record ) { console.log("post: ", { @@ -92,7 +92,7 @@ export async function post( ) const response = (await window.Http.post({ - url: C_URL + url + "#animevsub-vsub", + url: url.includes("://") ? url : C_URL + url + "#animevsub-vsub", headers: { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", diff --git a/src/pages/phim/_season.vue b/src/pages/phim/_season.vue index 4150ae81..c56d3810 100644 --- a/src/pages/phim/_season.vue +++ b/src/pages/phim/_season.vue @@ -470,6 +470,9 @@ import type { ResponseDataSeasonPending, ResponseDataSeasonSuccess, } from "./response-data-season" +import { getAmimeMyAnimeList } from "src/apis/runs/myanimelist/search" +import { getEpisodesMyAnimeList } from "src/apis/runs/myanimelist/episodes" +import { translateText } from "src/apis/runs/translate" // ================ follow ================ // ======================================================= // import SwipableBottom from "components/SwipableBottom.vue" @@ -612,6 +615,25 @@ watch( immediate: true, } ) +watch(data, async data => { + if (!data) return + + console.time('time load info episodes') + const info = await getAmimeMyAnimeList(data.name, data.othername) + + console.log("%cinfo: ", 'color: white; background-color: green', info) + + const infoEpisodes = await getEpisodesMyAnimeList(info.url) + + console.log("%cepisodes: ", 'color: white; background-color: green', infoEpisodes) + console.timeEnd('time load info episodes') + + console.time('time translate') + const nameEpisodesTranslated = await translateText(infoEpisodes.map(item=>item.name), 'en', 'vi') + + console.log("%cnameEpisodesTranslated: ", 'color: white; background-color: green',nameEpisodesTranslated) + console.timeEnd('time translate') +}) watch( [progressWatchStore, () => authStore.user_data],