您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Annict の「記録する」ページの内容を指定されたURLに送信します。
- // ==UserScript==
- // @name Annict: Track Broker
- // @namespace https://rinsuki.net
- // @match https://annict.com/*
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_xmlhttpRequest
- // @version 1.1
- // @author rinsuki
- // @description Annict の「記録する」ページの内容を指定されたURLに送信します。
- // ==/UserScript==
- // @ts-check
- (() => {
- /** @type {MutationObserver | null} */
- let observer
- const configUI = document.createElement("details")
- configUI.innerHTML = `<summary>Track Broker Config</summary><form><div>URL: <input name="url"></div><div>Authorization Header: <input name="auth" placeholder="Bearer ..."></div><div>method: <select name="method"><option>POST</option><option>PUT</option></select></div><button>Save</button></form>`
- configUI.style.position = "fixed"
- configUI.style.bottom = "1em"
- configUI.style.right = "1em"
- configUI.style.zIndex = "9999"
- configUI.style.backgroundColor = "white"
- configUI.style.border = "1px solid black"
- configUI.style.padding = "0.5em"
- const form = configUI.querySelector("form")
- if (form == null) return
- /** @type {HTMLInputElement | null} */
- const urlField = form.querySelector("input[name=url]")
- /** @type {HTMLInputElement | null} */
- const authField = form.querySelector("input[name=auth]")
- /** @type {HTMLSelectElement | null} */
- const methodField = form.querySelector("select[name=method]")
- if (urlField == null || authField == null || methodField == null) return
- form.addEventListener("submit", e => {
- e.preventDefault()
- GM_setValue("url", urlField.value)
- GM_setValue("auth", authField.value)
- GM_setValue("method", methodField.value)
- alert("Saved")
- })
- urlField.value = GM_getValue("url", "")
- authField.value = GM_getValue("auth", "")
- methodField.value = GM_getValue("method", "POST")
- async function main() {
- if (location.pathname !== "/track") {
- configUI.remove()
- return
- } else {
- document.body.appendChild(configUI)
- }
- const watchTarget = document.querySelector(`[data-reloadable-event-name-value="trackable-episode-list"]`)
- if (watchTarget == null) return
- function changed() {
- const works = document.querySelectorAll("#trackable-episode-list .card > .card-body > :first-child")
- let episodes = []
- for (const work of works) {
- const workName = work.querySelector(".col > .small.u-cursor-pointer")?.textContent
- const episodeName = work.querySelector(".col > .fw-bold")?.textContent
- const episodeSource = work.querySelector(".col > .mt-1.small > .text-muted")
- const episodeTimestamp = work.querySelector(".col > .small:not(.mt-1) > .text-muted")?.textContent
- const episodeJSON = {
- workName,
- episodeName,
- episodeSource: episodeSource != null ? {
- name: episodeSource.textContent,
- url: episodeSource.getAttribute("href"),
- } : null,
- episodeTimestamp,
- }
- episodes.push(episodeJSON)
- }
- const body = {
- episodes,
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
- }
- console.log("body", body)
- const url = GM_getValue("url", "")
- const auth = GM_getValue("auth", "")
- if (url.length && url.startsWith("http")) {
- const urlObj = new URL(url)
- GM_xmlhttpRequest({
- url,
- method: GM_getValue("method", "POST"),
- headers: {
- "Content-Type": "application/json",
- "Authorization": auth,
- },
- data: JSON.stringify(body),
- onload: res => {
- console.log(res)
- if (res.status >= 300) {
- alert(`failed to send data (${res.status})`)
- }
- }
- })
- } else {
- console.warn("skip sending because url is not set or invalid")
- }
- }
- if (observer != null) {
- observer.disconnect()
- }
- observer = new MutationObserver((mutations) => {
- changed()
- })
- observer.observe(watchTarget, {
- childList: true,
- })
- changed()
- }
- addEventListener("turbo:load", main)
- })()