// ==UserScript==
// @name OFans.party IPFS Gateway Switcher
// @namespace Violentmonkey Scripts
// @description IPFS gateway switcher for ofans.party.
// @match https://ofans.party/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant unsafeWindow
// @run-at document-start
// @version 0.6
// @author sudorain
// @noframes
// ==/UserScript==
// A (somewhat working) polyfill for beforescriptexecute event
// https://github.com/jspenguin2017/Snippets/blob/master/onbeforescriptexecute.html
(() => {
"use strict";
const Event = class {
constructor(script, target) {
this.script = script;
this.target = target;
this._cancel = false;
this._replace = null;
this._stop = false;
}
preventDefault() {
this._cancel = true;
}
stopPropagation() {
this._stop = true;
}
replacePayload(payload) {
this._replace = payload;
}
};
let callbacks = [];
window.addBeforeScriptExecuteListener = (f) => {
if (typeof f !== "function") {
throw new Error("Event handler must be a function.");
}
callbacks.push(f);
};
window.removeBeforeScriptExecuteListener = (f) => {
let i = callbacks.length;
while (i--) {
if (callbacks[i] === f) {
callbacks.splice(i, 1);
}
}
};
const dispatch = (script, target) => {
if (script.tagName !== "SCRIPT") {
return;
}
const e = new Event(script, target);
if (typeof window.onbeforescriptexecute === "function") {
try {
window.onbeforescriptexecute(e);
} catch (err) {
console.error(err);
}
}
for (const func of callbacks) {
if (e._stop) {
break;
}
try {
func(e);
} catch (err) {
console.error(err);
}
}
if (e._cancel) {
script.textContent = "";
script.remove();
} else if (typeof e._replace === "string") {
script.textContent = e._replace;
}
};
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const n of m.addedNodes) {
dispatch(n, m.target);
}
}
});
observer.observe(document, {
childList: true,
subtree: true,
});
})();
(async () => {
"use strict";
var loaded, regex_main_js, regex_creator, api_posts, gateways_json, public_gateways, current_gateway, gallery_mode, creator, posts_find, posts_replace;
loaded = false;
regex_main_js = new RegExp(/main\.[a-z0-9]+\.chunk\.js/);
regex_creator = new RegExp(/^https:\/\/ofans.party\/#\/creator\//);
// A site displaying public IPFS gateways and their online/offline status.
// https://ipfs.github.io/public-gateway-checker/
gateways_json = "https://ipfs.github.io/public-gateway-checker/gateways.json";
api_posts = "https://api.ofans.party/posts/";
public_gateways = await GM_getValue("public_gateways");
current_gateway = await GM_getValue("current_gateway");
gallery_mode = await GM_getValue("gallery_mode") || false;
posts_find = `Object(a.jsxs)("div",{className:"container",children:[o,this.state.posts&&this.state.posts.map((function(t,s){return r++,Object(a.jsxs)("div",{className:"row post",of_id:t.post_id,children:[r%15==0&&e.renderAd(),Object(a.jsxs)("div",{className:"col-lg-6 postText",children:[e.renderPostDate(t),Object(a.jsx)("br",{}),e.renderPostText(t)]}),Object(a.jsx)("div",{className:"col-lg-6",style:{textAlign:"center"},children:t.media&&t.media.map((function(t,s){return t.ipfs_media_hash?Object(a.jsx)("a",{href:e.state.ipfsHost+t.ipfs_media_hash,children:Object(a.jsx)("img",{src:e.state.ipfsHost+t.ipfs_thumb_hash,loading:"lazy",className:"mediaThumb"})},t.id.toString()):Object(a.jsx)("div",{style:{backgroundColor:"grey",height:"144px",width:"144px",margin:"0.5em",position:"relative"},title:"Importing...",children:Object(a.jsxs)("span",{style:{color:"white",fontSize:"2em",fontWeight:"bold",position:"absolute",top:"50%",left:"50%",margin:"-25px 0 0 -25px",height:"50px",width:"50px"},children:[" ",Object(a.jsx)(c.a,{icon:["fa","download"]})," "]})})}))})]},t.id.toString())}))]})`
posts_replace = `Object(a.jsxs)("div",{className:"container",children:[o,Object(a.jsxs)("div",{className:"row row-cols-5 align-items-center no-gutters bg-light",children:[this.state.posts&&this.state.posts.map((function(t,s){return r++,t.media&&t.media.map((function(t,s){return t.ipfs_media_hash?Object(a.jsx)("div",{className:"col",children:Object(a.jsx)("div",{className:"d-flex justify-content-center m-1",children:Object(a.jsx)("a",{href:e.state.ipfsHost+t.ipfs_media_hash,target:"_blank",children:Object(a.jsx)("img",{src:e.state.ipfsHost+t.ipfs_thumb_hash,loading:"lazy",className:"img-fluid"})},t.id.toString())})}):Object(a.jsx)("div",{className:"col",children:Object(a.jsx)("div",{className:"d-flex justify-content-center m-1",style:{backgroundColor:"grey",height:"150px",position:"relative"},title:"Importing...",children:Object(a.jsxs)("span",{style:{color:"white",fontSize:"2em",fontWeight:"bold",position:"absolute",top:"50%",left:"50%",margin:"-25px 0 0 -25px",height:"50px",width:"50px"},children:[" ",Object(a.jsx)(c.a,{icon:["fa","download"]})," "]})})})}))}))]})]})`
await GM_xmlhttpRequest({
method: "GET",
url: gateways_json,
onload: function (response) {
public_gateways = response.responseText.replace(/:hash/g, "");
GM_setValue("public_gateways", public_gateways)
}
});
window.onbeforescriptexecute = (e) => {
const script = e.script.outerHTML;
if (regex_main_js.test(script) && !loaded && (current_gateway || gallery_mode)) {
const source = e.script.attributes.src.value;
e.preventDefault();
e.stopPropagation();
GM_xmlhttpRequest({
method: "GET",
url: source,
onload: async function (response) {
loaded = !loaded
var text = response.responseText
if (current_gateway) {
text = text.replace(/ipfsHost:"(.*?)"/g, `ipfsHost:"${current_gateway}"`)
}
if (gallery_mode) {
text = text.replace(posts_find, posts_replace);
}
let newScript = createElement('script', text, { type: "text/javascript", id: "main" });
document.head.append(newScript);
}
});
}
}
document.addEventListener('DOMContentLoaded', function () {
let css = `
.custom-scrollbar::-webkit-scrollbar {
width: 5px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #dee2e6;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #888;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
`;
let style = createElement('style', css);
document.head.append(style);
const gateways = JSON.parse(public_gateways)
let wrapper = createElement('div', false, { class: "position-fixed fixed-bottom float-left text-monospace mb-3 ml-3", style: "width: fit-content;" });
let dropdown_group = createElement('div', false, { id: "switch", class: "btn-group dropup" });
let button = createElement('button', "Gateways", { class: "btn btn-dark dropdown-toggle", "data-toggle": "dropdown", "aria-haspopup": "true", "aria-expanded": "false" });
let badge = createElement('span', gateways.length, { class: "badge badge-light ml-1" });
button.append(badge)
dropdown_group.append(button)
let dropdown_menu = createElement('div', false, { class: "dropdown-menu overflow-auto custom-scrollbar shadow-lg px-0 pt-0 pb-2 mb-3", style: "max-height: 388px" });
for (const [index, url] of gateways.entries()) {
let current = current_gateway == url;
let fancy_url = url.match(/(^https?:\/\/)(.*)/);
let url_protocol = createElement('small', fancy_url[1], { class: `${current ? ' ' : ' text-black-50'}` });
let menu_item = createElement('button', fancy_url[2], { "data-value": url, class: `dropdown-item border-bottom button-switch ${current ? ' active' : ' text-dark'}`, type: "button" });
menu_item.prepend(url_protocol)
if (current) {
dropdown_menu.prepend(menu_item)
} else {
dropdown_menu.append(menu_item)
}
}
let dafault_gateway = createElement('button', 'Default', { "data-value": '', class: `dropdown-item border-bottom border-top button-switch ${!current_gateway ? ' active' : ''}`, type: "button" });
dropdown_menu.prepend(dafault_gateway)
let menu_header = createElement('h6', "Gateway Switcher", { class: "bg-light text-dark shadow-sm sticky-top dropdown-header border-bottom py-3 mb-2" });
let menu_checker = createElement('a', "Public Gateway Checker ", { class: "float-right text-success", href: "https://ipfs.github.io/public-gateway-checker/", target: "_blank" });
menu_header.append(menu_checker)
dropdown_menu.prepend(menu_header)
dropdown_group.append(dropdown_menu)
wrapper.append(dropdown_group)
let button_gallery = createElement('button', `Gallery Mode`, { class: `btn button-gallery ml-2 ${gallery_mode ? ' btn-info' : ' btn-dark'}` });
let gallery_status = createElement('span', `${gallery_mode ? 'On' : 'Off'}`, { class: "badge badge-light ml-1" });
button_gallery.append(gallery_status)
wrapper.append(button_gallery)
document.body.append(wrapper)
console.log(creator)
});
document.addEventListener("click", switchGateway);
function switchGateway(e) {
const class_list = e.target.classList;
const data_set = e.target.dataset;
if (class_list.contains('button-switch')) {
const value = data_set.value;
if (value) {
GM_setValue("current_gateway", value)
} else {
GM_deleteValue("current_gateway")
}
location.reload();
} else if (class_list.contains('button-gallery')) {
GM_setValue("gallery_mode", !gallery_mode)
location.reload();
}
}
/*
async function createGallery() {
const loc = window.location.href
if (regex_creator.test(loc)) {
const id = loc.substring(loc.lastIndexOf('/') + 1)
await GM_xmlhttpRequest({
method: "GET",
url: api_posts + id,
onload: function (response) {
let json = JSON.parse(response.responseText)
creator = json.response
let app = document.querySelectorAll('#root')[0];
let container = document.querySelectorAll('.container')[0];
let posts_container = createElement('div', false, { class: 'container' });
let posts_row = createElement('div', false, { class: 'row row-cols-5 align-items-center no-gutters bg-light' });
for (const [index, post] of creator.posts.entries()) {
for (const media of post.media) {
if (media.ipfs_media_hash) {
let col = createElement('div', false, { class: 'col' });
let item = createElement('div', false, { class: 'd-flex justify-content-center m-1' });
let link = createElement('a', false, { href: current_gateway + media.ipfs_media_hash });
let image = createElement('img', false, { src: current_gateway + media.ipfs_thumb_hash, loading: "lazy", class: 'img-fluid' });
link.append(image)
item.append(link)
col.append(item)
posts_row.append(col)
}
}
}
posts_container.append(posts_row)
container.innerHTML = posts_row.outerHTML
gallery_mode = true
}
});
}
}
*/
function createElement(el, text, attrs) {
let element = document.createElement(el);
if (text) element.textContent = text;
if (attrs) Object.keys(attrs).forEach(key => element.setAttribute(key, attrs[key]));
return element;
}
})();