atcoder-hashtag-setter2

ツイートボタンの埋め込みテキストに情報を追加します

目前為 2023-10-22 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
// @name         atcoder-hashtag-setter2
// @namespace    https://github.com/hotarunx
// @version      2.0.0
// @description  ツイートボタンの埋め込みテキストに情報を追加します
// @author       hotarunx
// @license      MIT
// @supportURL   https://github.com/hotarunx/atcoder-hashtag-setter2/issues
// @match        https://atcoder.jp/contests/*
// @exclude      https://atcoder.jp/contests/
// @grant        none
// ==/UserScript==
// AtCoderの問題ページをパースする
/**
 * URLをパースする パラメータを消す \
 * 例: in:  https://atcoder.jp/contests/abc210?lang=en \
 * 例: out: (5)['https:', '', 'atcoder.jp', 'contests', 'abc210']
 */
const parseURL = (url) => {
    // 区切り文字`/`で分割する
    // ?以降の文字列を削除してパラメータを削除する
    return url.split("/").map((x) => x.replace(/\?.*/i, ""));
};
const URL = parseURL(window.location.href);
//
/**
 * 表セル要素から、前の要素のテキストが引数と一致する要素を探す
 * 個別の提出ページで使うことを想定
 * 例: searchSubmissionInfo(["問題", "Task"])
 */
const searchSubmissionInfo = (key) => {
    const tdTags = document.getElementsByTagName("td");
    const tdTagsArray = Array.prototype.slice.call(tdTags);
    return tdTagsArray.filter((elem) => {
        const prevElem = elem.previousElementSibling;
        const text = prevElem?.textContent;
        if (typeof text === "string")
            return key.includes(text);
        return false;
    })[0];
};
/** コンテストタイトル 例: AtCoder Beginner Contest 210 */
const contestTitle = document.getElementsByClassName("contest-title")[0]?.textContent ?? "";
/** コンテストID 例: abc210 */
const contestID = URL[4] ?? "";
/**
 * ページ種類 \
 * 基本的にコンテストIDの次のパス
 * ### 例外
 * 問題: task
 * 個別の提出: submission
 */
const pageType = (() => {
    if (URL.length < 6)
        return "";
    if (URL.length >= 7 && URL[5] === "submissions" && URL[6] !== "me")
        return "submission";
    if (URL.length >= 7 && URL[5] === "tasks")
        return "task";
    return URL[5] ?? "";
})();
/** 問題ID 例: abc210_a */
const taskID = (() => {
    if (pageType === "task") {
        // 問題ページでは、URLから問題IDを取り出す
        return URL[6] ?? "";
    }
    if (pageType === "submission") {
        // 個別の提出ページでは、問題リンクのURLから問題IDを取り出す
        // 提出情報の問題のURLを取得する
        const taskCell = searchSubmissionInfo(["問題", "Task"]);
        if (!taskCell)
            return "";
        const taskLink = taskCell.getElementsByTagName("a")[0];
        if (!taskLink)
            return "";
        const taskUrl = parseURL(taskLink.href);
        const taskIDParsed = taskUrl[6] ?? "";
        return taskIDParsed;
    }
    return "";
})();
/** 問題名 例: A - Cabbages */
const taskTitle = (() => {
    if (pageType === "task") {
        // 問題ページでは、h2から問題名を取り出す
        return (document
            .getElementsByClassName("h2")[0]
            ?.textContent?.trim()
            .replace(/\n.*/i, "") ?? "");
    }
    if (pageType === "submission") {
        // 個別の提出ページでは、問題リンクのテキストから問題名を取り出す
        // 提出情報の問題のテキストを取得する
        const taskCell = searchSubmissionInfo(["問題", "Task"]);
        if (!taskCell)
            return "";
        const taskLink = taskCell.getElementsByTagName("a")[0];
        if (!taskLink)
            return "";
        return taskLink.textContent ?? "";
    }
    return "";
})();
/** 提出ユーザー 例: machikane */
const submissionsUser = (() => {
    if (pageType !== "submission")
        return "";
    // 個別の提出ページのとき
    const userCell = searchSubmissionInfo(["ユーザ", "User"]);
    if (!userCell)
        return "";
    return userCell?.textContent?.trim() ?? "";
})();
/** 提出結果 例: AC */
const judgeStatus = (() => {
    if (pageType !== "submission")
        return "";
    // 個別の提出ページのとき
    const statusCell = searchSubmissionInfo(["結果", "Status"]);
    if (!statusCell)
        return "";
    return statusCell?.textContent?.trim() ?? "";
})();
/** 得点 例: 100 */
const judgeScore = (() => {
    if (pageType !== "submission")
        return 0;
    // 個別の提出ページのとき
    const scoreCell = searchSubmissionInfo(["得点", "Score"]);
    if (!scoreCell)
        return 0;
    return parseInt(scoreCell?.textContent?.trim() ?? "0", 10);
})();

