426 lines
11 KiB
JavaScript
426 lines
11 KiB
JavaScript
/* global PleromaMod */
|
|
|
|
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) {
|
|
offset = { r: 0, g: 0, b: 0 };
|
|
}
|
|
return "#" +
|
|
this.componentToHex(this.color.r + offset.r) +
|
|
this.componentToHex(this.color.g + offset.g) +
|
|
this.componentToHex(this.color.b + offset.b);
|
|
}
|
|
|
|
detect () {
|
|
return new Promise((resolve) => {
|
|
const blockSize = 5;
|
|
const canvas = document.createElement("canvas");
|
|
const context = canvas.getContext && canvas.getContext("2d");
|
|
|
|
if (!context) {
|
|
console.warn("can't get context of avatar");
|
|
resolve(this.color);
|
|
return;
|
|
}
|
|
|
|
const height = canvas.height = this.image.naturalHeight || this.image.offsetHeight || this.image.height;
|
|
const width = canvas.width = this.image.naturalWidth || this.image.offsetWidth || this.image.width;
|
|
|
|
context.drawImage(this.image, 0, 0);
|
|
|
|
let data;
|
|
try {
|
|
data = context.getImageData(0, 0, width, height);
|
|
} catch (e) {
|
|
console.error("can't get avatar image data");
|
|
console.error(e);
|
|
resolve(this.color);
|
|
return;
|
|
}
|
|
|
|
let i = -4;
|
|
let count = 0;
|
|
const length = data.data.length;
|
|
const rgb = { r: 0, g: 0, b: 0 };
|
|
while ((i += blockSize * 4) < length) {
|
|
++count;
|
|
rgb.r += data.data[i];
|
|
rgb.g += data.data[i + 1];
|
|
rgb.b += data.data[i + 2];
|
|
}
|
|
|
|
this.color.r = ~~(rgb.r / count);
|
|
this.color.g = ~~(rgb.g / count);
|
|
this.color.b = ~~(rgb.b / count);
|
|
|
|
resolve(this.color);
|
|
});
|
|
}
|
|
}
|
|
|
|
class PleromaCat {
|
|
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) {
|
|
return;
|
|
}
|
|
|
|
this.config.nya.enabled = json.nya.enabled;
|
|
if (this.config.nya.enabled) {
|
|
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.dest = json.nya.replacer.dest || this.config.nya.replacer.dest;
|
|
}
|
|
}
|
|
|
|
getClassName () {
|
|
return "USER____" + this.handle.replace(/@/g, "_AT_").replace(/\./g, "_");
|
|
}
|
|
|
|
makeCat (element) {
|
|
element = element || document;
|
|
if (element.querySelectorAll) {
|
|
const posts = element.querySelectorAll("." + this.getClassName());
|
|
this.makeCatByClassName("user-info");
|
|
this.makeCatByClassName("basic-user-card");
|
|
posts.forEach((currentPost) => {
|
|
this.makeCatByElement(currentPost);
|
|
this.nyaByPost(currentPost);
|
|
});
|
|
}
|
|
}
|
|
|
|
makeCatByClassName (className, usernameClass) {
|
|
className = className || "user-info";
|
|
usernameClass = usernameClass || "user-screen-name";
|
|
const userInfos = document.querySelectorAll("." + className);
|
|
userInfos.forEach((userInfo) => {
|
|
if (userInfo.querySelector && !/cat$/.test(userInfo.innerText)) {
|
|
const handle = userInfo.querySelector("." + usernameClass);
|
|
const regexHandle = new RegExp(this.handle, "i");
|
|
if (handle) {
|
|
if (regexHandle.test(handle.innerText)) {
|
|
this.makeCatByElement(userInfo);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
makeCatByElement (element) {
|
|
if (element.querySelectorAll) {
|
|
element.classList.add("catified");
|
|
element.classList.add(this.type);
|
|
const avatars = element.querySelectorAll(".Avatar");
|
|
avatars.forEach((avatar) => {
|
|
if (avatar.style) {
|
|
if (this.colors.borderColor === "#000000") {
|
|
this.detectColors(avatar);
|
|
}
|
|
avatar.style.backgroundColor = this.colors.backgroundColor;
|
|
avatar.style.borderColor = this.colors.borderColor;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
nyaByPost (element) {
|
|
if (element.querySelectorAll && this.config.nya.enabled && element.classList.contains("cat")) {
|
|
const contents = element.querySelectorAll(".StatusContent");
|
|
contents.forEach((content) => {
|
|
if (content.innerHTML) {
|
|
const regex = new RegExp(this.config.nya.matcher, "g");
|
|
let match;
|
|
while ((match = regex.exec(content.innerHTML)) !== null) {
|
|
const source = match[0];
|
|
const dest = source.replace(
|
|
new RegExp(this.config.nya.replacer.source, "g"),
|
|
this.config.nya.replacer.dest
|
|
);
|
|
content.innerHTML = content.innerHTML.replace(source, dest);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
detectColors (avatar) {
|
|
const images = avatar.querySelectorAll("img");
|
|
images.forEach((image) => {
|
|
image.crossOrigin = "anonymous";
|
|
const colorAvatar = () => {
|
|
const detector = new ColorDetector(image);
|
|
detector.detect().then((color) => {
|
|
this.colors.backgroundColor = detector.getHexColor();
|
|
this.colors.borderColor = detector.getHexColor({ r: -40, g: -40, b: -40 });
|
|
avatar.style.backgroundColor = this.colors.backgroundColor;
|
|
avatar.style.borderColor = this.colors.borderColor;
|
|
});
|
|
};
|
|
|
|
if (image.complete) {
|
|
colorAvatar();
|
|
} else {
|
|
image.onload = colorAvatar;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
class PleromaModCatify extends PleromaMod {
|
|
constructor () {
|
|
super("pleroma-mod-catify");
|
|
this.animals = {};
|
|
this.config = {
|
|
includes: {
|
|
css: [
|
|
"style.css"
|
|
]
|
|
},
|
|
triggers: {
|
|
cat: {
|
|
displayName: [
|
|
"🐱",
|
|
"😺",
|
|
"🐈",
|
|
"😼",
|
|
"😹",
|
|
"にゃ",
|
|
"cat",
|
|
"mew",
|
|
"meow",
|
|
"nya",
|
|
"miaou",
|
|
"kitten",
|
|
"kitn",
|
|
"ktn",
|
|
"kadse",
|
|
"catte"
|
|
],
|
|
instances: [
|
|
"misskey.io"
|
|
]
|
|
},
|
|
bear: {
|
|
displayName: [
|
|
"🐻"
|
|
],
|
|
instances: []
|
|
},
|
|
rabbit: {
|
|
displayName: [
|
|
"🐰",
|
|
"🐇",
|
|
"rabbit",
|
|
"bunny",
|
|
"hase",
|
|
"häschen",
|
|
"kaninchen"
|
|
],
|
|
instances: []
|
|
}
|
|
},
|
|
nya: {
|
|
enabled: false
|
|
},
|
|
filter: [
|
|
"user-info",
|
|
"timeline",
|
|
"Conversation",
|
|
"panel-body",
|
|
"main",
|
|
"active",
|
|
"status-body"
|
|
]
|
|
};
|
|
}
|
|
|
|
onSettingInit (key, ul, li) {
|
|
if (key === "nya") {
|
|
const label = document.createElement("label");
|
|
label.classList.add("checkbox");
|
|
|
|
const input = document.createElement("input");
|
|
input.setAttribute("type", "checkbox");
|
|
input.checked = this.config.nya.enabled;
|
|
input.addEventListener("change", (event) => {
|
|
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();
|
|
for (const element of mutation.addedNodes) {
|
|
this.catify(element);
|
|
}
|
|
}
|
|
|
|
onRun () {
|
|
this.areYouACat();
|
|
this.detectCats();
|
|
this.catify();
|
|
}
|
|
|
|
onDestroy () {
|
|
const allCats = document.querySelectorAll(".catified");
|
|
for (const cat of allCats) {
|
|
cat.classList.remove("catified");
|
|
for (const type in this.config.triggers) {
|
|
cat.classList.remove(type);
|
|
}
|
|
}
|
|
}
|
|
|
|
addCat (handle, type) {
|
|
if (type == null) {
|
|
type = "cat";
|
|
}
|
|
handle = handle.trim();
|
|
if (!this.animals[handle]) {
|
|
this.animals[handle] = new PleromaCat(handle, type, this.config);
|
|
}
|
|
}
|
|
|
|
isCat (user) {
|
|
if (!user) {
|
|
return false;
|
|
}
|
|
let result = false;
|
|
Object.keys(this.config.triggers).forEach((type) => {
|
|
const pattern = this.config.triggers[type].displayName.join("|");
|
|
const regex = new RegExp(pattern, "i");
|
|
if (regex.test(user.name)) {
|
|
this.addCat(user.screen_name, type);
|
|
result = true;
|
|
return;
|
|
}
|
|
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;
|
|
}
|
|
|
|
areYouACat () {
|
|
const profile = document.querySelector(".user-card");
|
|
const cmp = PleromaModLoader.getVueComponent(profile);
|
|
if (cmp) {
|
|
this.isCat(cmp.user);
|
|
}
|
|
}
|
|
|
|
detectCatsByClassName (classname) {
|
|
classname = classname || "status-container";
|
|
const hits = document.querySelectorAll("." + classname);
|
|
hits.forEach((hit) => {
|
|
if (hit.classList.contains("catified")) {
|
|
return;
|
|
}
|
|
const cmp = PleromaModLoader.getVueComponent(hit);
|
|
if (cmp) {
|
|
if (cmp.user) {
|
|
this.isCat(cmp.user);
|
|
}
|
|
if (cmp.status) {
|
|
this.isCat(cmp.status.user);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
detectCats () {
|
|
this.detectCatsByClassName("status-container");
|
|
this.detectCatsByClassName("basic-user-card");
|
|
}
|
|
|
|
catify (element) {
|
|
for (const catKey in this.animals) {
|
|
this.animals[catKey].makeCat(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
PleromaModLoader.registerMod(PleromaModCatify);
|