//META{"name":"AnimatedStatus","source":"https://raw.githubusercontent.
com/
toluschr/BetterDiscord-Animated-Status/master/
Animated_Status.plugin.js","website":"https://github.com/toluschr/BetterDiscord-
Animated-Status"}*//
class AnimatedStatus {
/* BD functions */
getName() { return "Animated Status"; }
getVersion() { return "0.13.2"; }
getAuthor() { return "toluschr"; }
getDescription() { return "Animate your Discord status"; }
SetData(key, value) {
BdApi.setData("AnimatedStatus", key, value);
}
GetData(key) {
return BdApi.getData("AnimatedStatus", key);
}
/* Code related to Animations */
load() {
this.kSpacing = "15px";
this.kMinTimeout = 2900;
this.cancel = undefined;
this.animation = this.GetData("animation") || [];
this.timeout = this.GetData("timeout") || this.kMinTimeout;
this.randomize = this.GetData("randomize") || false;
// Import Older Config Files
if (typeof this.timeout == "string")
this.timeout = parseInt(this.timeout);
if (this.animation.length > 0 && Array.isArray(this.animation[0]))
this.animation = this.animation.map(em =>
this.ConfigObjectFromArray(em));
Status.authToken = BdApi.findModule(m => m.default &&
m.default.getToken).default.getToken();
this.currentUser = BdApi.findModule(m => m.default &&
m.default.getCurrentUser).default.getCurrentUser();
}
start() {
if (this.animation.length == 0)
BdApi.showToast("Animated Status: No status set. Go to
Settings>Plugins to set a custom animation!");
else
this.AnimationLoop();
}
stop() {
if (this.cancel) {
this.cancel();
} else {
console.assert(this.loop != undefined);
clearTimeout(this.loop);
}
Status.Set(null);
}
ConfigObjectFromArray(arr) {
let data = {};
if (arr[0] !== undefined && arr[0].length > 0) data.text =
arr[0];
if (arr[1] !== undefined && arr[1].length > 0) data.emoji_name =
arr[1];
if (arr[2] !== undefined && arr[2].length > 0) data.emoji_id =
arr[2];
if (arr[3] !== undefined && arr[3].length > 0) data.timeout =
parseInt(arr[3]);
return data;
}
async ResolveStatusField(text = "") {
let evalPrefix = "eval ";
if (!text.startsWith(evalPrefix)) return text;
try {
return eval(text.substr(evalPrefix.length));
} catch (e) {
BdApi.showToast(e, {type: "error"});
return "";
}
}
AnimationLoop(i = 0) {
i %= this.animation.length;
// Every loop needs its own shouldContinue variable, otherwise there
// is the possibility of multiple loops running simultaneously
let shouldContinue = true;
this.loop = undefined;
this.cancel = () => { shouldContinue = false; };
Promise.all([this.ResolveStatusField(this.animation[i].text),
this.ResolveStatusField(this.animation[i].emoji_name),
this.ResolveStatusField(this.animation[i].emoji_id)]).then(p => {
Status.Set(this.ConfigObjectFromArray(p));
this.cancel = undefined;
if (shouldContinue) {
let timeout = this.animation[i].timeout || this.timeout;
this.loop = setTimeout(() => {
if (this.randomize) {
i += Math.floor(Math.random() *
(this.animation.length - 2));
}
this.AnimationLoop(i + 1);
}, timeout);
}
});
}
NewEditorRow({text, emoji_name, emoji_id, timeout} = {}) {
let hbox = GUI.newHBox();
hbox.style.marginBottom = this.kSpacing;
let textWidget = hbox.appendChild(GUI.newInput(text, "Text"));
textWidget.style.marginRight = this.kSpacing;
let emojiWidget = hbox.appendChild(GUI.newInput(emoji_name, "👍" +
(this.currentUser.premiumType ? " / Nitro Name" : "")));
emojiWidget.style.marginRight = this.kSpacing;
emojiWidget.style.width = "140px";
let optNitroIdWidget = hbox.appendChild(GUI.newInput(emoji_id, "Nitro
ID"));
if (!this.currentUser.premiumType) optNitroIdWidget.style.display =
"none";
optNitroIdWidget.style.marginRight = this.kSpacing;
optNitroIdWidget.style.width = "140px";
let optTimeoutWidget = hbox.appendChild(GUI.newNumericInput(timeout,
this.kMinTimeout, "Time"));
optTimeoutWidget.style.width = "75px";
hbox.onkeydown = (e) => {
let activeContainer = document.activeElement.parentNode;
let activeIndex =
Array.from(activeContainer.children).indexOf(document.activeElement);
let keymaps = {
"Delete": [
[[false, true], () => {
activeContainer = hbox.nextSibling ||
hbox.previousSibling;
hbox.parentNode.removeChild(hbox);
}],
],
"ArrowDown": [
[[true, true], () => {
activeContainer = this.NewEditorRow();
hbox.parentNode.insertBefore(activeContainer,
hbox.nextSibling);
}],
[[false, true], () => {
let next = hbox.nextSibling;
if (next != undefined) {
next.replaceWith(hbox);
hbox.parentNode.insertBefore(next, hbox);
}
}],
[[false, false], () => {
activeContainer = hbox.nextSibling;
}],
],
"ArrowUp": [
[[true, true], () => {
activeContainer = this.NewEditorRow();
hbox.parentNode.insertBefore(activeContainer,
hbox);
}],
[[false, true], () => {
let prev = hbox.previousSibling;
if (prev != undefined) {
prev.replaceWith(hbox);
hbox.parentNode.insertBefore(prev,
hbox.nextSibling);
}
}],
[[false, false], () => {
activeContainer = hbox.previousSibling;
}],
],
};
let letter = keymaps[e.key];
if (letter == undefined) return;
for (let i = 0; i < letter.length; i++) {
if (letter[i][0][0] != e.ctrlKey || letter[i][0][1] !=
e.shiftKey)
continue;
letter[i][1]();
if (activeContainer)
activeContainer.children[activeIndex].focus();
e.preventDefault();
return;
}
};
return hbox;
}
EditorFromJSON(json) {
let out = document.createElement("div");
for (let i = 0; i < json.length; i++) {
out.appendChild(this.NewEditorRow(json[i]));
}
return out;
}
JSONFromEditor(editor) {
return Array.prototype.slice.call(editor.childNodes).map(row => {
return
this.ConfigObjectFromArray(Array.prototype.slice.call(row.childNodes).map(e =>
e.value));
});
}
// Settings
getSettingsPanel() {
let settings = document.createElement("div");
settings.style.padding = "10px";
// timeout
settings.appendChild(GUI.newLabel("Step-Duration (3000: 3 seconds,
3500: 3.5 seconds, ...), overwritten by invididual steps"));
let timeout = settings.appendChild(GUI.newNumericInput(this.timeout,
this.kMinTimeout));
timeout.style.marginBottom = this.kSpacing;
// Animation Container
settings.appendChild(GUI.newLabel("Animation"));
let animationContainer =
settings.appendChild(document.createElement("div"));
animationContainer.marginBottom = this.kSpacing;
// Editor
let edit =
animationContainer.appendChild(this.EditorFromJSON(this.animation));
// Actions
let actions = settings.appendChild(GUI.newHBox());
// Add Step
let addStep = actions.appendChild(GUI.setSuggested(GUI.newButton("+",
false)));
addStep.title = "Add step to end";
addStep.onclick = () => edit.appendChild(this.NewEditorRow());
// Del Step
let delStep = actions.appendChild(GUI.setDestructive(GUI.newButton("-",
false)));
delStep.title = "Remove last step";
delStep.style.marginLeft = this.kSpacing;
delStep.onclick = () =>
edit.removeChild(edit.childNodes[edit.childNodes.length - 1]);
// Move save to the right (XXX make use of flexbox)
actions.appendChild(GUI.setExpand(document.createElement("div"), 2));
// Save
let save = actions.appendChild(GUI.newButton("Save"));
GUI.setSuggested(save, true);
save.onclick = () => {
try {
// Set timeout
this.SetData("randomize", this.randomize);
this.SetData("timeout", parseInt(timeout.value));
this.SetData("animation", this.JSONFromEditor(edit));
} catch (e) {
BdApi.showToast(e, {type: "error"});
return;
}
// Show Toast
BdApi.showToast("Settings were saved!", {type: "success"});
// Restart
this.stop();
this.load();
this.start();
};
// End
return settings;
}
}
/* Status API */
const Status = {
strerror: (req) => {
if (req.status < 400) return undefined;
if (req.status == 401) return "Invalid AuthToken";
// Discord _sometimes_ returns an error message
let json = JSON.parse(req.response);
for (const s of ["errors", "custom_status", "text", "_errors", 0,
"message"])
if ((json == undefined) || ((json = json[s]) == undefined))
return "Unknown error. Please report at
github.com/toluschr/BetterDiscord-Animated-Status";
return json;
},
Set: async (status) => {
let req = new XMLHttpRequest();
req.open("PATCH", "/api/v9/users/@me/settings", true);
req.setRequestHeader("authorization", Status.authToken);
req.setRequestHeader("content-type", "application/json");
req.onload = () => {
let err = Status.strerror(req);
if (err != undefined)
BdApi.showToast(`Animated Status: Error: ${err}`, {type:
"error"});
};
if (status === {}) status = null;
req.send(JSON.stringify({custom_status: status}));
},
};
// Used to easily style elements like the 'native' discord ones
const GUI = {
newInput: (text = "", placeholder = "") => {
let input = document.createElement("input");
input.className = "inputDefault-3FGxgL input-2g-os5";
input.value = String(text);
input.placeholder = String(placeholder);
return input;
},
newNumericInput: (text = "", minimum = 0, placeholder = "") => {
let out = GUI.newInput(text, placeholder);
out.setAttribute("type", "number");
out.addEventListener("focusout", () => {
if (parseInt(out.value) < minimum) {
out.value = String(minimum);
BdApi.showToast(`Value must not be lower than ${minimum}`,
{type: "error"});
}
});
return out;
},
newLabel: (text = "") => {
let label = document.createElement("h5");
label.className = "h5-2RwDNl";
label.innerText = String(text);
return label;
},
newButton: (text, filled = true) => {
let button = document.createElement("button");
button.className = "button-f2h6uQ colorBrand-I6CyqQ sizeSmall-wU2dO-
grow-2sR_-F";
if (filled) button.classList.add("lookFilled-yCfaCM");
else button.classList.add("lookOutlined-3yKVGo");
button.innerText = String(text);
return button;
},
newHBox: () => {
let hbox = document.createElement("div");
hbox.style.display = "flex";
hbox.style.flexDirection = "row";
return hbox;
},
setExpand: (element, value) => {
element.style.flexGrow = value;
return element;
},
setSuggested: (element, value = true) => {
if (value) element.classList.add("colorGreen-3y-Z79");
else element.classList.remove("colorGreen-3y-Z79");
return element;
},
setDestructive: (element, value = true) => {
if (value) element.classList.add("colorRed-rQXKgM");
else element.classList.remove("colorRed-rQXKgM");
return element;
}
};