/** 常設コンテストID一覧 */
const permanentContestIDs = [
    "practice",
    "APG4b",
    "abs",
    "practice2",
    "typical90",
    "math-and-algorithm",
    "tessoku-book",
];
/**
 * 次を判定
 * * コンテストが終了している
 * * コンテストが常設コンテスト
 */
var isContestOver = () => {
    if (permanentContestIDs.includes(contestID))
        return true;
    return Date.now() > endTime.valueOf();
};

var html = "<a target=\"_blank\" href=\"\" rel=\"nofollow noopener\">\n  <span class=\"a2a_svg a2a_s__default a2a_s_twitter\" style=\"background-color: rgb(230, 18, 114);\n      width: 20px;\n      line-height: 20px;\n      height: 20px;\n      background-size: 20px;\n      border-radius: 3px;\n      --darkreader-inline-bgcolor: #0c72b7;\" data-darkreader-inline-bgcolor=\"\">\n    <svg focusable=\"false\" aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\">\n      <path fill=\"#FFF\" d=\"M28 8.557a9.913 9.913 0 0 1-2.828.775 4.93 4.93 0 0 0 2.166-2.725 9.738 9.738 0 0 1-3.13 1.194 4.92 4.92 0 0 0-3.593-1.55 4.924 4.924 0 0 0-4.794 6.049c-4.09-.21-7.72-2.17-10.15-5.15a4.942 4.942 0 0 0-.665 2.477c0 1.71.87 3.214 2.19 4.1a4.968 4.968 0 0 1-2.23-.616v.06c0 2.39 1.7 4.38 3.952 4.83-.414.115-.85.174-1.297.174-.318 0-.626-.03-.928-.086a4.935 4.935 0 0 0 4.6 3.42 9.893 9.893 0 0 1-6.114 2.107c-.398 0-.79-.023-1.175-.068a13.953 13.953 0 0 0 7.55 2.213c9.056 0 14.01-7.507 14.01-14.013 0-.213-.005-.426-.015-.637.96-.695 1.795-1.56 2.455-2.55z\" data-darkreader-inline-fill=\"\" style=\"--darkreader-inline-fill: #e8e6e3\"></path>\n    </svg> </span><span class=\"a2a_label\">Twitter2</span>\n</a>\n";

/** ツイートボタンのHTML要素 */
const a2akit = document.getElementsByClassName("a2a_kit")[0];
/**
 * ツイートボタンのテキストを取得する
 */
const getTweetButtonText = () => {
    if (!a2akit)
        return "";
    return a2akit.getAttribute("data-a2a-title");
};
/**
 * ツイートボタンのテキストを変更する
 */
const setTweetButtonText = (text) => {
    if (!a2akit)
        return;
    a2akit.setAttribute("data-a2a-title", text);
};
const addTweetSearchButton = (text) => {
    const searchURL = `https://twitter.com/search?q=${encodeURIComponent(text)}`;
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, "text/html");
    const docA = doc.getElementsByTagName("a")[0];
    docA.href = searchURL;
    a2akit.insertAdjacentElement("beforeend", docA);
};

(() => {
    // ネタバレ防止のため、コンテスト終了前(常設コンテストを除く)は無効とする
    if (!isContestOver())
        return;
    /** コンテストハッシュタグ 例: #AtCoder_abc210_a */
    const contestHashtag = contestID ? ` #AtCoder_${contestID}` : "";
    /** 問題ハッシュタグ 例: #AtCoder_abc210_a */
    const taskHashtag = taskID ? ` #AtCoder_${taskID}` : "";
    /** ユーザーハッシュタグ 例: #AtCoder_machikane */
    const userHashtag = userScreenName ? ` #AtCoder_${userScreenName}` : "";
    // ツイートボタンのテキストを取得する
    const textOrg = getTweetButtonText();
    if (!textOrg)
        return;
    // ツイートボタンのテキストを編集
    let text = textOrg + contestHashtag;
    if (pageType === "task") {
        // 問題ページ
        text = `${taskTitle} - ${contestTitle}${taskHashtag}${contestHashtag}${userHashtag}`;
    }
    else if (pageType === "submission") {
        // 個別の提出ページ
        text = `${taskTitle} - ${submissionsUser}の提出 - 結果 ${judgeStatus} ${judgeScore}点 - ${contestTitle}${taskHashtag}${contestHashtag}${userHashtag}`;
    }
    setTweetButtonText(text);
    // タグ検索ボタンを追加
    /** 検索タグ 問題ハッシュタグ なければコンテストハッシュタグ */
    const searchTag = taskHashtag || contestHashtag;
    addTweetSearchButton(searchTag.trim());
})();