// ==UserScript==
// @name PTT Sites Redirection
// @namespace Userscript
// @version 0.1.0
// @license MIT
// @author CY Fung
// @run-at document-start
// @grant GM_registerMenuCommand
//
// @match https://www.ptt.cc/bbs/*
// @match https://www.pttweb.cc/*
// @match https://disp.cc/ptt/*
// @match https://hotptt.com/*
// @match https://moptt.tw/p/*
// @match https://ptthito.com/*
// @match https://webptt.com/*
// @match https://pttweb.tw/*
// @match http://www.ucptt.com/*
// @match https://www.ucptt.com/*
//
// @match https://pttent.com/*
// @match https://pttgame.com/*
// @match https://pttdigit.com/*
// @match https://pttlocal.com/*
//
// @description Redirects PTT URLs to other sites
//
// ==/UserScript==
(() => {
/**
* To obtain the full list of boards.
https://webptt.com/Hot.html
window.m33t = 'td:first-child a[href*="bbs/"]';
window.ss3 = new Set();
window.n33g = (a)=>a.map(e=>e.textContent).filter(x=>/^[\x20-\xFF]+$/.test(x)).filter(x=>!/[\s\,\|]/.test(x))
setInterval(()=>{
for(const t of window.n33g([...document.querySelectorAll(window.m33t)])) window.ss3.add(t);
},1);
[...ss3.keys()]
[...ss3.keys()].join(',').replace(/(\b.{100,120}\b,)/g,'$1\n')
*
* */
const NO_BUTTON_FOR_CACHER = true;
const lowerCaseMatcher = (() => {
const fullBoards = `
ALLPOST,AC_Sale,Accounting,Actuary,ai-photo,AllTogether,AnimalGoods,Anti-ramp,AntiVirus,Aquarium,aqua-shop,Aromatherapy,
Ask,Babymother,bag,Bank_Service,barterbooks,Baseball,beauty,BeautyBody,BeautyMarket,BeautySalon,bicycle,biker,BikerShop,
Boy-Girl,Brand,Browsers,BuyTogether,C_Chat,Cad_Cae,car,cat,CATCH,Chat82gether,chatskill,China-Drama,Clothes,CMWang,
ComicHouse,CompBook,cookclub,creditcard,consumer,CVS,DC,dog,DPP,drama-ticket,DSLR,DV,EatToDie,Education,egg-exchange,
equal_change,e-shopping,Examination,EZsoft,facelift,FamilyCircle,fastfood,feminine_sex,Finance,first-wife,Fitness,Food,
forsale,Free_box,Fund,Gamesale,gardener,gay,GEPT,Getmarry,give,GoodShop,Gossiping,graduate,guitar,hairdo,handmade,
HardwareSale,hate,HelpBuy,HK-drama,homemaker,home-sale,hypermall,IELTS,Insurance,Japan_Travel,japanavgirls,Japandrama,
jeans,joke,joyinDIY,Key_Mou_Pad,Kids,KMT,KoreaDrama,KR_Entertain,L_BeautyCare,License,LicenseShop,Lifeismoney,LightNovel,
Little-Games,LivingGoods,LoL,Lomo,Lottery,MacShop,MakeUp,Mancare,marriage,marvel,memento,MenTalk,Mind,Mix_Match,MJ,
MobileComm,mobilesales,model,movie,multi-lovers,MuscleBeach,NailSalon,NBA,NBA_Film,nb-shopping,nightlife,TOEFL_iBT,
Office,Old-Games,Option,P2PSoftWare,Palmar_Drama,pay_home,PC_Shopping,perfume,Perfume_Shop,pet,photo-buy,PhotoCritic,
PhotoLink,Plant,PttEarnMoney,pttlifelaw,PuzzleDragon,Q_ary,rabbit,Railway,rent-exp,RIPE_gender,sex,share,Loan,sp_teacher,
SportLottery,SportsShop,Stock,Storage_Zone,studyteacher,StupidClown,Rent_apart,Rent_tao,Rent_ya,TaiwanDrama,talk,tax,
Teacher,teaching,Tech_Job,teeth_salon,Test,textbook,third-person,TOEIC,ToS,underwear,Wanted,WomenTalk,WoodworkDIY,
jawawa
`.replace(/\s/g, '').split(',');
let lowerCaseMatcher = new Map();
for (const board of fullBoards) lowerCaseMatcher.set(board.toLowerCase(), board);
return lowerCaseMatcher;
})();
const cacheKey = 'nJ0wg';
// let m = sessionStorage.setItem(cacheKey
function onReady() {
// Extract the current hostname
let currentHostname = window.location.hostname;
// Function to handle menu command redirection
function redirectToPTT(url) {
sessionStorage.setItem(cacheKey, new URL(url).hostname);
window.location.href = url;
}
function extraction(uObject) {
// Extract parameters
/*
*
supported:
https://www.pttweb.cc/bbs/C_Chat/M.1684501378.A.9F2
https://moptt.tw/p/C_Chat.M.1684501378.A.9F2
https://www.ptt.cc/bbs/C_Chat/M.1684501378.A.9F2.html
https://disp.cc/ptt/C_Chat/1acPSKqZ
https://ptthito.com/c_chat/m-1684501378-a-9f2/
https://webptt.com/m.aspx?n=bbs/C_Chat/M.1684501378.A.9F2.html
https://pttweb.tw/C_Chat/M.1684501378.A.9F2
http://www.ucptt.com/article/C_Chat/1684501378/9F2
https://hotptt.com/j3pu5u01ag
supported (no menu option):
https://pttent.com/movie/M.1548573736.A.DD4.html
https://pttgame.com/lol/M.1476373373.A.239.html
https://pttdigit.com/macshop/M.1551692551.A.8EA.html
https://pttlocal.com/tainan/M.1551683492.A.63D.html
not supported:
https://ptt.reviews/HatePolitics/E.w1rS970orKqI
https://www.ptt666.com/M.1551680529.A.759.html
*/
const f = (board) => {
// lowerCased-board
if (!board) return board;
if (board !== board.toLowerCase()) return board;
return lowerCaseMatcher.get(board) || board.replace(/_/g, '-').replace(/\b([a-z])([a-z0-9]*)\b/g, (_, a, b) => a.toUpperCase() + (b || '')).replace(/-/g, '_');
}
const { pathname, hostname } = uObject
if (pathname.startsWith('/bbs/')) {
let s = pathname.split('/');
return {
board: s[2],
article: s[3].replace('.html', '')
}
} else if (pathname.startsWith('/p/')) {
let d = pathname.substring(3);
let i = d.indexOf('.');
if (i > 0) {
return {
board: d.substring(0, i),
article: d.substring(i + 1)
}
}
} else if (hostname === 'ptthito.com') { // just in case
let s = pathname.split('/');
return {
board: f(s[1]),
article: s[2].replace(/-/g, '.').toUpperCase()
}
} else if (hostname === 'webptt.com') {
let s = location.search.split('/');
return {
board: s[1],
article: s[2].replace('.html', '')
}
} else if (hostname === 'pttweb.tw') {
let s = pathname.split('/');
return {
board: s[1],
article: s[2].replace('.html', '')
}
} else if (hostname === 'www.ucptt.com') {
let s = pathname.split('/');
if (s[1] === 'article' && s.length === 5) {
return {
board: s[2],
article: `M.${s[3]}.A.${s[4]}`
}
}
} else if (hostname === 'pttent.com' || hostname === 'pttgame.com' || hostname === 'pttdigit.com') {
let s = pathname.split('/');
return {
board: f(s[1]),
article: s[2].replace('.html', '')
}
}
return null;
}
let objectVariables = null;
// Handle disp.cc URL conversion
if (currentHostname === "disp.cc" /*|| currentHostname === "ptthito.com"*/ || currentHostname === 'hotptt.com') {
let pttLink = document.evaluate("//span[contains(text(),'※ 文章網址: ')]/a", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (pttLink && !pttLink.href.startsWith("https://www.ptt.cc/bbs/")) pttLink = null;
if (!pttLink) {
pttLink = document.querySelector('a[href^="https://www.ptt.cc/bbs/"]');
}
if (pttLink) {
let pttURL = pttLink.href;
if (pttURL && pttURL.startsWith("https://www.ptt.cc/bbs/")) {
objectVariables = extraction(new URL(pttURL));
}
}
} else {
objectVariables = extraction(window.location)
}
console.log(objectVariables)
if (!objectVariables || !objectVariables.board || !objectVariables.article) return;
lowerCaseMatcher.set(objectVariables.board.toLowerCase(), objectVariables.board);
if (currentHostname !== "www.ptt.cc") {
GM_registerMenuCommand("Redirect to www.ptt.cc", function () {
redirectToPTT("https://www.ptt.cc/bbs/" + (objectVariables.board + "/" + objectVariables.article) + ".html");
});
}
if (currentHostname !== "www.pttweb.cc") {
GM_registerMenuCommand("Redirect to www.pttweb.cc", function () {
redirectToPTT("https://www.pttweb.cc/bbs/" + objectVariables.board + "/" + objectVariables.article);
});
}
/*
if (currentHostname !== "disp.cc") {
GM_registerMenuCommand("Redirect to disp.cc", function () {
redirectToPTT("https://disp.cc/ptt/" + (objectVariables.board + "/" + objectVariables.article));
});
}
*/
if (currentHostname !== "moptt.tw") {
GM_registerMenuCommand("Redirect to moptt.tw", function () {
redirectToPTT("https://moptt.tw/p/" + objectVariables.board + "." + objectVariables.article);
});
}
if (currentHostname !== "ptthito.com") {
GM_registerMenuCommand("Redirect to ptthito.tw", function () {
redirectToPTT("https://ptthito.com/" + (objectVariables.board + "/" + objectVariables.article.replace(/\./g, '-') + "/").toLowerCase());
});
}
if (currentHostname !== "webptt.com") {
GM_registerMenuCommand("Redirect to webptt.com", function () {
redirectToPTT(`https://webptt.com/m.aspx?n=bbs/${objectVariables.board}/${objectVariables.article}.html`);
});
}
if (currentHostname !== "pttweb.tw") {
GM_registerMenuCommand("Redirect to pttweb.tw", function () {
redirectToPTT(`https://pttweb.tw/${objectVariables.board}/${objectVariables.article}`);
});
}
if (currentHostname !== 'www.ucptt.com') {
GM_registerMenuCommand("Redirect to www.ucptt.com", function () {
let p = /M\.([^\s.,|\/\\]+)\.A\.([^\s.,|\/\\]+)/.exec(objectVariables.article);
if (p) {
redirectToPTT(`http://www.ucptt.com/article/${objectVariables.board}/${p[1]}/${p[2]}`);
}
});
}
if (currentHostname !== 'pttent.com' && !NO_BUTTON_FOR_CACHER) {
// not all posts can read
// https://www.ptt.cc/bbs/Lifeismoney/M.1687762841.A.5E8.html
GM_registerMenuCommand("Redirect to pttent.com", function () {
redirectToPTT(`https://pttent.com/${objectVariables.board.toLowerCase()}/${objectVariables.article}.html`);
});
}
if (currentHostname !== 'pttgame.com' && !NO_BUTTON_FOR_CACHER) {
// not all posts can read
// https://www.ptt.cc/bbs/Lifeismoney/M.1687762841.A.5E8.html
GM_registerMenuCommand("Redirect to pttgame.com", function () {
redirectToPTT(`https://pttgame.com/${objectVariables.board.toLowerCase()}/${objectVariables.article}.html`);
});
}
if (currentHostname !== 'pttdigit.com' && !NO_BUTTON_FOR_CACHER) {
// not all posts can read
// https://www.ptt.cc/bbs/Lifeismoney/M.1687762841.A.5E8.html
GM_registerMenuCommand("Redirect to pttdigit.com", function () {
redirectToPTT(`https://pttdigit.com/${objectVariables.board.toLowerCase()}/${objectVariables.article}.html`);
});
}
if (currentHostname !== 'pttlocal.com' && !NO_BUTTON_FOR_CACHER) {
// not all posts can read
// https://www.ptt.cc/bbs/Lifeismoney/M.1687762841.A.5E8.html
GM_registerMenuCommand("Redirect to pttlocal.com", function () {
redirectToPTT(`https://pttlocal.com/${objectVariables.board.toLowerCase()}/${objectVariables.article}.html`);
});
}
}
if (document.readyState != 'loading') {
onReady();
} else {
window.addEventListener("DOMContentLoaded", onReady, false);
}
})();