ツイートボタンの埋め込みテキストに情報を追加します
目前為
// ==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());
})();