Update mods

This commit is contained in:
Your New SJW Waifu 2022-05-27 09:48:57 -05:00
parent 8e0ca4b8a9
commit 98dc169e46
2 changed files with 625 additions and 437 deletions

View File

@ -1,14 +1,17 @@
function ColorDetector (image) { /* global PleromaMod */
this.color = { r: 0, g: 0, b: 0 };
this.image = image;
}
[
function componentToHex (c) {
var hex = Math.max(Math.min(c, 255), 0).toString(16);
return hex.length === 1 ? "0" + hex : hex;
},
function getHexColor (offset) { class ColorDetector {
constructor (image) {
this.color = { r: 0, g: 0, b: 0 };
this.image = image;
}
componentToHex (c) {
const hex = Math.max(Math.min(c, 255), 0).toString(16);
return hex.length === 1 ? "0" + hex : hex;
}
getHexColor (offset) {
if (!offset) { if (!offset) {
offset = { r: 0, g: 0, b: 0 }; offset = { r: 0, g: 0, b: 0 };
} }
@ -16,40 +19,39 @@ function ColorDetector (image) {
this.componentToHex(this.color.r + offset.r) + this.componentToHex(this.color.r + offset.r) +
this.componentToHex(this.color.g + offset.g) + this.componentToHex(this.color.g + offset.g) +
this.componentToHex(this.color.b + offset.b); this.componentToHex(this.color.b + offset.b);
}, }
function detect () { detect () {
return new Promise((resolve) => { return new Promise((resolve) => {
let blockSize = 5; const blockSize = 5;
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const context = canvas.getContext && canvas.getContext("2d"); const context = canvas.getContext && canvas.getContext("2d");
let data; let width; let height;
let i = -4;
let rgb = { r: 0, g: 0, b: 0 };
let length;
let count = 0;
if (!context) { if (!context) {
console.warn("can't get context of avatar"); console.warn("can't get context of avatar");
resolve(this.color); resolve(this.color);
return; return;
} }
height = canvas.height = this.image.naturalHeight || this.image.offsetHeight || this.image.height; const height = canvas.height = this.image.naturalHeight || this.image.offsetHeight || this.image.height;
width = canvas.width = this.image.naturalWidth || this.image.offsetWidth || this.image.width; const width = canvas.width = this.image.naturalWidth || this.image.offsetWidth || this.image.width;
context.drawImage(this.image, 0, 0); context.drawImage(this.image, 0, 0);
let data;
try { try {
data = context.getImageData(0, 0, width, height); data = context.getImageData(0, 0, width, height);
} catch (e) { } catch (e) {
console.error("can't get image data"); console.error("can't get avatar image data");
console.error(e); console.error(e);
resolve(this.color); resolve(this.color);
return; return;
} }
length = data.data.length; let i = -4;
let count = 0;
const length = data.data.length;
const rgb = { r: 0, g: 0, b: 0 };
while ((i += blockSize * 4) < length) { while ((i += blockSize * 4) < length) {
++count; ++count;
rgb.r += data.data[i]; rgb.r += data.data[i];
@ -64,103 +66,96 @@ function ColorDetector (image) {
resolve(this.color); resolve(this.color);
}); });
} }
].forEach((fn) => { ColorDetector.prototype[fn.name] = fn; });
function PleromaCat (handle, type) {
this.type = type || "cat";
this.handle = handle;
this.colors = {
backgroundColor: "#000000",
borderColor: "#000000"
};
this.config = {
"nya": {
enabled: true,
matcher: "(^|\s|>)な+(\s|<|$)", // eslint-disable-line no-useless-escape
replacer: {
source: "な",
dest: "にゃ"
}
}
};
this.loadConfig();
} }
[
function loadConfig () { class PleromaCat {
const json = PleromaModCatify.config; constructor (handle, type, config) {
config = config || {};
this.type = type || "cat";
this.handle = handle;
this.colors = {
backgroundColor: "#000000",
borderColor: "#000000"
};
this.config = {
"nya": {
enabled: false,
replacer: {}
}
};
this.loadConfig(config);
}
loadConfig (json) {
if (!json.nya) { if (!json.nya) {
return; return;
} }
this.config.nya.enabled = json.nya.enabled; this.config.nya.enabled = json.nya.enabled;
if (this.config.nya.enabled) { if (this.config.nya.enabled) {
this.config.nya.matcher = json.nya.matcher || this.config.nya.matcher; this.config.nya.matcher = json.nya.matcher || this.config.nya.matcher;
this.config.nya.replacer.source = json.nya.replacer.source || this.config.nya.replacer.source; this.config.nya.replacer.source = json.nya.replacer.source || this.config.nya.replacer.source;
this.config.nya.replacer.dest = json.nya.replacer.dest || this.config.nya.replacer.dest; this.config.nya.replacer.dest = json.nya.replacer.dest || this.config.nya.replacer.dest;
} }
}, }
function getClassName () { getClassName () {
return "USER____" + this.handle.replace(/@/g, "_AT_").replace(/\./g, "_"); return "USER____" + this.handle.replace(/@/g, "_AT_").replace(/\./g, "_");
}, }
function makeCat (element) { makeCat (element) {
if (!element) { element = element || document;
element = document;
}
if (element.querySelectorAll) { if (element.querySelectorAll) {
var posts = element.querySelectorAll("." + this.getClassName()); const posts = element.querySelectorAll("." + this.getClassName());
this.makeCatByClassName("user-info"); this.makeCatByClassName("user-info");
this.makeCatByClassName("basic-user-card", "basic-user-card-screen-name"); this.makeCatByClassName("basic-user-card");
for (const currentPost of posts) { posts.forEach((currentPost) => {
this.makeCatByElement(currentPost); this.makeCatByElement(currentPost);
this.nyaByPost(currentPost); this.nyaByPost(currentPost);
} });
} }
}, }
function makeCatByClassName (className, usernameClass) { makeCatByClassName (className, usernameClass) {
if (!className) { className = className || "user-info";
className = "user-info"; usernameClass = usernameClass || "user-screen-name";
} const userInfos = document.querySelectorAll("." + className);
if (!usernameClass) { userInfos.forEach((userInfo) => {
usernameClass = "user-screen-name"; if (userInfo.querySelector && !/cat$/.test(userInfo.innerText)) {
} const handle = userInfo.querySelector("." + usernameClass);
const userinfos = document.querySelectorAll("." + className);
for (const infoIndex in userinfos) {
if (userinfos[infoIndex].querySelector && !/cat$/.test(userinfos[infoIndex].innerText)) {
const handle = userinfos[infoIndex].querySelector("." + usernameClass);
const regexHandle = new RegExp(this.handle, "i"); const regexHandle = new RegExp(this.handle, "i");
if (handle) { if (handle) {
if (regexHandle.test(handle.innerText)) { if (regexHandle.test(handle.innerText)) {
this.makeCatByElement(userinfos[infoIndex]); this.makeCatByElement(userInfo);
} }
} }
} }
} });
}, }
function makeCatByElement (element) { makeCatByElement (element) {
if (element.querySelectorAll) { if (element.querySelectorAll) {
element.classList.add("catified"); element.classList.add("catified");
element.classList.add(this.type); element.classList.add(this.type);
const avatars = element.querySelectorAll(".Avatar"); const avatars = element.querySelectorAll(".Avatar");
for (const avatarIndex in avatars) { avatars.forEach((avatar) => {
const currentAvatar = avatars[avatarIndex]; if (avatar.style) {
if (currentAvatar.style) {
if (this.colors.borderColor === "#000000") { if (this.colors.borderColor === "#000000") {
this.detectColors(currentAvatar); this.detectColors(avatar);
} }
currentAvatar.style.backgroundColor = this.colors.backgroundColor; avatar.style.backgroundColor = this.colors.backgroundColor;
currentAvatar.style.borderColor = this.colors.borderColor; avatar.style.borderColor = this.colors.borderColor;
} }
} });
} }
}, }
function nyaByPost (element) { nyaByPost (element) {
if (element.querySelectorAll && this.config.nya.enabled && element.classList.contains("cat")) { if (element.querySelectorAll && this.config.nya.enabled && element.classList.contains("cat")) {
const contents = element.querySelectorAll(".status-content"); const contents = element.querySelectorAll(".StatusContent");
for (const content of contents) { contents.forEach((content) => {
if (content.innerHTML) { if (content.innerHTML) {
const regex = new RegExp(this.config.nya.matcher, "g"); const regex = new RegExp(this.config.nya.matcher, "g");
let match; let match;
@ -173,69 +168,75 @@ function PleromaCat (handle, type) {
content.innerHTML = content.innerHTML.replace(source, dest); content.innerHTML = content.innerHTML.replace(source, dest);
} }
} }
} });
} }
}, }
function detectColors (avatarElement) { detectColors (avatar) {
const images = avatarElement.querySelectorAll("img"); const images = avatar.querySelectorAll("img");
for (const imageIndex in images) { images.forEach((image) => {
images[imageIndex].crossOrigin = "anonymous"; image.crossOrigin = "anonymous";
const colorAvatar = () => { const colorAvatar = () => {
const detector = new ColorDetector(images[imageIndex]); const detector = new ColorDetector(image);
detector.detect().then((color) => { detector.detect().then((color) => {
this.colors.backgroundColor = detector.getHexColor(); this.colors.backgroundColor = detector.getHexColor();
this.colors.borderColor = detector.getHexColor({ r: -40, g: -40, b: -40 }); this.colors.borderColor = detector.getHexColor({ r: -40, g: -40, b: -40 });
avatarElement.style.backgroundColor = this.colors.backgroundColor; avatar.style.backgroundColor = this.colors.backgroundColor;
avatarElement.style.borderColor = this.colors.borderColor; avatar.style.borderColor = this.colors.borderColor;
}); });
}; };
if (images[imageIndex].complete) {
if (image.complete) {
colorAvatar(); colorAvatar();
} else { } else {
images[imageIndex].onload = colorAvatar; image.onload = colorAvatar;
} }
return; });
}
} }
].forEach((fn) => { PleromaCat.prototype[fn.name] = fn; }); }
function PleromaModCatify () { class PleromaModCatify extends PleromaMod {
this.animals = {}; constructor () {
this.config = { super("pleroma-mod-catify");
stylesheet: "style.css", this.animals = {};
triggers: { this.config = {
cat: { includes: {
displayName: [ css: [
"🐱", "style.css"
"😺",
"🐈",
"😼",
"😹",
"にゃ",
"cat",
"mew",
"meow",
"nya",
"miaou",
"kitten",
"kitn",
"ktn",
"kadse",
"catte"
],
instances: [
"misskey.io"
] ]
}, },
bear: { triggers: {
displayName: [ cat: {
"🐻" displayName: [
], "🐱",
instances: [] "😺",
}, "🐈",
rabbit: { "😼",
displayName: [ "😹",
"にゃ",
"cat",
"mew",
"meow",
"nya",
"miaou",
"kitten",
"kitn",
"ktn",
"kadse",
"catte"
],
instances: [
"misskey.io"
]
},
bear: {
displayName: [
"🐻"
],
instances: []
},
rabbit: {
displayName: [
"🐰", "🐰",
"🐇", "🐇",
"rabbit", "rabbit",
@ -243,65 +244,98 @@ function PleromaModCatify () {
"hase", "hase",
"häschen", "häschen",
"kaninchen" "kaninchen"
], ],
instances: [] instances: []
} }
}, },
filter: [ nya: {
"user-info", enabled: false
"timeline", },
"Conversation", filter: [
"panel-body", "user-info",
"main", "timeline",
"active", "Conversation",
"status-body" "panel-body",
] "main",
}; "active",
"status-body"
]
};
}
this.loadConfig(); onSettingInit (key, ul, li) {
} if (key === "nya") {
[ const label = document.createElement("label");
function loadConfig () { label.classList.add("checkbox");
window.fetch(PleromaModLoader.getModDir() + "pleroma-mod-catify/config.json").then((response) => {
if (response.ok) {
response.json().then((json) => {
PleromaModCatify.config = json;
for (const type in json.triggers) {
this.config.triggers[type] = {};
this.config.triggers[type].displayName = json.triggers[type].displayName || [];
this.config.triggers[type].instances = json.triggers[type].instances || [];
}
}).catch((error) => {
console.error("can't parse catify config");
console.error(error);
});
}
}).catch((error) => {
console.warn("can't load catify config");
console.warn(error);
});
},
function onMutation (mutation, observer) { const input = document.createElement("input");
if (mutation.target.classList.contains("user-info")) { input.setAttribute("type", "checkbox");
mutation.target.classList.remove("catified"); input.checked = this.config.nya.enabled;
for (const type in this.config.triggers) { input.addEventListener("change", (event) => {
mutation.target.classList.remove(type); if (event.target.checked) {
} this.config.nya.enabled = true;
} else {
this.config.nya.enabled = false;
}
this.saveConfig();
});
label.appendChild(input);
const fakeCheckbox = document.createElement("i");
fakeCheckbox.classList.add("checkbox-indicator");
label.appendChild(fakeCheckbox);
const text = document.createElement("span");
text.classList.add("label");
text.innerText = "enable nyanification of posts";
label.appendChild(text);
li.appendChild(label);
ul.appendChild(li);
} }
}
onConfigLoad () {
return new Promise((resolve) => {
this.fetchJson("config.json").then((json) => {
resolve(json);
}).catch((error) => {
console.error("can't parse catify config");
console.error(error);
resolve({});
});
});
}
onMutation (mutation, observer) {
const component = PleromaModLoader.getVueComponent(mutation.target);
if (component) {
this.isCat(component.user);
}
mutation.addedNodes.forEach((node) => {
const nodeComp = PleromaModLoader.getVueComponent(node);
if (nodeComp) {
if (nodeComp.user) {
this.isCat(nodeComp.user);
}
if (nodeComp.status) {
this.isCat(nodeComp.status.user);
}
}
});
this.detectCats(); this.detectCats();
for (const element of mutation.addedNodes) { for (const element of mutation.addedNodes) {
this.catify(element); this.catify(element);
} }
}, }
function onReady () { onRun () {
this.areYouACat(); this.areYouACat();
this.detectCats(); this.detectCats();
this.catify(); this.catify();
}, }
function onDestroy () { onDestroy () {
const allCats = document.querySelectorAll(".catified"); const allCats = document.querySelectorAll(".catified");
for (const cat of allCats) { for (const cat of allCats) {
cat.classList.remove("catified"); cat.classList.remove("catified");
@ -309,81 +343,83 @@ function PleromaModCatify () {
cat.classList.remove(type); cat.classList.remove(type);
} }
} }
}, }
function run () { addCat (handle, type) {
PleromaModLoader.includeModCss("pleroma-mod-catify/" + this.config.stylesheet);
},
function addCat (handle, type) {
if (type == null) { if (type == null) {
type = "cat"; type = "cat";
} }
handle = handle.trim(); handle = handle.trim();
if (!this.animals[handle]) { if (!this.animals[handle]) {
this.animals[handle] = new PleromaCat(handle, type); this.animals[handle] = new PleromaCat(handle, type, this.config);
} }
}, }
function areYouACat () { isCat (user) {
const profile = document.querySelector(".user-card"); if (!user) {
for (const type in this.config.triggers) { return false;
}
let result = false;
Object.keys(this.config.triggers).forEach((type) => {
const pattern = this.config.triggers[type].displayName.join("|"); const pattern = this.config.triggers[type].displayName.join("|");
const regex = new RegExp(pattern, "i"); const regex = new RegExp(pattern, "i");
if (profile) { if (regex.test(user.name)) {
const username = profile.querySelector(".user-name"); this.addCat(user.screen_name, type);
if (username) { result = true;
if (regex.test(username.innerText)) { return;
const accountName = profile.querySelector(".user-screen-name");
if (accountName) {
this.addCat(accountName.innerText.substring(1), type);
}
}
}
} }
} if (regex.test(user.screen_name)) {
}, this.addCat(user.screen_name, type);
result = true;
return;
}
user.fields.forEach((field) => {
if (regex.test(field.name) || regex.test(field.value)) {
this.addCat(user.screen_name, type);
result = true;
}
});
});
return result;
}
function detectCatsByClassName (classname, usernameClass, accountnameClass) { areYouACat () {
const profile = document.querySelector(".user-card");
const cmp = PleromaModLoader.getVueComponent(profile);
if (cmp) {
this.isCat(cmp.user);
}
}
detectCatsByClassName (classname) {
classname = classname || "status-container"; classname = classname || "status-container";
usernameClass = usernameClass || "status-username"; const hits = document.querySelectorAll("." + classname);
accountnameClass = accountnameClass || "account-name"; hits.forEach((hit) => {
const nameAndAccountNames = document.querySelectorAll("." + classname); if (hit.classList.contains("catified")) {
for (const type in this.config.triggers) { return;
const regexName = new RegExp(this.config.triggers[type].displayName.join("|"), "i"); }
const regexInstance = new RegExp(this.config.triggers[type].instances.join("|"), "i"); const cmp = PleromaModLoader.getVueComponent(hit);
for (const currentAccount of nameAndAccountNames) { if (cmp) {
if (currentAccount.querySelector) { if (cmp.user) {
let isCat = false; this.isCat(cmp.user);
const username = currentAccount.querySelector("." + usernameClass); }
if (username && this.config.triggers[type].displayName.length > 0) { if (cmp.status) {
isCat = regexName.test(username.innerText); this.isCat(cmp.status.user);
}
const account = currentAccount.querySelector("." + accountnameClass);
if (account) {
const handle = account.innerText;
if (this.config.triggers[type].instances.length > 0) {
isCat = isCat || regexInstance.test(handle);
}
if (isCat) {
this.addCat(handle, type);
}
}
} }
} }
} });
}, }
function detectCats () { detectCats () {
this.detectCatsByClassName("status-container"); this.detectCatsByClassName("status-container");
this.detectCatsByClassName("basic-user-card", "basic-user-card-user-name-value", "basic-user-card-screen-name"); this.detectCatsByClassName("basic-user-card");
}, }
function catify (element) { catify (element) {
for (const catKey in this.animals) { for (const catKey in this.animals) {
this.animals[catKey].makeCat(element); this.animals[catKey].makeCat(element);
} }
} }
].forEach((fn) => { PleromaModCatify.prototype[fn.name] = fn; }); }
PleromaModLoader.registerMod(PleromaModCatify); PleromaModLoader.registerMod(PleromaModCatify);

View File

@ -1,80 +1,78 @@
function PleromaModLoader () { class PleromaModLoader {
this.config = { constructor () {
"modDirectory": "/instance/pleroma-mods/", this.config = {
"mods": [] "modDirectory": "/instance/pleroma-mods/",
}; "mods": []
this.loadConfig(); };
this.loadedMods = {}; this.loadConfig();
this.classes = {}; this.loadedMods = {};
} this.classes = {};
[ }
function loadMods () {
for (const mod of this.config.mods) {
const modObject = new PleromaMod(mod);
modObject.enabled = true;
if (localStorage.getItem("pleroma_mod_" + mod + "_enabled") === "false") {
modObject.enabled = false;
}
localStorage.setItem("pleroma_mod_" + mod + "_enabled", modObject.enabled);
modObject.include();
this.loadedMods[mod] = modObject;
}
},
function loadConfig () { loadMods () {
window.fetch("/instance/pleroma-mod-config.json").then((response) => { this.config.mods.forEach((mod) => {
if (response.ok) { this.loadMod(mod);
response.json().then((json) => {
for (const key in json) {
this.config[key] = json[key];
}
this.loadMods();
}).catch((error) => {
console.error("can't parse loader config");
console.error(error);
});
}
}).catch((error) => {
console.warn("can't load mod loader config");
console.warn(error);
}); });
}, }
function registerClass (className, object) { loadMod (modName) {
return PleromaModLoader.includeScript(
this.config.modDirectory + "pleroma-mod-" + modName + "/mod.js"
).then(() => {
this.loadedMods[modName] = new this.classes[PleromaModLoader.getClassName(modName)]();
});
}
loadConfig () {
window.fetch("/instance/pleroma-mod-config.json").then((response) => {
return response.json();
}).then((json) => {
Object.keys(json).forEach((key) => {
this.config[key] = json[key];
});
this.loadMods();
}).catch((error) => {
console.error("can't load loader config");
console.error(error);
});
}
registerClass (className, object) {
this.classes[className] = object; this.classes[className] = object;
}, }
function waitUntilReady () { waitUntilReady () {
const postPanel = document.getElementsByClassName("post-status-form"); const postPanel = document.querySelector(".status-container");
const loginPanel = document.getElementsByClassName("login-form"); const loginPanel = document.querySelector(".login-form");
if (postPanel.length > 0 || loginPanel.length > 0) { if (postPanel || loginPanel) {
for (var modName in this.loadedMods) { Object.keys(this.loadedMods).forEach((modName) => {
const settings = document.querySelector(".settings div[label]:first-child"); const settings = document.querySelector(".settings-modal div[label]:first-child");
if (settings) { if (settings) {
if (!settings.querySelector(".mod-settings")) { if (!settings.querySelector(".mod-settings")) {
this.appendModSettings(settings); this.appendModSettings(settings);
} }
} }
const mod = this.loadedMods[modName]; const mod = this.loadedMods[modName];
if (mod.enabled && mod.instance) { if (mod.isEnabled) {
if (mod.instance.onReady) { mod.ready();
mod.instance.onReady();
}
} }
} });
this.createObserver(); this.createObserver();
} else { } else {
console.warn("not ready, trying again in 1s");
window.setTimeout(() => { this.waitUntilReady(); }, 1000); window.setTimeout(() => { this.waitUntilReady(); }, 1000);
} }
}, }
function createCheckbox (label, mod) { createCheckbox (label, mod) {
const labelElement = document.createElement("label"); const labelElement = document.createElement("label");
labelElement.classList.add("checkbox"); labelElement.classList.add("checkbox");
const input = document.createElement("input"); const input = document.createElement("input");
input.setAttribute("type", "checkbox"); input.setAttribute("type", "checkbox");
input.checked = mod.enabled; input.checked = mod.isEnabled;
input.addEventListener("change", (event) => { input.addEventListener("change", (event) => {
if (event.target.checked) { if (event.target.checked) {
mod.enable(); mod.enable();
@ -91,41 +89,51 @@ function PleromaModLoader () {
const text = document.createElement("span"); const text = document.createElement("span");
text.classList.add("label"); text.classList.add("label");
text.innerText = label; text.innerText = label;
labelElement.appendChild(text); labelElement.appendChild(text);
return labelElement; return labelElement;
}, }
function appendModSettings (element) { appendModSettings (element) {
const container = document.createElement("div"); const container = document.createElement("div");
container.classList.add("setting-item"); container.classList.add("setting-item");
container.classList.add("mod-settings"); container.classList.add("mod-settings");
const title = document.createElement("h2"); const title = document.createElement("h2");
title.innerText = "Mods"; title.innerText = "Pleroma Mods";
container.appendChild(title); container.appendChild(title);
const optionList = document.createElement("ul"); const optionList = document.createElement("ul");
optionList.classList.add("setting-list"); optionList.classList.add("setting-list");
const modNames = Object.keys(this.loadedMods).sort(); Object.keys(this.loadedMods).sort().forEach((modName) => {
for (const mod of modNames) {
const li = document.createElement("li"); const li = document.createElement("li");
const enable = this.createCheckbox("enable " + mod, this.loadedMods[mod]); const enable = this.createCheckbox("enable " + modName, this.loadedMods[modName]);
li.appendChild(enable); li.appendChild(enable);
const ulConfig = document.createElement("ul");
ulConfig.classList.add("setting-list");
Object.keys(this.loadedMods[modName].config).forEach((key) => {
if (key === "includes" || key === "filter") {
return;
}
this.loadedMods[modName].onSettingInit(key, ulConfig, document.createElement("li"));
});
li.appendChild(ulConfig);
optionList.appendChild(li); optionList.appendChild(li);
} });
container.appendChild(optionList); container.appendChild(optionList);
element.appendChild(container); element.appendChild(container);
}, }
function createObserver () { createObserver () {
this.containers = { this.containers = {
main: document.querySelector(".main"), main: document.querySelector(".main"),
notifications: document.querySelector(".notifications"), notifications: document.querySelector(".notifications"),
@ -133,33 +141,26 @@ function PleromaModLoader () {
settingsModal: document.querySelector(".settings-modal") settingsModal: document.querySelector(".settings-modal")
}; };
const observerConfig = { subtree: true, childList: true }; const observerConfig = {
subtree: true,
childList: true
};
this.observer = new MutationObserver((mutations, observer) => { this.observer = new MutationObserver((mutations, observer) => {
if ( const modal = document.querySelector(".settings-modal div[label]:first-child");
mutations.length > 0 && if (modal && !modal.querySelector(".mod-settings")) {
mutations[0].addedNodes.length > 0 && this.appendModSettings(modal);
mutations[0].addedNodes[0].classList &&
mutations[0].addedNodes[0].classList.contains("settings_tab-switcher")
) {
this.appendModSettings(mutations[0].addedNodes[0].querySelector("div[label]:first-child"));
} }
for (var modName in this.loadedMods) {
const mod = this.loadedMods[modName]; Object.values(this.loadedMods).forEach((mod) => {
if (mod.instance && mod.enabled) { if (mod.isEnabled) {
if (mod.instance.onMutation) { mutations.forEach((mutation) => {
for (const mutation of mutations) { mod.mutate(mutation, observer);
let filter = null; });
if (mod.instance.config.filter) {
filter = new RegExp(mod.instance.config.filter.join("|"));
}
if (!filter || filter.test(mutation.target.className)) {
mod.instance.onMutation(mutation, observer);
}
}
}
} }
} });
}); });
this.observer.observe(this.containers.main, observerConfig); this.observer.observe(this.containers.main, observerConfig);
if (this.containers.notifications) { if (this.containers.notifications) {
this.observer.observe(this.containers.notifications, observerConfig); this.observer.observe(this.containers.notifications, observerConfig);
@ -171,155 +172,306 @@ function PleromaModLoader () {
this.observer.observe(this.containers.settingsModal, observerConfig); this.observer.observe(this.containers.settingsModal, observerConfig);
} }
} }
].forEach((fn) => { PleromaModLoader.prototype[fn.name] = fn; });
[ static registerMod (mod) {
function registerMod (mod) {
window.__pleromaModLoader.registerClass(mod.name, mod); window.__pleromaModLoader.registerClass(mod.name, mod);
}, }
function includeModScript (src) { static includeScript (src) {
return PleromaModLoader.includeScript(window.__pleromaModLoader.config.modDirectory + src); console.log("include " + src);
},
function includeModCss (src) {
return PleromaModLoader.includeCss(window.__pleromaModLoader.config.modDirectory + src);
},
function excludeModScript (src) {
return PleromaModLoader.excludeScript(window.__pleromaModLoader.config.modDirectory + src);
},
function excludeModCss (src) {
return PleromaModLoader.excludeCss(window.__pleromaModLoader.config.modDirectory + src);
},
function includeScript (src) {
return new Promise((resolve) => { return new Promise((resolve) => {
const body = document.getElementsByTagName("body")[0];
const script = document.createElement("script"); const script = document.createElement("script");
script.setAttribute("src", src); script.setAttribute("src", src);
script.setAttribute("type", "text/javascript"); script.setAttribute("type", "text/javascript");
script.onload = () => { resolve(); }; script.onload = () => {
body.appendChild(script); resolve();
};
document.querySelector("body").appendChild(script);
}); });
}, }
function getVueScope (element) { static includeCss (src) {
console.log("include " + src);
return new Promise((resolve) => {
const link = document.createElement("link");
link.setAttribute("href", src);
link.setAttribute("rel", "stylesheet");
link.setAttribute("type", "text/css");
link.onload = () => {
resolve();
};
document.querySelector("head").appendChild(link);
});
}
static excludeScript (src) {
return new Promise((resolve) => {
const script = document.querySelector("script[src=\"" + src + "\"]");
if (script) {
script.remove();
}
resolve();
});
}
static excludeCss (src) {
return new Promise((resolve) => {
const link = document.querySelector("link[href=\"" + src + "\"]");
if (link) {
link.remove();
}
resolve();
});
}
static getVueScope (element) {
if (!element) { if (!element) {
return null; return null;
} }
if (element.__vue__) { if (element.__vue__) {
console.warn("old vue version, please update pleroma-fe");
return element.__vue__; return element.__vue__;
} }
if (element._vnode) {
return element._vnode;
}
if (element.__vnode) {
return element.__vnode;
}
if (element.parentNode) { if (element.parentNode) {
return PleromaModLoader.getVueScope(element.parentNode); return PleromaModLoader.getVueScope(element.parentNode);
} }
return null; return null;
}, }
function getRootVueScope () { static getVueComponent (element) {
return document.querySelector("#app").__vue__; if (!element) {
}, return null;
}
if (element.__vnode && element.__vnode.component) {
return element.__vnode.component;
}
if (element.__vueParentComponent) {
return element.__vueParentComponent.ctx;
}
if (element.__vueComponent__) {
return element.__vueComponent__;
}
if (element.parentNode) {
return PleromaModLoader.getVueComponent(element.parentNode);
}
return null;
}
function includeCss (src) { static getRootVueScope () {
return new Promise((resolve) => { return PleromaModLoader.getVueScope(document.querySelector("#app"));
const head = document.getElementsByTagName("head")[0]; }
const link = document.createElement("link");
link.setAttribute("href", src);
link.setAttribute("rel", "stylesheet");
link.setAttribute("type", "text/css");
link.onload = () => { resolve(); };
head.appendChild(link);
});
},
function excludeScript (src) { static getToken () {
return new Promise((resolve) => { return PleromaModLoader.getRootVueScope().appContext.provides.store.getters.getUserToken();
const script = document.querySelector('script[src="' + src + '"]'); }
if(script) {
script.remove();
}
resolve();
});
},
function excludeCss (src) { static getModDir () {
return new Promise((resolve) => {
const link = document.querySelector('link[href="' + src + '"]');
if(link) {
link.remove();
}
resolve();
});
},
function getToken () {
return PleromaModLoader.getRootVueScope().$store._vm.getUserToken();
},
function getModDir () {
return window.__pleromaModLoader.config.modDirectory; return window.__pleromaModLoader.config.modDirectory;
} }
].forEach((fn) => { PleromaModLoader[fn.name] = fn; });
function PleromaMod (name) { static getClassName (name) {
this.name = name;
this.instance = null;
this.enabled = localStorage.getItem("pleroma_mod_" + this.name + "_enabled") !== "false";
}
[
function getClassName () {
let className = "PleromaMod"; let className = "PleromaMod";
const nameParts = this.name.split("-"); name.split("-").forEach((namePart) => {
for (const namePart of nameParts) {
className += namePart.substring(0, 1).toUpperCase(); className += namePart.substring(0, 1).toUpperCase();
className += namePart.substring(1); className += namePart.substring(1);
}
return className;
},
function enable () {
this.enabled = true;
this.modLoaded();
if (this.instance.onReady) {
this.instance.onReady();
}
localStorage.setItem("pleroma_mod_" + this.name + "_enabled", this.enabled);
},
function disable () {
this.enabled = false;
if (this.instance.onDestroy) {
this.instance.onDestroy();
}
console.log(this.name + " unloaded");
this.instance = null;
localStorage.setItem("pleroma_mod_" + this.name + "_enabled", this.enabled);
},
function include () {
console.log("loading " + this.name);
PleromaModLoader.includeScript(
PleromaModLoader.getModDir() + "pleroma-mod-" + this.name + "/mod.js"
).then(() => {
if (this.enabled) {
this.modLoaded();
}
}); });
}, return className;
}
function modLoaded () { static api (method, path, params) {
console.log(this.name + " loaded"); return new Promise((resolve, reject) => {
this.instance = new window.__pleromaModLoader.classes[this.getClassName()](); const token = PleromaModLoader.getToken();
const xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.open(method, path);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", "Bearer " + token);
xhr.onreadstatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
reject(new Error({
status: xhr.status,
response: xhr.response,
xhr: xhr
}));
}
}
};
xhr.onload = () => {
resolve(xhr.response);
};
xhr.send(JSON.stringify(params));
});
}
}
class PleromaMod { // eslint-disable-line no-unused-vars
constructor (name) {
this.name = name;
this.config = {};
this.isRunning = false;
}
get isEnabled () {
return localStorage.getItem("pleroma_mod_" + this.name + "_enabled") !== "false" || true;
}
getClassName () {
return PleromaModLoader.getClassName(this.name);
}
getModDir () {
return PleromaModLoader.getModDir() + this.name + "/";
}
ready () {
this.onReady();
this.run(); this.run();
}, }
function run () { destroy () {
if (this.instance) { this.isRunning = false;
this.instance.run(); if (this.config.includes) {
const styles = this.config.includes.css || [];
const scripts = this.config.includes.js || [];
Promise.all(styles.map((style) => {
return this.excludeCss(style);
}).concat(scripts.map((script) => {
return this.excludeScript(script);
}))).then(() => {
this.onDestroy();
});
return;
}
this.onDestroy();
}
run () {
if (this.config.includes) {
const styles = this.config.includes.css || [];
const scripts = this.config.includes.js || [];
Promise.all(styles.map((style) => {
return this.includeCss(style);
}).concat(scripts.map((script) => {
return this.includeScript(script);
})).concat([
this.loadConfig(),
this.onCreate()
])).then(() => {
this.isRunning = true;
this.onRun();
});
return;
}
this.isRunning = true;
this.onRun();
}
mutate (mutation, observer) {
if (this.isRunning) {
this.onMutation(mutation, observer);
} }
} }
].forEach((fn) => { PleromaMod.prototype[fn.name] = fn; });
saveConfig () {
const storedConfig = {};
Object.keys(this.config).filter((key) => {
return key !== "includes" && key !== "filter";
}).forEach((key) => {
storedConfig[key] = this.config[key];
});
localStorage.setItem(this.name + "_config", JSON.stringify(storedConfig));
}
mergeConfig (newConfig) {
Object.keys(newConfig).forEach((key) => {
this.config[key] = JSON.parse(JSON.stringify(newConfig[key]));
});
}
loadConfig () {
return new Promise((resolve) => {
// TODO: use structuredClone when its more supported
this.defaultConfig = JSON.parse(JSON.stringify(this.config));
const storedConfig = JSON.parse(localStorage.getItem(this.name + "_config"));
this.onConfigLoad().then((json) => {
this.mergeConfig(json);
if (storedConfig) {
this.mergeConfig(storedConfig);
}
this.saveConfig();
resolve();
});
});
}
onReady () {}
onCreate () {
return new Promise((resolve) => {
resolve();
});
}
onDestroy () {}
onRun () {}
onMutation (mutation, observer) {}
onConfigLoad () {
return new Promise((resolve) => {
resolve({});
});
}
onSettingInit (key, ul, li) {}
includeCss (src) {
return PleromaModLoader.includeCss(PleromaModLoader.getModDir() + this.name + "/" + src);
}
includeScript (src) {
return PleromaModLoader.includeScript(PleromaModLoader.getModDir() + this.name + "/" + src);
}
excludeCss (src) {
return PleromaModLoader.excludeCss(PleromaModLoader.getModDir() + this.name + "/" + src);
}
excludeScript (src) {
return PleromaModLoader.excludeScript(PleromaModLoader.getModDir() + this.name + "/" + src);
}
fetchJson (src) {
console.log("loading " + src);
return window.fetch(PleromaModLoader.getModDir() + this.name + "/" + src).then((response) => {
return response.json();
});
}
api (method, path, params) {
return PleromaModLoader.api(method, path, params);
}
enable () {
this.ready();
localStorage.setItem("pleroma_mod_" + this.name + "_enabled", true);
}
disable () {
this.destroy();
localStorage.setItem("pleroma_mod_" + this.name + "_enabled", false);
}
}
window.__pleromaModLoader = new PleromaModLoader(); window.__pleromaModLoader = new PleromaModLoader();
window.__pleromaModLoader.waitUntilReady(); window.__pleromaModLoader.waitUntilReady();