// ==UserScript== // @name PixivのリンクをTweetに添えて // @name:ja PixivのリンクをTweetに添えて // @name:en Show me your Pixiv. // @version 1145141919810.1.2 // @description Tweetの画像の下に(プロフィール欄にあれば)その人のPixivのリンクを表示します。 // @description:ja Tweetの画像の下に(あれば)その人のPixivのリンクを表示します。 // @description:en Display a Pixiv link below the Tweet image. // @author ゆにてぃー // @match https://twitter.com/* // @match https://mobile.twitter.com/* // @connect api.twitter.com // @connect api.fanbox.cc // @connect skeb.jp // @connect fantia.jp // @connect booth.pm // @connect linktr.ee // @connect profcard.info // @connect lit.link // @connect potofu.me // @connect creatorlink.net // @icon https://www.google.com/s2/favicons?sz=64&domain=twitter.com // @grant GM_xmlhttpRequest // @license MIT // @namespace https://greasyfork.org/ja/users/1023652 // @downloadURL none // ==/UserScript== (function() { 'use strict'; const desktop_selector = {'tweet_field': 'article[data-testid="tweet"]','media_field': '.css-1dbjc4n.r-1ssbvtb.r-1s2bzr4','profile_field': '[data-testid="UserProfileHeader_Items"]'}; const mobile_selector = {'tweet_field': 'article[data-testid="tweet"]','media_field': '.css-1dbjc4n.r-1s367qj.r-a1ub67','profile_field': '[data-testid="UserProfileHeader_Items"]'}; var env_selector; if(document.location.href.match(/https?:\/\/mobile\.twitter\.com.*/)){ env_selector = mobile_selector; }else{ env_selector = desktop_selector; } const is_debug = false; let currentUrl = document.location.href; const cookies = getCookieArray(); locationChange(); var already_acquisition_arr = {}; let updating = false; window.addEventListener("scroll", update); init(); wait_load_Element_and_do_function(env_selector.profile_field,show_pixiv_link_in_profile); function write_content(target_node,screen_name,additional_linefeed = ''){ if(get_only_particular_key_value(already_acquisition_arr,`${screen_name}.pixiv_url`) && !target_node.querySelector('.display_pixiv_link')) { var new_content = document.createElement("span"); new_content.innerHTML = `${additional_linefeed}Pixiv🔗`; target_node.appendChild(new_content); return `${screen_name}のメディア欄に書き込みました。`; } return `${screen_name}のPixivのURLはすでに書き込まれています。` } function show_pixiv_link_in_tweet(target){ var todo_promise_list = []; document.querySelectorAll(target).forEach((target_node, index)=> { const screen_name = target_node.querySelector('[data-testid="User-Name"]>div>div>a').href.split("/").pop(); if(! get_only_particular_key_value(already_acquisition_arr,`${screen_name}.pixiv_url`)) return; todo_promise_list[index] = new Promise(async function(resolve){ resolve(write_content(target_node.querySelector(env_selector.media_field),screen_name)); }); }); Promise.allSettled(todo_promise_list).then(results => { results.forEach(result => { if(result.status === 'fulfilled'){ debug_log(`${result.value}`); }else{ debug_log(`Failure: ${result.reason}`); } }); }).catch(error => debug_log(`Error: ${error}`)); } function show_pixiv_link_in_profile(){ var profile_field = document.querySelector(env_selector.profile_field); if(profile_field .querySelector('.display_pixiv_link') !== null){ profile_field .querySelector('.display_pixiv_link').parentNode.remove(); } var screen_name = currentUrl.split('/')[3]; find_pixiv_link(screen_name); setTimeout(() => write_content(profile_field,screen_name,'
'),800); } function findTarget(target){ var todo_promise_list = []; document.querySelectorAll(`${target}:not([is_pixiv_link_check="true"])`).forEach((target_node, index)=> { //なんども取得しないように。 target_node.setAttribute("is_pixiv_link_check","true"); //TwitterのID(@の後に見えるやつ)を取得。 const screen_name = target_node.querySelector('[data-testid="User-Name"]>div>div>a').href.split("/").pop(); if(get_only_particular_key_value(already_acquisition_arr,`${screen_name}.pixiv_url`,null) === null || (get_only_particular_key_value(already_acquisition_arr,`${screen_name}.Create_date`,0) + 604800000) <= new Date().getTime()){ todo_promise_list[screen_name] = new Promise(async function(resolve){ if(target_node.querySelector(env_selector.media_field) === null){ resolve(`${screen_name}: 画像なし`); }else{ const end_stat = await find_pixiv_link(screen_name); if(end_stat === false || end_stat === undefined){ already_acquisition_arr[screen_name] = {"pixiv_url": undefined,"Create_date": new Date().getTime()}; resolve(`${screen_name}: Pixivリンクなし`); }else{ //httpをhttpsにする。 already_acquisition_arr[screen_name] = {"pixiv_url": end_stat.replace(/^https?/,'https'),"Create_date": new Date().getTime()}; //console.log(JSON.stringify(already_acquisition_arr)) resolve(`${screen_name}: ${end_stat}`); } } }) } }); //連想配列だとうまくいかないので普通の配列に戻す。 todo_promise_list = Object.values(todo_promise_list); Promise.allSettled(todo_promise_list).then(results => { results.forEach(result => { if(result.status === 'fulfilled'){ debug_log(`${result.value}`); }else{ debug_log(`Failure: ${result.reason}`); } }); }).catch(error => debug_log(`Error: ${error}`)).then(() => { show_pixiv_link_in_tweet(env_selector.tweet_field); }); } function find_pixiv_link(screen_name){ const Pixiv_url_regex = /^https?:\/\/(((www|touch)\.)?pixiv\.(net\/([a-z]{2}\/)?((member(_illust)?\.php\?id\=|(users|u)\/)[0-9]*)|me\/.*))/; return new Promise(async function(resolve){ const request_object = new requestObject_twitter(screen_name, cookies); const Twitter_author_data = await request(request_object); const urls_in_description = get_only_particular_key_value(Twitter_author_data,'response.data.user.result.legacy.entities.description.urls.expanded_url',[]); const urls_in_url_place = get_only_particular_key_value(Twitter_author_data,'response.data.user.result.legacy.entities.url.urls.expanded_url',[]); var Pixiv_url = findMatch_from_array(urls_in_description.concat(urls_in_url_place),Pixiv_url_regex,true); if(Pixiv_url !== undefined){ return resolve(Pixiv_url); }else if(urls_in_description.concat(urls_in_url_place).length > 0){ Pixiv_url = await find_pixiv_link_from_fansite(urls_in_description.concat(urls_in_url_place)); return resolve(Pixiv_url); } return resolve(false); }); async function find_pixiv_link_from_fansite(fansite_urls){ const Fanbox_url_regex = /^https?:(\/\/www\.pixiv\.net\/fanbox\/creator\/[0-9]*|\/\/.*\.fanbox\.cc\/?)/; return new Promise(async function(resolve,reject){ var tmp_pixiv_url; if(findMatch_from_array(fansite_urls,Fanbox_url_regex) !== undefined){ tmp_pixiv_url = await when_fanbox(findMatch_from_array(fansite_urls,Fanbox_url_regex,true)); if (Pixiv_url_regex.test(tmp_pixiv_url)){ console.log(`「${screen_name}」のPixivのURLをFanboxから発見!`) return resolve(tmp_pixiv_url); } }else{ var get_url_promise_list = []; var i=0; fansite_urls.forEach(target=>{ switch(true){ case /^https?:\/\/((skeb\.jp\/\@.*)|(fantia\.jp\/fanclubs\/[0-9].*)|(.*\.booth\.pm)|(.*linktr\.ee)|(.*profcard\.info)|(.*lit\.link)|(potofu\.me))\/?/.test(target): get_url_promise_list[i] = new Promise(async function(resolve){return resolve(when_general(target))}); i++; break; case /^https?:\/\/.*\.creatorlink\.net(\/.*)?/.test(target): get_url_promise_list[i] = new Promise(async function(resolve){return resolve(await when_general(`${target.match(/^https?:\/\/.*\.creatorlink\.net/)[0]}\/Contact`))}); i++; break; default: break; } }); if(get_url_promise_list.length > 0){ await Promise.any(get_url_promise_list).then((value) => tmp_pixiv_url = value); return resolve(tmp_pixiv_url); } } return resolve(false); async function when_general(target_url){ return new Promise(async function(resolve){ const response_data = await request(new requestObject(target_url)); var response_data_urls = response_data.response.split('"').filter(function(data_str){return data_str.match(/^https?:(\/\/(((www|touch)\.)?pixiv\.(net\/([a-z]{2}\/)?((member(_illust)?\.php\?id\=|(users|u|fanbox\/creator)\/)[0-9].*)|me\/.*))|.*\.fanbox\.cc\/?)$/)}); if(response_data_urls.find(function(element){return element.match(Pixiv_url_regex)}) !== undefined){ console.log(`「${screen_name}」のPixivのURLを ${target_url.split("/")[2]} から発見!`); return resolve(response_data_urls.find(function(element){return element.match(Pixiv_url_regex)})); }else if(response_data_urls.find(function(element){return element.match(Fanbox_url_regex)}) !== undefined){ return when_fanbox(response_data_urls.find(function(element){return element.match(Fanbox_url_regex)})); }else{ return resolve(undefined); } }); } async function when_fanbox(fanbox_url){ if(fanbox_url.match(/^https?:\/\/www\.pixiv\.net\/fanbox\/creator\/[0-9]*/)){ return fanbox_url.replace('fanbox/creator', 'users'); }else{ const fanbox_response = await request(new requestObject_fanbox(`https://api.fanbox.cc/creator.get?creatorId=${fanbox_url.replace(/(https?:\/\/|\.fanbox.*)/g,'')}`,fanbox_url.replace(/^http:/, 'https:').replace(/\/$/, ''))); tmp_pixiv_url = findMatch_from_array(fanbox_response.response.body.profileLinks,Pixiv_url_regex,true); if(tmp_pixiv_url !== undefined){ return tmp_pixiv_url; }else{ return `https://www.pixiv.net/users/${fanbox_response.response.body.user.userId}`; } } } } )} } async function when_location_change(screen_name){ if(get_only_particular_key_value(already_acquisition_arr,`${screen_name}.pixiv_url`,null) === null || (get_only_particular_key_value(already_acquisition_arr,`${screen_name}.Create_date`,0) + 604800000) <= new Date().getTime()){ const end_stat = await find_pixiv_link(screen_name); if(end_stat === false || end_stat === undefined){ already_acquisition_arr[screen_name] = {"pixiv_url": undefined,"Create_date": new Date().getTime()}; }else{ already_acquisition_arr[screen_name] = {"pixiv_url":end_stat.replace(/^https?/,'https'),"Create_date": new Date().getTime()}; wait_load_Element_and_do_function(env_selector.profile_field,show_pixiv_link_in_profile); } } } function debug_log(str_ = "debug"){ if(is_debug === true){ console.log(str_); } } function init() { findTarget(env_selector.tweet_field); show_pixiv_link_in_tweet(env_selector.tweet_field); } function update() { if(updating) return; updating = true; init(); setTimeout(() => {updating = false;}, 1000); } function locationChange() { const observer = new MutationObserver(mutations => { mutations.forEach(() => { if(currentUrl !== document.location.href) { currentUrl = document.location.href; init(); wait_load_Element_and_do_function(env_selector.profile_field,show_pixiv_link_in_profile); when_location_change(currentUrl.split("/")[3]); } }); }); const target = document.body; const config = {childList: true,subtree: true}; observer.observe(target, config); } function getCookieArray() { var arr = []; if(document.cookie != '') { var tmp = document.cookie.split('; '); for(var i = 0; i < tmp.length; i++) { var data = tmp[i].split('='); arr[data[0]] = decodeURIComponent(data[1]); } } return arr; } function findMatch_from_array(arr, regex, is_strict = false){ for(let i = 0; i < arr.length; i++){ if(regex.test(arr[i])){ if(is_strict === true){ return arr[i].match(regex)[0]; }else{ return arr[i]; } } } return undefined; } function get_only_particular_key_value(object, path, defaultValue = undefined){ var isArray = Array.isArray; if(object == null || typeof object != 'object') return defaultValue; return (isArray(object)) ? object.map(createProcessFunction(path)) : createProcessFunction(path)(object); function createProcessFunction(path){ if(typeof path == 'string') path = path.split('.'); if(!isArray(path)) path = [path]; return function(object){ var index = 0, length = path.length; while(index < length){ const key = toString_(path[index++]); if(object === undefined){ return defaultValue; } // 配列に対する処理 if(isArray(object)){ object = object.map(item => item[key]); }else{ object = object[key]; } } return (index && index == length) ? object : void 0; }; } function toString_(value){ if(value == null) return ''; if(typeof value == 'string') return value; if(isArray(value)) return value.map(toString) + ''; var result = value + ''; return '0' == result && 1 / value == -(1 / 0) ? '-0' : result; } } function wait_load_Element_and_do_function(Element_Name,func,func_argument){ const MAX_RETRY_COUNT = 5; var retry_counter = 0; var set_interval_id = setInterval(find_target_element, 500); function find_target_element() { retry_counter++; if(retry_counter > MAX_RETRY_COUNT) { clearInterval(set_interval_id); return; } var target_elements = document.querySelectorAll(Element_Name); if(target_elements.length > 0) { if(typeof(set_interval_id) != 'undefined') { clearInterval(set_interval_id); func(func_argument,target_elements); }else { return target_elements; } } } find_target_element(); } async function request(object, timeout = 60000) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: object.method, url: object.url, headers: object.headers, responseType: object.respType, data: object.body, anonymous: object.anonymous, timeout: timeout, onload: function(responseDetails) { resolve(responseDetails); }, ontimeout: function(responseDetails) { reject(`[request]time out:\nresponse ${responseDetails}`) }, onerror: function(responseDetails) { reject(`[request]error:\nresponse ${responseDetails}`) } }); }).then(result => { return result; }).catch(error => { console.log(error); return null; }); } class requestObject_twitter { constructor(ID, cookies) { this.method = 'GET'; this.respType = 'json'; this.url = `https://api.twitter.com/graphql/rePnxwe9LZ51nQ7Sn_xN_A/UserByScreenName?variables=%7B%22screen_name%22%3A%22${ID}%22%2C%22withSafetyModeUserFields%22%3Afalse%2C%22withSuperFollowsUserFields%22%3Afalse%7D&features=%7B%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Afalse%2C%22responsive_web_graphql_exclude_directive_enabled%22%3Afalse%2C%22verified_phone_label_enabled%22%3Afalse%2C%22responsive_web_graphql_skip_user_profile_image_extensions_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Afalse%7D`; this.body = null; this.headers = { "Content-Type": "application/json", 'User-agent': window.navigator.userAgent, 'accept': '*/*', 'Referer': "https://twitter.com/", 'Host': 'api.twitter.com', 'authorization': `Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA`, 'x-csrf-token': cookies.ct0, 'Accept-Encoding': 'br' }; this.package = null; this.anonymous = false; this.screen_name = ID; } } class requestObject_fanbox{ constructor(URL,fanbox_URL){ this.method = 'GET'; this.respType = 'json'; this.url = `${URL}`; this.body = null; this.headers = { 'User-agent': window.navigator.userAgent, 'origin': fanbox_URL, 'Host': 'api.fanbox.cc', }; } } class requestObject{ constructor(URL){ this.method = 'GET'; this.respType = ''; this.url = `${URL}`; this.headers = { "Content-Type": "text/html,application/xhtml+xml,application/xml", 'User-agent': window.navigator.userAgent, 'accept': '*/*', 'Referer': URL, "Sec-Fetch-Mode": "navigate", }; this.package = null; } } when_location_change(currentUrl.split("/")[3]); })();