Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/tverplus_curl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 11 additions & 7 deletions .github/workflows/tverplus_puppeteer.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
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";
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`;
Expand All @@ -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`);
Expand All @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/tverplus_puppeteer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
33 changes: 23 additions & 10 deletions tverplus.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: {},
Expand All @@ -59,7 +61,8 @@ function waitForTitle() {

if (isTitleReady()) {
previousTitle = document.querySelector(titleSelector).textContent;
return res(previousTitle);
res(previousTitle);
return;
}

const observer = new MutationObserver(() => {
Expand Down Expand Up @@ -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),
Expand All @@ -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;
Expand Down Expand Up @@ -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),
Expand All @@ -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}`);
Expand Down Expand Up @@ -282,8 +295,8 @@ function resetSeriesData() {
seriesData.mdl = {};
seriesElements.fm = {};
seriesElements.mdl = {};
seriesID = undefined;
previousTitle = undefined;
seriesID = null;
previousTitle = null;
}

function runScript() {
Expand Down Expand Up @@ -323,7 +336,7 @@ function matchScript({ url }) {
runScript();
}
else {
console.warn("Invalid series ID");
console.warn("[ERROR] Invalid series ID");
resetSeriesData();
}
}
Expand Down