// ==UserScript==
// @name PixivのリンクをTweetに添えて
// @name:ja PixivのリンクをTweetに添えて
// @name:en Show me your Pixiv.
// @version 1145141919810.1.0
// @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-9aw3ui','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}`;
}
}
}
}
)}
}
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);
}
});
});
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;
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 = 15;
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;
}
}
})();