diff --git a/.github/workflows/tverplus_curl.yml b/.github/workflows/tverplus_curl.yml index 33b5203..44d2f3d 100644 --- a/.github/workflows/tverplus_curl.yml +++ b/.github/workflows/tverplus_curl.yml @@ -128,13 +128,13 @@ jobs: run: | resp_body=$(curl -s "$TAMPERMONKEY_DOCUMENTATION_URL") - grep -q "window.onurlchange" <<< "$resp_body" + grep -qF "window.onurlchange" <<< "$resp_body" urlchange=$? - grep -q "GM.getValue" <<< "$resp_body" + grep -qF "GM.getValue" <<< "$resp_body" gmgetvalue=$? - grep -q "GM.setValue" <<< "$resp_body" + grep -qF "GM.setValue" <<< "$resp_body" gmsetvalue=$? if [[ "$urlchange" -ne 0 || "$gmgetvalue" -ne 0 || "$gmsetvalue" -ne 0 ]]; then diff --git a/.github/workflows/tverplus_puppeteer.mjs b/.github/workflows/tverplus_puppeteer.mjs index aadf3a0..0e2848f 100644 --- a/.github/workflows/tverplus_puppeteer.mjs +++ b/.github/workflows/tverplus_puppeteer.mjs @@ -1,5 +1,9 @@ import puppeteer from "puppeteer"; +const SERIES_CONTAINER_CLASS = "Series_container"; +const SERIES_CONTENT_CLASS = "Series_info"; +const SERIES_TITLE_CLASS = "Series_title"; + const SERIES_URL = "https://tver.jp/series/sr2u73jipd"; const SERIES_TITLE = "あなたの番です"; const SERIES_FM_LINK = "https://filmarks.com/dramas/6055/8586"; @@ -7,10 +11,6 @@ const SERIES_FM_RATING = 4.0; const SERIES_MDL_LINK = "https://mydramalist.com/33145-it-s-your-turn"; const SERIES_MDL_RATING = 8.0; -const SERIES_CONTAINER_CLASS = "Series_container"; -const SERIES_CONTENT_CLASS = "Series_info"; -const SERIES_TITLE_CLASS = "Series_title"; - const USERSCRIPT_GITHUB_REPO = process.env.GITHUB_REPO || "e0406370/tverplus"; const USERSCRIPT_GITHUB_REF = process.env.GITHUB_REF || "main"; const USERSCRIPT_GITHUB_URL = `https://raw.githubusercontent.com/${USERSCRIPT_GITHUB_REPO}/${USERSCRIPT_GITHUB_REF}/tverplus.user.js`; @@ -21,6 +21,7 @@ const logMessage = (message) => console.log(message); (async () => { const browser = await puppeteer.launch({ headless: isBrowserHeadless }); + const page = await browser.newPage(); page.setDefaultTimeout(15_000); logMessage(`Browser is launched with ${isBrowserHeadless ? "headless" : "headful"} mode`); @@ -29,19 +30,22 @@ const logMessage = (message) => console.log(message); logMessage(`'${SERIES_URL}' is loaded`); const containerElement = await page.waitForSelector(retrieveSelectorClassStartsWith(SERIES_CONTAINER_CLASS)); - logMessage(`Element with selector starting with '${SERIES_CONTAINER_CLASS}' is loaded`); + if (!containerElement) { + throw new Error("Something is wrong with the container element") + } + logMessage(`Element with selector starting with '${SERIES_CONTAINER_CLASS}' is visible`); const contentElement = await containerElement.$(retrieveSelectorClassStartsWith(SERIES_CONTENT_CLASS)); - logMessage(`Element with selector starting with '${SERIES_CONTENT_CLASS}' is visible`); if (!contentElement) { throw new Error("Something is wrong with the content element"); } + logMessage(`Element with selector starting with '${SERIES_CONTENT_CLASS}' is visible`); const titleElement = await containerElement.$(retrieveSelectorClassStartsWith(SERIES_TITLE_CLASS)); - logMessage(`Element with selector starting with '${SERIES_TITLE_CLASS}' is visible`); if (!titleElement) { throw new Error("Something is wrong with the title element"); } + logMessage(`Element with selector starting with '${SERIES_TITLE_CLASS}' is visible`); const titleText = await titleElement.evaluate(el => el.textContent); if (titleText != SERIES_TITLE) { diff --git a/.github/workflows/tverplus_puppeteer.yml b/.github/workflows/tverplus_puppeteer.yml index 87f8b4e..e3f68d3 100644 --- a/.github/workflows/tverplus_puppeteer.yml +++ b/.github/workflows/tverplus_puppeteer.yml @@ -9,12 +9,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 24 # Refer to https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md # Refer to https://github.com/puppeteer/puppeteer/blob/312a65117036845241e1dcf792cd0101edbdf4cd/.github/workflows/ci.yml#L145 diff --git a/README.md b/README.md index c7470e4..646397f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## tverplus [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](https://developer.mozilla.org/en-US/docs/Web/JavaScript) -[![Tampermonkey](https://img.shields.io/badge/tampermonkey-5.4.0-blue?logo=tampermonkey&label=Tampermonkey)](https://www.tampermonkey.net/index.php) +[![Tampermonkey](https://img.shields.io/badge/tampermonkey-5.4.1-blue?logo=tampermonkey&label=Tampermonkey)](https://www.tampermonkey.net/index.php) [![Perform-minimum-validation-1-curl-2-puppeteer](https://github.com/e0406370/tverplus/actions/workflows/tverplus_base.yml/badge.svg)](https://github.com/e0406370/tverplus/actions/workflows/tverplus_base.yml) ### Purpose diff --git a/tverplus.user.js b/tverplus.user.js index 07b4114..3964a7a 100644 --- a/tverplus.user.js +++ b/tverplus.user.js @@ -4,7 +4,7 @@ // @description Adds Filmarks and MyDramaList ratings with links to their respective pages directly on TVer series pages. 1-1 matching is not guaranteed. // @author e0406370 // @match https://tver.jp/* -// @version 2025-12-11 +// @version 2026-02-18 // @grant GM.getValue // @grant GM.setValue // @grant window.onurlchange @@ -21,19 +21,21 @@ const FM_FAVICON_URL = `${ASSETS_BASE_URL}favicon_fm.png`; const MDL_FAVICON_URL = `${ASSETS_BASE_URL}favicon_mdl.png`; const TVER_SERIES_URL = "https://tver.jp/series/"; +const TVER_EXCLUDED_COUNTRIES = ["中国", "韓国", "韓流"]; const FM_API_BASE_URLS = ["https://markuapi.vercel.app", "https://markuapi.apn.leapcell.app"]; +const FM_COUNTRY_TYPE = "日本"; const MDL_API_BASE_URLS = ["https://kuryana-kappa.vercel.app", "https://kuryana.tbdh.app"]; const MDL_DRAMA_TYPES = ["Japanese Drama", "Japanese TV Show"]; const retrieveSelectorClassStartsWith = (className) => `[class^=${className}]`; const retrieveSeriesIDFromSeriesURL = (url) => (url.match(/sr[a-z0-9]{8,9}/) || [])[0] || null; -const isTimestampExpired = (timestamp) => timestamp < Date.now() - 7 * 24 * 60 * 60 * 10 ** 3; +const isTimestampExpired = (timestamp) => timestamp < Date.now() - 24 * 60 * 60 * 10 ** 3; const isEmptyObject = (obj) => Object.keys(obj).length === 0; const getFMSearchDramasEndpoint = (url, query) => `${url}/search/dramas?q=${query}`; const getMDLSearchDramasEndpoint = (url, query) => `${url}/search/q/${query}`; const getMDLGetDramaInfoEndpoint = (url, slug) => `${url}/id/${slug}`; -const normaliseTitle = (query) => query.replace(/[-–—−―]/g, "").replace(/[~~〜⁓∼˜˷﹏﹋]/g, "") .replace(/[\//∕⁄]/g, "").replace(/[()()]/g, "").replace(/\s/g, "").normalize("NFKC"); +const normaliseTitle = (query) => query.normalize("NFKC").replace(/[-‐–—−―]/g, " ").replace(/[~~〜⁓∼˜˷﹏﹋]/g, " ").replace(/[\//∕⁄]/g, " ").replace(/[()()]/g, " ").replace(/\s+/g, "").trim(); let seriesData = { fm: {}, @@ -59,7 +61,8 @@ function waitForTitle() { if (isTitleReady()) { previousTitle = document.querySelector(titleSelector).textContent; - return res(previousTitle); + res(previousTitle); + return; } const observer = new MutationObserver(() => { @@ -89,6 +92,11 @@ async function retrieveSeriesDataFM(title) { while (!toBreak && urlPtr < FM_API_BASE_URLS.length) { try { + if (TVER_EXCLUDED_COUNTRIES.some(c => title.includes(c))) { + toBreak = true; + throw new Error(`[FM] Title contains excluded country, skipping ${title}`); + } + const url = getFMSearchDramasEndpoint(FM_API_BASE_URLS[urlPtr], title); const res = await Promise.race([ fetch(url), @@ -107,12 +115,12 @@ async function retrieveSeriesDataFM(title) { } for (const [idx, drama] of data.results.dramas.entries()) { - if (idx === 3) break; + if (idx === 5) break; const titleSearch = normaliseTitle(title); const titleFM = normaliseTitle(drama.title); - if (titleSearch.includes(titleFM) || titleFM.includes(titleSearch)) { + if (drama.country_of_origin.includes(FM_COUNTRY_TYPE) && (titleSearch.includes(titleFM) || titleFM.includes(titleSearch))) { console.info(`[FM] ${drama.title} | ${drama.rating}`); seriesDataFM.rating = drama.rating; @@ -146,6 +154,11 @@ async function retrieveSeriesDataMDL(title) { while (!toBreak && urlPtr < MDL_API_BASE_URLS.length) { try { + if (TVER_EXCLUDED_COUNTRIES.some(c => title.includes(c))) { + toBreak = true; + throw new Error(`[MDL] Title contains excluded country, skipping ${title}`); + } + const searchUrl = getMDLSearchDramasEndpoint(MDL_API_BASE_URLS[urlPtr], normaliseTitle(title)); const searchRes = await Promise.race([ fetch(searchUrl), @@ -166,7 +179,7 @@ async function retrieveSeriesDataMDL(title) { let slug = null; for (const [idx, drama] of searchData.results.dramas.entries()) { - if (idx === 3) break; + if (idx === 5) break; if (MDL_DRAMA_TYPES.includes(drama.type)) { console.info(`[MDL] ${drama.title} | ${drama.year}`); @@ -282,8 +295,8 @@ function resetSeriesData() { seriesData.mdl = {}; seriesElements.fm = {}; seriesElements.mdl = {}; - seriesID = undefined; - previousTitle = undefined; + seriesID = null; + previousTitle = null; } function runScript() { @@ -323,7 +336,7 @@ function matchScript({ url }) { runScript(); } else { - console.warn("Invalid series ID"); + console.warn("[ERROR] Invalid series ID"); resetSeriesData(); } }