atcoder-hashtag-setter2

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

目前為 2022-11-13 提交的版本,檢視 最新版本

// ==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());
})();