Ctrl+CでタイトルとURLをコピー

Shift+C:除去文字列設定  Shift+T:後回しタイトル設定 (YouTube)Alt+c/Ctrl+x:列挙

  1. // ==UserScript==
  2. // @name Ctrl+CでタイトルとURLをコピー
  3. // @description Shift+C:除去文字列設定  Shift+T:後回しタイトル設定 (YouTube)Alt+c/Ctrl+x:列挙
  4. // @match *://*/*
  5. // @version 0.7.12
  6. // @run-at document-idle
  7. // @grant GM.setClipboard
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @grant GM_deleteValue
  11. // @namespace https://greasyfork.org/users/181558
  12. // @require https://code.jquery.com/jquery-3.3.1.min.js
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. const cleanUrlFirst = 0; // 1:ページ読み込み時にURLのゴミを除去Lv1 2:同Lv2(さらに除去)
  17. const DISPLAY_TITLE_URL = 0; // 1:Alt+c時にタイトルとURLの詳細をポップアップで表示(推奨)
  18. const DISPLAY_ENUM = 0; // 1:Alt+c時にタイトルとURLの列挙の詳細をポップアップで表示(推奨)
  19. const ALTC_VARIATIONS = 2; // Alt+cを複数回押した時ソート条件を何番目まで使うか 1:並べ替えない 2:名前順 3:URL昇順 4:URL降順
  20. const YOUTUBE_WATCH_CTRLC_URL_VARIATIONS = 3; // YouTube動画視聴画面でCtrl+Cの機能を何番目まで使うか 1:パラメータ除去(推奨) 2:再生リストを残す 3:再生リストを残す+投稿者名
  21. const YOUTUBE_GENERATE_PLAYLIST = 0; // 1:YouTube検索結果のAlt+cの最後で画面中の動画を連続再生するURLをコピー (「YouTube検索結果「全てキューに入れて再生」ボタンを追加」の注意事項を熟読)
  22. let originaltitle = document?.title || "";
  23.  
  24. // Open Multiple URLs併用推奨
  25.  
  26. const ALTC_URLMAX = 50;
  27. var YOUTUBE_SEARCH_ALTC_USE_PLAYLIST_URL = lh(/:\/\/(www\.)?(youtube|youtu\.be)\.com/) && YOUTUBE_GENERATE_PLAYLIST
  28.  
  29. String.prototype.match0 = function(re) { let tmp = this.match(re); if (!tmp) { return null } else if (tmp.length > 1) { return tmp[1] } else return tmp[0] } // gフラグ不可
  30.  
  31. if (window != parent) return;
  32.  
  33. const VARIATIONS = ALTC_VARIATIONS + YOUTUBE_SEARCH_ALTC_USE_PLAYLIST_URL
  34. const ALTC_SITES = [/seiga\.nicovideo\.jp\/my\/manga\/favorite/,
  35. /webcomics\.jp\/mylist/,
  36. /webcomics\.jp\/$/,
  37. /webcomics.jp\/total$/,
  38. /webcomics\.jp\/bookmark/,
  39. /webcomics\.jp\/ranking/,
  40. /www.nicovideo.jp\/my\/watchlater/,
  41. /www\.nicovideo\.jp\/my\/mylist/,
  42. /seiga\.nicovideo\.jp\/my\/clip/,
  43. /www\.nicovideo\.jp\/my\/fav\//,
  44. /www\.nicovideo\.jp\/my\/channel/,
  45. /www\.nicovideo\.jp\/my\/community/,
  46. /www\.nicovideo\.jp\/my\/top\/all/,
  47. /https:\/\/www\.nicovideo\.jp\/my/,
  48. /\/\/www\.nicovideo\.jp\/my\/\?ref=pc_mypage_menu/,
  49. /\/\/www\.nicovideo\.jp\/my\/$/,
  50. /https:\/\/www\.nicovideo\.jp\/my\/nicorepo\//,
  51. /https:\/\/www\.nicovideo\.jp\/user\//,
  52. /\/\/.*\.5ch\.net\/\w*\/$/,
  53. /twicomi\.com\/author\//,
  54. /youtube\.com\/$/,
  55. /youtube\.com\/(?:channel\/|c\/|user\/|@)[^/]+\/(?:videos|shorts|playlist|streams)/,
  56. /youtube\.com\/playlist/,
  57. ///^https:\/\/www\.youtube\.com\/@[^\/]+\/search\?query=/,
  58. /www\.youtube\.com\/results/,
  59. ///\/\/www.youtube.com\/(?:channel\/|c\/|user\/|@)[^\/]+\/search\?query=/,
  60. /\/\/www.youtube.com\/(?:channel\/|c\/|user\/|@)[^\/]+\/search/,
  61. /https:\/\/www\.nicovideo\.jp\/search\//,
  62. /\/\/www.nicovideo.jp\/user\/[^/]+\/video/,
  63. /\/\/www.nicovideo.jp\/my\/watchlater/,
  64. /\/\/www.nicovideo.jp\/my\/mylist/,
  65. /\/\/www.nicovideo.jp\/user\/\d+\/mylist\/\d+/,
  66. /\/\/webcomics\.jp/,
  67. /search\.bilibili\.com/,
  68. /\/\/free\.arinco\.org\/mail\//,
  69. /\/\/www\.suruga-ya\.jp\/cargo\/detail/
  70. ];
  71. // e2::
  72. var altc = [{
  73. is: () => lh(`https://www.netoff.co.jp/`),
  74. getfunc: n => {
  75. return elegeta('li.clearfix').map(box => {
  76. return {
  77. title: `${et('p[class*="mat"]',box)} ${et('.subinfo',box)} ${et('.price',box)}`?.replace(/\s+| +/gm, " "),
  78. url: eleget0('p[class*="mat"] > a', box)?.href,
  79. aele: eleget0('p[class*="mat"] > a', box),
  80. }
  81. })
  82. }
  83. }, {
  84. is: () => lh(`https://www.tiktok.com/`),
  85. getfunc: n => elegeta('a').filter(e => e?.href?.match(/^https:\/\/www\.tiktok\.com\/[^\/]+\/video\/\d+$/)).map(box => {
  86. return {
  87. title: `${box.href?.match0(/https:\/\/www\.tiktok\.com\/([^\/]+\/video\/\d+)/)}`,
  88. url: box?.href,
  89. aele: box,
  90. }
  91. })
  92. }];
  93.  
  94. var mllID;
  95. var sakujoRE = pref("ctrlcsakujoRE") || "";
  96. var kaisuu = 0;
  97. var sakujoTitleRE = pref("ctrlcsakujoTitleRE") || "";
  98. var ret = (navigator.platform.indexOf("Win") != -1) ? "\r\n" : "\n";
  99.  
  100. String.prototype.tr = function(errorOrigin = "Shift+C") {
  101. let str = this;
  102. if (sakujoRE) {
  103. try {
  104. str = this.replace(RegExp(sakujoRE, "gm"), "")?.trim()?.replace(/(\s+)$/gm, "")
  105. } catch (e) { alert(errorOrigin + ":\n文法エラーのようです\n\n設定値:\n" + sakujoRE); }
  106. }
  107. return str;
  108. }
  109. // String.prototype.tr = function(errorOrigin = "Shift+C") { return tryReplace(this, sakujoRE, errorOrigin) }
  110.  
  111. if (cleanUrlFirst) {
  112. var cuf = () => { let newurl = modUrl(cleanUrlFirst); if (newurl != location.href) window.history.pushState(null, null, newurl); }
  113. cuf();
  114. setTimeout(cuf, 2000)
  115. }
  116.  
  117. // Shift+T ページタイトル部分後回し
  118. sakujotitle(sakujoTitleRE);
  119.  
  120. function sakujotitle(sakujoTitleRE) {
  121. if (sakujoTitleRE) {
  122. try {
  123. var hit = document.title.match(RegExp(sakujoTitleRE, "g"));
  124. if (hit) document.title = document.title.replace(RegExp(sakujoTitleRE, "g"), "") + " " + hit;
  125. } catch (e) { alert("Shift+T:\n文法エラーのようです\n\n設定値:\n" + sakujoTitleRE); }
  126. }
  127. }
  128. // 検索ワードをページタイトルの最初に付ける
  129. addtitle(/^https?:\/\/calil.jp\/local\/search/, "", '//form[@class="search"]/div/input|//input[@id="query"]'); // calil検索
  130. addtitle(/^https?:\/\/tv\.yahoo\.co\.jp\/search\/\?q=/, "", '//input[@class="generic_inputText floatl"]'); // yahooテレビ検索
  131. addtitle(/^https?:\/\/www\.nicovideo\.jp\/mylist_search\//, "", '//input[@id="search_united"]', "", "", " - マイリスト検索 "); // ニコ動マイリスト検索
  132. addtitle(/^https?:\/\/www\.jstage\.jst\.go\.jp\/result/, "", '//span[@class="search-parameter"]'); // J-STAGE詳細検索結果
  133. setTimeout(() => { addtitle(/^https?:\/\/www\.pinterest\.jp\/search\/pins\/.*q=/, "", '//input[@role="combobox"]') }, 2500); // Pinterest詳細検索結果
  134.  
  135. if (document.title.indexOf("|") === -1) addtitle(/^https?:\/\/www\.suruga-ya\.jp\/search\?/, "", '//input[@id="searchText"]', '//div[@id="topicPath"]', /駿河屋TOP.≫.|駿河屋TOP/gi); // 駿河屋検索
  136.  
  137. document.addEventListener("keydown", async function(e) { //キー入力
  138. if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable || ((e.target.closest('#chat-messages,ytd-comments-header-renderer') || document.activeElement.closest('#chat-messages,ytd-comments-header-renderer')))) return;
  139. var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key;
  140. if (e.shiftKey && !e.altKey && !e.ctrlKey && e.which == 84) { // shift+T ページタイトル部分後回し文字列設定
  141. var sample = "ありません";
  142. if (location.href.indexOf("://www.nicovideo.jp") !== -1) sample = "キーワードで動画検索 |キーワードで|」動画|人気の「";
  143. if (location.href.indexOf("://www.amazon.co.jp") !== -1) sample = "^Amazon(?:\\.co\\.jp)*[\\s\\::\\||-]{0,4}|^カスタマーレビュー(?:\\.co\\.jp)*[\\s\\::\\||-]{0,4}"; //"Amazon.co.jp\s?: |Amazon.co.jp: |Amazon \|\s|Amazon||Amazon.co.jp:カスタマーレビュー: |Amazon - |Amazon.co.jp - ";
  144. if (location.href.indexOf("auctions.yahoo.co.jp") !== -1) sample = "ヤフオク! -";
  145. if (location.href.indexOf("kakaku.com/") !== -1) sample = "価格.com - ";
  146. if (location.href.indexOf("//www.yodobashi.com/") !== -1) sample = "ヨドバシ.com - ";
  147. if (location.href.indexOf("//www.sukima.me/book/title/") !== -1) sample = "^\\[.*\\]";
  148. var str = prompt("shift+T:\r\n\"" + document.domain + "\" でページタイトルの中で後ろに移動したい文字列を正規表現で入力してください\r\n\r\nこのサイトでのお薦め設定例:\r\n" + sample + "\r\n\r\n現在値:" + (sakujoTitleRE || "なし") + "\n\n", sakujoTitleRE || "");
  149. sakujoTitleRE = str === null ? sakujoTitleRE : str;
  150. if (sakujoTitleRE != "") {
  151. pref("ctrlcsakujoTitleRE", sakujoTitleRE);
  152. sakujotitle(sakujoTitleRE);
  153. } else {
  154. pref("ctrlcsakujoTitleRE", "");
  155. }
  156. return;
  157. }
  158. if (key == "Shift+C") { //if (e.shiftKey && !e.altKey && !e.ctrlKey && e.which == 67) { // shift+C 除去文字列設定
  159. var str = prompt("shift+C:\r\n\"" + document.domain + "\" で Ctrl+C 押下時タイトルやURLや選択文字列から除去したい文字列を正規表現で入力してください\r\n\r\n現在値:" + sakujoRE + "\r\n", sakujoRE);
  160. sakujoRE = str === null ? sakujoRE : str;
  161. if (sakujoRE != "") {
  162. pref("ctrlcsakujoRE", sakujoRE);
  163. } else {
  164. pref("ctrlcsakujoRE", "");
  165. }
  166. return;
  167. }
  168. if (key === "Ctrl+c" || (/^Alt\+c$|^Ctrl\+x$/.test(key) && (ALTC_SITES.some(v => v.test(location.href)) || (altc.find(v => v.is()))))) { // ctrl+c::
  169. if (window.getSelection() != "") {
  170. // 選択文字列がある
  171. var selection = window.getSelection().toString();
  172. if (sakujoRE) { // 除去文字列があれば除去してコピーも自前(なければブラウザにさせる)
  173. selection = tryReplace(selection, sakujoRE, "Shift+C");
  174. if (window.getSelection().toString() !== selection) {
  175. copy2cb(selection);
  176. e.preventDefault();
  177. }
  178. }
  179. } else {
  180.  
  181. // 選択文字列がない
  182.  
  183. var doc = location.href;
  184. var txt1 = doc;
  185. var txt2 = txt1;
  186. var opt = "";
  187. var idEnum = [];
  188. var urlmax
  189. var bgcolor = undefined
  190.  
  191. var txt2 = modUrl(2, kaisuu);
  192.  
  193. if (/www\.youtube\.com\/embed\//.test(txt1)) txt2 = txt1.replace(/\?.*/, "").replace(/embed\//, "watch?v="); // youtube埋め込みを正規のページに
  194.  
  195. var ret = (navigator.platform.indexOf("Win") != -1) ? "\r\n" : "\n";
  196. // var title = document.title.replace(/ https?:.*/, "");
  197. var title = document.title.replace(/ https?:\S*/g, "");
  198.  
  199. // 複数回押した時の追加
  200. let pprele = elegeta('.pprcccopy , span.kangengo').slice(0, 3)
  201. //let pprele = elegeta('.pprcccopy').slice(0, 2)
  202. if (kaisuu % 2 && pprele.length) {
  203. title = title.trim() + pprele.map(e => " " + e.innerText?.replace(/(還元後:([\\¥\d,\.]+))/, "$1")).join("")
  204. bgcolor = "#6080b0"
  205. }
  206. if (kaisuu % 2 && lh('https://pubmed.ncbi.nlm.nih.gov/')) {
  207. title = title.trim() + eleget0('div.article-source > span.cit')?.textContent?.replace(/[\s。]*$/, "")
  208. bgcolor = "#6080b0"
  209. }
  210.  
  211. if (lh('https://chromewebstore.google.com/detail/') && kaisuu % 2 == 1) {
  212. bgcolor = "#6080b0";
  213. title += " - " + elegeta('div.j3zrsd , ul > li.ZbWJPd > div:visible').map(v => v.innerText?.replace(/\n/gm, " ") || "").filter(v => !"懸念を報告する 懸念事項を報告".split(" ").includes(v)).slice(0, 11).join(" ")
  214. }
  215. if (lh('https://addons.mozilla.org/ja/firefox/addon/') && kaisuu % 2 == 1) {
  216. bgcolor = "#6080b0";
  217. title += " - " + elegeta('.MetadataCard,AddonMeta-overallRating,.AddonTitle-author dd.Definition-dd.AddonMoreInfo-version,dd.Definition-dd.AddonMoreInfo-last-updated').map(v => v.innerText?.replace(/\n/gm, " ") || "").filter(v => v != "懸念を報告する").slice(0, 9).join(" ")
  218. }
  219. if (lh("2chan.net")) { if (kaisuu % 2 == 1) { bgcolor = "#6080b0"; } else { title = originaltitle; } }
  220. title = title.tr()
  221. if (kaisuu % 1 == 0) {
  222. var txt = title + ret + txt2 + ret;
  223. var bal = title + "\n" + txt2;
  224. }
  225. var sort = "";
  226.  
  227. // YouTube動画視聴画面なら
  228. elegeta("#link4bm").forEach(e => e.remove())
  229. if (location.href.match(/^https?:\/\/www\.youtube\.com\/watch\?v\=/)) {
  230. let translatedTitle = eleget0('//h1[@class="style-scope ytd-watch-metadata"]|//h1[@class=\"title style-scope ytd-video-primary-info-renderer\"]/yt-formatted-string/font/font|//div[@id=\"title\" and contains(@class,\"style-scope ytd-watch-metadata\")]/h1/yt-formatted-string/font/font')?.innerText || document?.title
  231. if (!translatedTitle.match(/ - YouTube$/)) translatedTitle += " - YouTube"
  232. kaisuu = (kaisuu % YOUTUBE_WATCH_CTRLC_URL_VARIATIONS) + 1;
  233. if (kaisuu == 2) {
  234. let cb = (translatedTitle || document.title)?.tr() + ret + deleteParam(["start_radio=", "index="], location.href)
  235. popup(DISPLAY_TITLE_URL ? cb : "タイトルとURL(パラメータを残す)")
  236. GM.setClipboard(cb + ret);
  237. e.preventDefault();
  238. } else if (kaisuu == 3) {
  239. eleget0('div#description-inner > ytd-text-inline-expander.style-scope > tp-yt-paper-button#expand.style-scope[role="button"]')?.click();
  240. await wait();
  241. let uploader = eleget0('yt-formatted-string#text.style-scope.ytd-channel-name a.yt-simple-endpoint.style-scope.yt-formatted-string:visible')?.textContent || ""
  242. if (uploader) uploader = ` - ${uploader}`
  243. let playlist = lh(/\&list=PL|\&list=UULP|\&list=UL01234567890|\&list=ULcxqQ59vzyTk/) && eleget0('yt-formatted-string.title.style-scope.ytd-playlist-panel-renderer.complex-string a.yt-simple-endpoint.style-scope.yt-formatted-string:visible')?.textContent || "";
  244. if (playlist) playlist = ` - ${playlist}`
  245. let desc = sani((eleget0('ytd-text-inline-expander#description-inline-expander.style-scope.ytd-watch-metadata')?.innerText || "")?.replace(/\n+|\s+/gm, " ")?.slice(0, 60)?.trim())
  246. if (desc) desc = ` - ${desc}`
  247. let cb = (translatedTitle || document.title)?.tr() + `${uploader}${playlist}` + ret + deleteParam(["start_radio=", "index="], location.href);
  248. popup(DISPLAY_TITLE_URL ? cb : "タイトルと投稿者名とプレイリスト名とURL(パラメータを残す)", "#44a")
  249. if (kaisuu != 2) $(`<div style="margin-left:4em;font-size:14px;" id="link4bm" title="ブラウザからブックマークを検索する時に便利なようにタイトルに投稿者、プレイリスト名、概要欄の情報を増やしただけのリンクです">ブックマーク用リンク<br><a href="${deleteParam(["start_radio=", "index="], location.href)}">${title}${uploader}${playlist}${desc}</a></div>`).hide(0).insertAfter($('#logo')).show(150).delay(9999).hide(250, function() { $(this).remove() })
  250. GM.setClipboard(cb + ret);
  251. e.preventDefault();
  252. } else {
  253. let cb = (translatedTitle || document.title)?.tr() + ret + deleteParam(["list=", "t=", "index=", "start_radio="], location.href)
  254. popup(DISPLAY_TITLE_URL ? cb : "タイトルとURL(パラメータ削除)")
  255. GM.setClipboard(cb + ret);
  256. e.preventDefault();
  257. }
  258. return false;
  259. }
  260.  
  261.  
  262. // 列挙するタイプのサイトなら
  263. if (/^Alt\+c$|^Ctrl\+x$/.test(key) && (ALTC_SITES.some(v => v.test(location.href)) || (altc.find(v => v.is())))) { // Alt+c::
  264.  
  265. // e1::
  266. var rekkyo = [
  267. '//span[@class="VideoMediaObject-bodyTitle"]/../..',
  268. '//div[@class="title"]/a',
  269. `div.entry-title>a:first-child`, //'//div[@class="entry-title"]/a[1]',
  270. '//div[@class="mylistVideo"]/h5/a',
  271. '//div[@class="illust_box_li cfix"]/div/div[@class="text_ttl"]/a',
  272. '//div[@class="outer"]/div/h5/a', '//div[@class="outer"]/h5/a',
  273. '//div[@class="log-target-info"]/a',
  274. '//div[@class="mylistVideo MylistItem-videoDetail"]/h5/a|//div[@id="mainWrap"]/div[@id="main"]/ul[contains(@class,"itemList")]/li[@class="clearfix"]/dl/dd/p/a',
  275. '//div[@id="thread-list-box"]/table[@id="thread-list"]/tbody/tr/td/a',
  276.  
  277. '//span[@class="NicorepoItem-contentDetailTitle"]', // ニコレポ
  278. '//a[@id="video-title"]', // youtube search
  279. '//a[@id="video-title-link"]', // youtube top
  280. 'h3 > a.yt-simple-endpoint.focus-on-expand.ytd-rich-grid-slim-media > span.style-scope.ytd-rich-grid-slim-media , a.ShortsLockupViewModelHostEndpoint.ShortsLockupViewModelHostOutsideMetadataEndpoint', //'//span[@id="video-title"]', // '//div[@id="details"]/a/h3/span[@id="video-title"]', // youtube channel/shorts
  281. '//a[@class="manga-image"]', // twicomi
  282. 'p.itemTitle > a[target="_blank"]', // ニコ動検索
  283. '//h2[@class="NC-MediaObjectTitle NC-VideoMediaObject-title NC-MediaObjectTitle_fixed2Line"]', // ニコ動user,あとでみる動画
  284. '//h3[@class="bili-video-card__info--tit"]', // bilibili search
  285. '//ul[@class="itemlist clear stage m0"]/li/a', // arinco
  286. '//p[@class="item_name"]/a[1]', // 駿河屋カート
  287. `div.TimelineItem-contentTitle`, // ニコ動フォロー新着
  288. ];
  289. txt = "";
  290. txt2 = "";
  291. var num = 0;
  292. var youtubeContPlay = YOUTUBE_SEARCH_ALTC_USE_PLAYLIST_URL && (kaisuu % (VARIATIONS)) == VARIATIONS - 1 && lh(/youtube\.com/)
  293.  
  294. let rekkyo2 = altc?.find(v => v.is())?.getfunc() || []
  295. rekkyo2?.forEach(v => v.aele.innerTextZ2 = v.title)
  296.  
  297. var ele = [...rekkyo2.map(v => v?.aele), ...[...new Set(rekkyo.reduce((a, b) => a.concat(elegeta(b + ":visible")), []).flat())]]
  298. if (lh("youtube.com/playlist")) ele = ele.filter(e => !eleget0('//div/h2/span[@class="style-scope ytd-shelf-renderer" and contains(text(),"おすすめのプレイリスト")]', e.closest(`ytd-item-section-renderer`))) // プレイリスト画面の下に出るおすすめプレイリストを除外
  299.  
  300. ele.forEach((a, i) => {
  301. if (!a.href) { // ニコ動マイリストだけの取り込み方(名前順ソート不可
  302. a.innerTextZ = a.innerText.replace(/\n/gm, " ")
  303. a.href = a.closest("a").href;
  304. } else if (location.href.match(/\/\/twicomi\.com/)) { // twicomiだけの取り込み方
  305. let titleEle = eleget0('.//div[@class="tweet-text"]|.//div[@class="tweet-data"]', a.parentNode.parentNode.parentNode)
  306. if (titleEle && titleEle.textContent) a.innerTextZ = titleEle.textContent.replace(/\n/gm, " ")?.trim() || "";
  307. } else {
  308. a.innerTextZ = a.innerTextZ2 || a.innerText?.trim();
  309. }
  310. })
  311.  
  312. ele = ele.reduce((a, v) => { if (!a.some((e) => (e.href === v.href && e.innerTextZ === v.innerTextZ))) { a.push(v); } return a; }, []); // uniq
  313.  
  314. if (!youtubeContPlay) {
  315. if (kaisuu % VARIATIONS == 1) {
  316. sort = " 名前↓"; // ヤフオクのAキーと結果が違うのは向こうはソートにショート動画を含めず(画面が崩れるので)、こちらは含めるから
  317. ele.sort(function(a, b) { return new Intl.Collator("ja", { numeric: true, sensitivity: 'base' }).compare(a.innerTextZ, b.innerTextZ) });
  318. }
  319. if (kaisuu % VARIATIONS == 2) {
  320. sort = " URL↓";
  321. ele.sort(function(a, b) { return (a.href.split(/\/\//g)[1]) > (b.href.split(/\/\//g)[1]) ? 1 : -1; });
  322. }
  323. if (kaisuu % VARIATIONS == 3) {
  324. sort = " URL↑";
  325. ele.sort(function(a, b) { return (a.href.split(/\/\//g)[1]) < (b.href.split(/\/\//g)[1]) ? 1 : -1; });
  326. }
  327. }
  328. for (let a of ele) {
  329. if (a.offsetHeight) { // 非表示じゃないやつだけ
  330. var cleanurl = a.href.replace(/\?track=.*|\?_topic=.*|\?ref=.*|\&list=.*|\&.+/g, "")
  331. if (location.href.match(/\/\/www.nicovideo.jp\/my\//)) { // ニコ動マイリストだけの取り込み方
  332. cleanurl = a.href.replace(/\?.*/g, "")
  333. }
  334.  
  335. var ytID = cleanurl.match0(/^https?\:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_\-]{11})/) || cleanurl?.match0(/^https?\:\/\/www\.youtube\.com\/shorts\/([a-zA-Z0-9_\-]{11})/)
  336. if (ytID) {
  337. idEnum.push(ytID);
  338. a.innerTextZ += ` - YouTube`
  339. }
  340.  
  341. if (DISPLAY_ENUM) {
  342. txt2 += a.innerTextZ.tr() + ret + cleanurl + ret;
  343. }
  344. txt += a.innerTextZ.replace(/\n/gm, " ").tr() + ret + cleanurl + ret;
  345. num++;
  346. }
  347. }
  348. idEnum = [...new Set(idEnum)]
  349.  
  350. // YouTube連続再生URL
  351. if (youtubeContPlay && location.href.match0("youtube.com")) {
  352. urlmax = 50;
  353. var enumText = ``
  354. for (let u = 0; u < idEnum.length / urlmax; u++) {
  355. enumText += `▶ ${u*urlmax+1}/${idEnum.length} ${document.title}\nhttps://www.youtube.com/watch_videos?video_ids=${ (idEnum.slice(u*urlmax, u*urlmax+urlmax).join(",") ) }${ret}`
  356. }
  357. num = idEnum.length; //urlmax
  358. }
  359.  
  360. title += " " + num + "件" + sort;
  361. txt = title + ret + txt;
  362.  
  363. // YouTube連続再生URL
  364. if (youtubeContPlay) {
  365. sort = ""
  366. txt = (enumText ? document.title + ret + enumText + ret : "");
  367. txt2 = (youtubePopupContPlay(idEnum.slice(0, Math.min(idEnum.length, num, urlmax))), enumText)
  368. txt = txt2
  369. }
  370.  
  371. /*if (ADD_OPEN_MULTIPLE_URL_GUIDE) {
  372. txt += "\nOpen Multiple URLs - Google 検索\nhttps://www.google.co.jp/search?q=Open%20Multiple%20URLs&lr=lang_ja\n";
  373. txt2 += `\nOpen Multiple URLs - Google 検索\nhttps://www.google.co.jp/search?q=Open%20Multiple%20URLs&lr=lang_ja\n`;
  374. }*/
  375. var bal = `${title}\n${txt2}`
  376. var mintitle = youtubeContPlay ? `連続再生URL` : "項目の列挙"; //var mintitle = youtubeContPlay ? `連続再生URL(上限${urlmax})` : "項目の列挙"
  377. if (DISPLAY_TITLE_URL && DISPLAY_ENUM) popup(mintitle + "\n" + bal);
  378. if (DISPLAY_TITLE_URL && !DISPLAY_ENUM) popup(mintitle + "\n" + title + "\n");
  379. if (!DISPLAY_TITLE_URL) popup(mintitle + " " + num + "件" + "" + sort + "\n");
  380. } else {
  381. if (DISPLAY_TITLE_URL) {
  382. popup(bal + opt, bgcolor);
  383. } else {
  384. popup("タイトルとURL\n" + opt, bgcolor);
  385. }
  386. }
  387.  
  388. // クリップボードにコピー
  389. copy2cb(txt);
  390. e.preventDefault();
  391. }
  392. kaisuu++;
  393. return;
  394. }
  395. },
  396. false);
  397. return;
  398.  
  399. function elegeta(xpath, node = document) {
  400. if (!xpath || !node) return [];
  401. let flag
  402. if (!/^\.?\//.test(xpath)) return /:visible$/.test(xpath) ? [...node.querySelectorAll(xpath.replace(/:visible$/, ""))].filter(e => e.offsetHeight) : [...node.querySelectorAll(xpath)]
  403. try {
  404. var array = [];
  405. var ele = document.evaluate("." + xpath.replace(/:visible$/, ""), node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  406. let l = ele.snapshotLength;
  407. for (var i = 0; i < l; i++) array[i] = ele.snapshotItem(i);
  408. return /:visible$/.test(xpath) ? array.filter(e => e.offsetHeight) : array;
  409. } catch (e) { alert(e + ret + xpath + ret + JSON.stringify(node), 1); return []; }
  410. }
  411.  
  412. function eleget0(xpath, node = document) {
  413. if (!xpath || !node) return null;
  414. if (!/^\.?\//.test(xpath)) return /:visible$/.test(xpath) ? [...node.querySelectorAll(xpath.replace(/:visible$/, ""))].filter(e => e.offsetHeight)[0] ?? null : node.querySelector(xpath.replace(/:visible$/, ""));
  415. try {
  416. var ele = document.evaluate("." + xpath.replace(/:visible$/, ""), node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
  417. return ele.snapshotLength > 0 ? ele.snapshotItem(0) : null;
  418. } catch (e) { alert(e + ret + xpath + ret + JSON.stringify(node)); return null; }
  419. }
  420.  
  421. function et(xpath, node = document) {
  422. return eleget0(xpath, node)?.innerText?.replace(/\n/gm, " ")
  423. }
  424. // タイトルに足す
  425. function addtitle(url, txt1, xpath, optionxpath = "", optionReplaceRE = /$^/, separator = " - ") {
  426. if (!url.test(location.href)) return;
  427. var ele = eleget0(xpath);
  428. if (!ele) return;
  429. ele = ele.value || ele.innerText;
  430. var ret = ele.trim() > "" ? ele.trim() + separator : "";
  431. if (optionxpath && eleget0(optionxpath)) {
  432. ret += "" + (eleget0(optionxpath).innerText.trim().replace(optionReplaceRE, "")) + " ";
  433. }
  434. document.title = ret + document.title;
  435. return;
  436. }
  437.  
  438. // クリップボードにコピー
  439. function copy2cb(txt) {
  440. GM.setClipboard(txt);
  441. }
  442.  
  443. function deleteParam(cutREs, txt1) { //余計なパラメータを除去
  444. var para = txt1.split(/[&?#]/);
  445. var txt2 = para[0] + "?";
  446. var j = 0;
  447. for (var i = 1; i < para.length; i++) {
  448. for (let reptxt of cutREs) {
  449. para[i] = para[i].replace(new RegExp("^" + reptxt + ".*"), "");
  450. }
  451. if (para[i] !== "") {
  452. txt2 += (j++ > 0 ? "&" : "") + para[i];
  453. }
  454. }
  455. return txt2.replace(/\?$/, ""); //行末が?なら削除
  456. }
  457.  
  458. function modUrl(forceLevel = 2, kaisuu = 0) {
  459. var txt1 = location.href;
  460. var txt2 = txt1;
  461.  
  462. if (/^https?:\/\/www\.amazon\.co\.jp\/|^https?:\/\/www\.amazon\.com\//.test(txt1) && (txt1.indexOf("/dp/product/") != -1)) // Amazonのパラメータ除去
  463. txt2 = "https://" + document.domain + "/dp/" + txt1.substr(txt1.indexOf("/dp/product/") + 12, 10);
  464. else if (/^https?:\/\/www\.amazon\.co\.jp\/|^https?:\/\/www\.amazon\.com\//.test(txt1) && (txt1.indexOf("/dp/") != -1)) // Amazonのパラメータ除去
  465. txt2 = "https://" + document.domain + "/dp/" + txt1.substr(txt1.indexOf("/dp/") + 4, 10);
  466. if (/^https?:\/\/www\.amazon\.co\.jp\/|^https?:\/\/www\.amazon\.com\//.test(txt1) && (txt1.indexOf("/ASIN/") != -1)) // Amazonのパラメータ除去
  467. txt2 = "https://www.amazon.co.jp/dp/" + txt1.substr(txt1.indexOf("/ASIN/") + 6, 10);
  468. if (/^https?:\/\/www\.amazon\.co\.jp\/|^https?:\/\/www\.amazon\.com\//.test(txt1) && (txt1.indexOf("/gp/product/") != -1)) // Amazonのパラメータ除去
  469. txt2 = "https://www.amazon.co.jp/dp/" + txt1.substr(txt1.indexOf("/gp/product/") + 12, 10);
  470. if (/^https?:\/\/www\.amazon\.co\.jp\/gp\/customer-reviews\//.test(txt1)) // Amazonのカスタマーレビューのパラメータ除去
  471. txt2 = deleteParam(["ref=", "ie=", "ASIN="], txt1).replace(/\/ref=.*/, "");
  472. txt1 = txt1.replace(/(^https?:\/\/www\.amazon\.co\.jp\/)(.*)(product-reviews\/)/, "$1$3");
  473. if (txt1.match(/^https?:\/\/www\.amazon\.co\.jp\/.*product-reviews\//)) // Amazonのカスタマーレビューのパラメータ除去
  474. txt2 = deleteParam(["ref=", "ie=", "ASIN="], txt1).replace(/\/ref=.*/, "");
  475.  
  476. if (/^https?:\/\/www\.google\.co\.jp\/search\?|^https?:\/\/www\.google\.com\/search\?/.test(txt1)) // google検索結果のパラメータ除去
  477. txt2 = deleteParam(["ei=", "oq=", "gs_l=", "hl=", "source=", "sa=", "ved=", "biw=", "bih=", "dpr=", "ie=", "oe=", "client=", "aqs=", "sourceid=", "btgG=", "gs_lcp=", "sclient=", "uact=", "iflsig=", "ictx=", "fir=", "vet=", "usg=", "imgrc=", "sca_esv=", "gs_lp=", "sca_upv="], txt1);
  478.  
  479. if (/^https?:\/\/books\.google\.co\.jp\/books\?/.test(txt1)) // Googleブックス検索のパラメータ除去
  480. txt2 = deleteParam(["souce=", "ots=", "sig=", "hl=", "sa=", "ved=", "f=", "lpg=", "dq=", "source=", "f=", "v=", kaisuu % 2 == 0 ? "$^" : "q="], txt1); // q=を残すと検索ワードは残る
  481.  
  482. if (/^https?:\/\/www\.ted\.com\/talks/.test(txt1)) // TEDのパラメータ除去
  483. txt2 = deleteParam(["awesm=", "utm_medium=", "share=", "utm_source=", "utm_campaign=", "utm_content=", "source=", "embed=", "t-", "frm_id=", "device_id=", "fb_action_ids=", "action_type_map=", "action_object_map=", "fb_source=", "fb_action_types", "action_ref_map=", "ref=", "refid=", "_ft_=", "guid="], txt1);
  484.  
  485. if (/^https?:\/\/translate\.google\.com\/translate|^https?:\/\/translate\.googleusercontent\.com\/translate_c/.test(txt2)) { // google翻訳のパラメータ除去
  486. txt2 = (txt2.match(/^https?:\/\/translate\.google\.com\/translate|^https?:\/\/translate\.googleusercontent\.com\/translate_c/)[0] + txt2.match(/[\?&]u=[^&]*/)).replace(/&/, "?");
  487. }
  488.  
  489. if (/^https?:\/\/www\.nicovideo\.jp\//.test(txt1)) {
  490. txt2 = deleteParam(["ref=", "ss_id=", "cmnhd_ref="], txt2); // ニコ動のパラメータ除去
  491. if (forceLevel >= 2) txt2 = deleteParam(["playlist="], txt2); // ニコ動のパラメータ除去
  492. if (kaisuu % 2 == 0) txt2 = deleteParam(["ss_pos=", "continuous=", "sort=", "order="], txt2); // ニコ動のパラメータ除去
  493. }
  494.  
  495. if (/^https?:\/\/seiga\.nicovideo\.jp\//.test(txt1)) txt2 = deleteParam(["ref=", "cmnhd_ref=", "track="], txt2); // ニコ動のパラメータ除去
  496.  
  497. if (/^https?:\/\/www\.ebay\.com\/itm\/.*\?hash=/.test(txt1)) txt2 = txt1.replace(/\?hash=.*$/, ""); // eBayのパラメータ除去
  498. if (/^https?:\/\/ja.aliexpress.com\/item\/.*?.html\?spm=/.test(txt1)) txt2 = txt1.replace(/\?spm=.*$/, ""); // AliExpressのパラメータ除去
  499. if (/^https?:\/\/www.mercari.com\/jp\/items\/m51992587701\/\?_s=/.test(txt1)) txt2 = txt1.replace(/\/\?_s=.*$/, ""); // メルカリのパラメータ除去
  500. if (/^https?:\/\/www.reddit.com\/r\/.*\?sort=confidence/.test(txt1)) txt2 = txt1.replace(/\?sort=confidence/, ""); // redditのパラメータ除去
  501. if (/^https?:\/\/www.pinterest.jp\/search\/pins\/\?q=/.test(txt1)) txt2 = txt1.replace(/\&rs=typed.*$/, ""); // pinterestのパラメータ除去
  502.  
  503. if (/^https:\/\/www\.ebay\.com\/itm\//.test(txt1)) txt2 = txt1.replace(/\?.*$/, ""); // eBayのパラメータ除去
  504. if (/^https:\/\/onlinelibrary\.wiley\.com\/doi\/full\//.test(txt1)) txt2 = txt1.replace(/\?casa_token=.*$/, ""); // ollのパラメータ除去
  505. if (/^https:\/\/www\.tiktok\.com\//.test(txt1)) txt2 = txt1.replace(/\?referer_url=.*$/, ""); // tiktokのパラメータ除去
  506. //if (/^https:\/\/www.uniqlo.com\/jp\/ja\/products\//.test(txt1)) txt2 = txt1.replace(/\?.*$/, ""); // ユニクロオンラインストアのパラメータ除去
  507. if (/^https?:\/\/www\.youtube\.com\//.test(txt1)) txt2 = deleteParam(["pp=", "bp="], txt2)
  508. if (/^https?:\/\/sundrug-online\.com\//.test(txt1)) txt2 = deleteParam(["_pos=", "_sid=", "_ss=", "variant="], txt2)
  509. if (/^https:\/\/www\.yodobashi\.com\//.test(txt1)) txt2 = deleteParam(["utm_medium=", "kad1=", "utm_source=", "utm_term=", "xfr="], txt2)
  510. if (/^https:\/\/comic-walker\.com\/detail\//.test(txt1)) txt2 = deleteParam(["episodeType="], txt2)
  511. if (/^https:\/\/manga\.nicovideo\.jp\/comic\//.test(txt1)) txt2 = deleteParam(["track="], txt2)
  512. if (/^https:\/\/manga\.nicovideo\.jp\/watch\//.test(txt1)) txt2 = deleteParam(["track="], txt2)
  513.  
  514. return txt2;
  515. }
  516.  
  517. function tryReplace(str, re, title) {
  518. if (re) {
  519. try {
  520. str = str.replace(RegExp(re, "gm"), "")?.trim()?.replace(/(\s+)$/gm, "")
  521. } catch (e) { alert(title + ":\n文法エラーのようです\n\n設定値:\n" + re); }
  522. }
  523. return str;
  524. }
  525.  
  526. function pref(name, store = undefined) { // pref(name,data)で書き込み(数値でも文字列でも配列でもオブジェクトでも可)、pref(name)で読み出し
  527. var domain = (location.href.match(/^https?:\/{2,}(.*?)(?:\/|\?|#|$)/)[1] || location.href);
  528. if (store === undefined) { // 読み出し
  529. let data = GM_getValue(domain + " ::: " + name)
  530. if (data == undefined) return store; // 値がないなら終わり
  531. if (data.substr(0, 1) === "[") { // 配列なのでJSONで返す
  532. try { return JSON.parse(data || '[]'); } catch (e) {
  533. console.log("データベースがバグってるのでクリアします\n" + e);
  534. pref(name, []);
  535. return;
  536. }
  537. } else return data;
  538. }
  539. if (store === "" || store === [] || store === null) { // 書き込み、削除
  540. GM_deleteValue(domain + " ::: " + name);
  541. return store;
  542. } else if (typeof store === "string") { // 書き込み、文字列
  543. GM_setValue(domain + " ::: " + name, store);
  544. return store;
  545. } else { // 書き込み、配列
  546. try { GM_setValue(domain + " ::: " + name, JSON.stringify(store)); } catch (e) {
  547. console.log("データベースがバグってるのでクリアします\n" + e);
  548. pref(name, "");
  549. }
  550. return store;
  551. }
  552. }
  553.  
  554. function popup(text, bgcolor = "#6080ff") {
  555. var e = document.getElementById("cccbox");
  556. if (e) { e.remove(); }
  557. if (mllID) { clearTimeout(mllID); }
  558. var ele = document.body.appendChild(document.createElement("span"));
  559. ele.className = "ignoreMe"
  560. text = text.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/`/g, '&#x60;').replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/gm, "<br>")
  561. if (/www\.translatetheweb\.com|\.translate\.goog\/|translate\.google\.com|\/embed\//gmi.test(location.href + " " + text)) bgcolor = "#d06050"
  562. ele.innerHTML = `<span id="cccbox" class="ignoreMe" title="クリックでこれを再度コピー" style="all:initial; max-height:100vh; overflow-y:auto; scrollbar-width:thin; ${text.match(/<br>/gmi)&&text.match(/<br>/gmi).length>5?"max-width:50%;":""} word-break:break-all; position: fixed; right:0em; top:0em; z-index:2147483647; opacity:1; font-size:15px; max-width:50%; font-weight:bold; margin:0px 1px; text-decoration:none !important; text-align:none; padding:1px 6px 1px 6px; border-radius:12px; background-color:${bgcolor }; color:white; ">${text}</span>`;
  563. mllID = setTimeout(function() { var ele = document.getElementById("cccbox"); if (ele) ele.remove(); }, 5000);
  564. ele.onclick = () => { var e = document.getElementById("cccbox"); if (e) { e.remove(); } if (mllID) { clearTimeout(mllID); } }
  565. }
  566.  
  567. function youtubePopupContPlay(eles2, indexNo = 0) {
  568. if (eles2.length == 0) return "";
  569. let kaisuu = 1
  570. let indexUrlQP = indexNo > 0 ? `&index=${indexNo}` : "";
  571. elegeta("#link4bm").forEach(e => e.remove())
  572. var cb = kaisuu == 2 ? `<iframe referrerpolicy="no-referrer" src="https://www.youtube.com/embed/${eles2[0]}?playlist=${ eles2.join(",")}" id="ytplayer" type="text/html" allowfullscreen="" allow="picture-in-picture" width="320" height="180" frameborder="0"></p></iframe>` : "https://www.youtube.com/watch_videos?video_ids=" + eles2.join(",") + indexUrlQP;
  573. var embedHTML = `<iframe referrerpolicy="no-referrer" src="${cb}" id="ytplayer" type="text/html" width=320 height=180 frameborder=0 allowfullscreen>`
  574. var cb2 = cb
  575. var cbEsc = (cb2).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;')
  576. var title = `▶ ${indexNo+1}/${eles2.length} ${document.title}`
  577. popup(kaisuu == 2 ? cb : document.title + ret + cb, "", "right:0em; top:0em;max-width:40%;")
  578. GM.setClipboard(kaisuu == 2 ? cb : `${document.title}\n${cb2}\n`);
  579. if (kaisuu != 2) $(`<div style="margin-left:4em;font-size:14px;" id="link4bm">${kaisuu==2?"埋め込み":"ブックマーク"}用リンク(${eles2.length})<br><a href=${cb}>${title}</a></div>`).hide(0).insertAfter($('#logo')).show(150).delay(9999).hide(250, function() { $(this).remove() })
  580. return `${title}\n`
  581. }
  582.  
  583. function lh(re) { let tmp = location.href.match(re); if (!tmp) { return null } else if (tmp.length > 1) { return tmp[1] } else return tmp[0] } // gフラグ不可
  584. function sani(s) { return s?.replace(/&/g, "&amp;")?.replace(/"/g, "&quot;")?.replace(/'/g, "&#39;")?.replace(/`/g, '&#x60;')?.replace(/</g, "&lt;")?.replace(/>/g, "&gt;") || "" }
  585.  
  586. function wait() {
  587. async function waitFrame() {
  588. return new Promise(resolve => requestAnimationFrame(() => { resolve() }));
  589. }
  590. }
  591.  
  592. })();
  593.