/**
* @name ImageSenderV2
* @author CriosChan
* @authorLink https://github.com/CriosChan/
* @description This plugin allows you to easily send an image from your PC, like memes for example!
* @version 0.0.5
* @invite R7vuNSv
* @authorid 328191996579545088
* @updateUrl https://raw.githubusercontent.com/CriosChan/ImageSender/main/ImageSender.plugin.js
* @website https://github.com/CriosChan/
* @source https://github.com/CriosChan/ImageSender
*/
module.exports = (meta) => {
////////////////////////////////////
///// /////
///// Utils /////
///// /////
////////////////////////////////////
function delay(time) {
return new Promise(function (resolve) {
setTimeout(resolve, time);
});
}
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}
function getModule(filter, searchOptions) {
return BdApi.Webpack.getModule((...args) => {
try {
return filter(...args);
} catch (ignored) {
return false;
}
}, searchOptions);
}
function contains(target, pattern) {
var value = 0;
pattern.forEach(function (word) {
value = value + target.includes(word);
});
return value > 0;
}
function generateChecksum(str, algorithm, encoding) {
return crypto
.createHash(algorithm || "sha256")
.update(str, "utf8")
.digest(encoding || "hex");
}
function createMessage(message_content = "", image = "") {
const container = document.createElement("div");
container.id = "imagesender_message";
const message = document.createElement("div");
message.className = "emptyText-mZZyQk";
message.textContent = message_content;
const svg = document.createElement("div");
svg.style.backgroundImage = `url(${image})`;
svg.style.backgroundSize = "contain";
svg.style.backgroundRepeat = "no-repeat";
svg.style.backgroundPosition = "center";
container.append(svg, message);
return container;
}
////////////////////////////////////
///// /////
///// ZeresPluginLibrary /////
///// /////
////////////////////////////////////
const fs = require("fs");
console.log(
fs.existsSync(BdApi.Plugins.folder + "\\0PluginLibrary.plugin.js")
);
if (
!BdApi.Plugins.get("ZeresPluginLibrary") &&
!fs.existsSync(BdApi.Plugins.folder + "\\0PluginLibrary.plugin.js")
) {
return {
start: () => {
BdApi.showConfirmationModal(
"Library Missing",
`The library plugin needed for ${meta.name} is missing. Please click Download Now to install it.`,
{
confirmText: "Download Now",
cancelText: "Cancel",
onConfirm: () => {
require("request").get(
"https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js",
async (err, res, body) => {
if (err) return;
await new Promise((r) =>
fs.writeFile(
require("path").join(
BdApi.Plugins.folder,
"0PluginLibrary.plugin.js"
),
body,
r
)
).then(async () => {
await delay(1000);
BdApi.Plugins.reload(meta.name);
});
}
);
},
}
);
},
stop: () => {},
};
}
////////////////////////////////////
///// /////
///// Modules /////
///// /////
////////////////////////////////////
const { DiscordSelectors, DOMTools, Settings } =
BdApi.Plugins.get("ZeresPluginLibrary").instance.Library;
const { Switch, Textbox } = Settings;
const cloudUploader = getModule((module) => {
return Object.values(module).some((value) => {
if (typeof value !== "object" || value === null) return false;
const curValue = value;
return (
curValue.NOT_STARTED !== undefined &&
curValue.UPLOADING !== undefined &&
module.n !== undefined
);
});
});
const uploader = BdApi.findModuleByProps("instantBatchUpload");
const message_sender = BdApi.findModuleByProps("_sendMessage");
const crypto = require("crypto");
const path = require("path");
////////////////////////////////////
///// /////
///// SETTINGS /////
///// /////
////////////////////////////////////
const settings_default = {
nitroUser: false,
saveLink: false,
folders: [],
savedLinks: [],
};
let settings = {};
////////////////////////////////////
///// /////
///// Global Variables /////
///// /////
////////////////////////////////////
let loaded_folders = [];
let hidden = {};
////////////////////////////////////
///// /////
///// Plugin Events /////
///// /////
////////////////////////////////////
const navbar_button_compatibility = (e) => {
let node = null;
//Security to avoid putting the "selected" class on the navbar
if (e.target.nodeName.toLowerCase() == "button") {
node = e.target;
} else if (e.target.className.includes("contents")) {
node = e.target.parentElement;
} else if (e.target.parentElement.className.includes("contents")) {
node = e.target.parentElement.parentElement;
}
const navButtonActive_class = remove_selected(
document.querySelector("#imagesender-picker-tab")
);
if (document.querySelector("#imagesender-picker-tab-panel") != null) {
document.querySelector("#imagesender-picker-tab-panel").style.display =
"none";
}
node.parentElement.parentElement.parentElement.lastChild.style.display =
null;
if (!node.className.includes(navButtonActive_class))
node.className += " " + navButtonActive_class;
};
const reactions_picker_event = () => {
create_UI(false);
};
const image_swipe_load = () => {
const body = document.querySelector("#imagesender_body");
const scrollTop = body.scrollTop;
const scrollHeight = body.scrollHeight;
const clientHeight = body.clientHeight;
if (scrollTop + clientHeight >= scrollHeight) {
generateimages(
body,
document.querySelector("#imagesender_searchBar").querySelector("input")
.value,
body.querySelectorAll(".imagesender_buttons").length
);
}
};
////////////////////////////////////
///// /////
///// Plugin Functions /////
///// /////
////////////////////////////////////
function createSettings(settingsPanel) {
const nitroUserSwitch = new Switch(
"Enable Nitro support",
"Allows you to send larger files",
settings.nitroUser,
(checked) => {
settings.nitroUser = checked;
BdApi.saveData(meta.name, "settings", settings);
}
);
const saveLinkSwitch = new Switch(
"[ALPHA] Save Link on send",
"Saves the link of the image you just sent, to later use this link instead of doing an upload. Theoretically more ecological.",
settings.saveLink,
(checked) => {
settings.saveLink = checked;
BdApi.saveData(meta.name, "settings", settings);
}
);
const folders_settings = document.createElement("div");
folders_settings.className = "settings";
const folders_label = document.createElement("label");
folders_label.className = "title-2yADjX";
folders_label.textContent = "Folders :";
folders_settings.append(folders_label);
try {
settings.folders.forEach((element, i) => {
const trash_html = ``;
const trash_button = DOMTools.createElement(trash_html);
const container = document.createElement("div");
trash_button.addEventListener("click", () => {
container.remove();
settings.folders.splice(i, 1);
BdApi.saveData(meta.name, "settings", settings);
settingsPanel.innerHTML = "";
createSettings(settingsPanel);
check_folders();
});
container.className = "container";
const name = new Textbox("Name", "", element.name, (changes) => {
settings.folders[i].name = changes;
BdApi.saveData(meta.name, "settings", settings);
check_folders();
});
const path = new Textbox("Path", "", element.path, (changes) => {
settings.folders[i].path = changes;
BdApi.saveData(meta.name, "settings", settings);
check_folders();
});
container.append(name.getElement(), path.getElement(), trash_button);
folders_settings.append(container);
});
} catch {}
const add_html = ``;
const add_button_el = DOMTools.createElement(add_html);
add_button_el.addEventListener("click", () => {
settings.folders.push({ name: "", path: "" });
settingsPanel.innerHTML = "";
createSettings(settingsPanel);
});
folders_settings.append(add_button_el);
settingsPanel.append(
nitroUserSwitch.getElement(),
saveLinkSwitch.getElement(),
folders_settings
);
}
function check_folders() {
_loaded_folders = [];
settings.folders.forEach((folder) => {
BdApi.showToast(`[${meta.name}] Working on ${folder.name}`);
if (fs.existsSync(folder.path)) {
_loaded_folders.push(folder);
BdApi.showToast(`[${meta.name}] ${folder.name} loaded with success !`, {
type: "success",
});
} else {
BdApi.showToast(`[${meta.name}] ${folder.name} doesn't exist !`, {
type: "error",
});
}
});
loaded_folders = [];
Object.assign(loaded_folders, _loaded_folders);
}
//Add UI button to textarea
function add_button() {
document.querySelectorAll("#ImageSender_UI_Button").forEach((element) => {
element.remove();
});
//If not allowed to send a message, do not display the button
if (
!document
.querySelector(DiscordSelectors.Textarea.buttons)
.parentElement.className.includes("Disabled")
) {
const ui_opener = document.createElement("button");
ui_opener.id = "ImageSender_UI_Button";
ui_opener.addEventListener("click", () => {
open_ui();
});
document
.querySelector(DiscordSelectors.Textarea.buttons)
.append(ui_opener);
}
//Generate UI to reaction picker
document
.querySelectorAll(".expression-picker-chat-input-button")
.forEach((node) => {
node.addEventListener("click", reactions_picker_event);
});
}
function open_ui() {
const emoji_button = document.querySelector("button[class*=emojiButton]");
emoji_button.click();
create_UI(true);
}
function remove_selected(selected) {
const navButtonActive_class = selected.className
.split(" ")
.filter((name) => name.includes("navButtonActive"))
.toString();
selected.className = selected.className.replace(navButtonActive_class, "");
return navButtonActive_class;
}
function create_images_button(channelID, path, name, ext) {
const button = document.createElement("button");
button.className = "imagesender_buttons";
const data = fs.readFileSync(path + "\\" + name, { encoding: "base64" });
const background = "data:image/" + ext + ";base64," + data;
button.style.backgroundImage = `url(${background})`;
button.addEventListener("click", async () => {
let checksum = await generateChecksum(data);
let link = settings.savedLinks[checksum];
if (link) {
const message_settings = {
content: link,
tts: false,
invalidEmojis: [],
validNonShortcutEmojis: [],
};
message_sender.sendMessage(channelID, message_settings);
} else {
const upload = new cloudUploader.n(
{
file: dataURLtoFile(background, `${name}`),
platform: 1,
},
channelID
);
const upload_settings = {
channelId: channelID,
uploads: [upload],
draftType: 0,
parsedMessage: {
channel_id: channelID,
content: "",
tts: false,
invalidEmojis: [],
},
};
uploader.uploadFiles(upload_settings);
if (settings.saveLink) {
const observer = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
if (
mutation.type === "childList" &&
mutation.addedNodes.length > 0
) {
if (
mutation.addedNodes[0].querySelector("a[class*=originalLink]")
) {
settings.savedLinks[checksum] =
mutation.addedNodes[0].querySelector(
"a[class*=originalLink]"
).href;
BdApi.saveData(meta.name, "settings", settings);
observer.disconnect();
}
}
}
});
observer.observe(
document.querySelector("ol[data-list-id=chat-messages]"),
{ childList: true }
);
}
}
});
return button;
}
async function generateimages(body, search = "", offset = 0) {
if (loaded_folders.length == 0) {
body.innerHTML = "";
body.append(
createMessage(
"Hmm... You didn't give a folder in the settings",
"https://raw.githubusercontent.com/CriosChan/ImageSender/main/require/folder.svg"
)
);
}
const conditions = [
search.toLowerCase(),
search.toLowerCase().replaceAll(" ", "_"),
search.toLowerCase().replaceAll("_", " "),
];
if (offset == 0 && search != "") {
body.innerHTML = "";
}
let added = 0;
for (const folder of loaded_folders) {
const folder_content = fs.readdirSync(folder.path, {
withFileTypes: true,
});
let filecontainer = null;
if (!document.querySelector(`#${folder.name.replaceAll(" ", "-")}`)) {
let { hide_container_button, filecontainer_temp } = create_container(
folder.name.replaceAll(" ", "-"),
folder_content.length
);
filecontainer = filecontainer_temp;
body.append(hide_container_button, filecontainer);
} else {
filecontainer = document.querySelector(
`#${folder.name.replaceAll(" ", "-")}`
);
hide_container_button = document.querySelector(
`#${folder.name.replaceAll(" ", "-")}-hide`
);
}
let emergency_load = false;
if (hidden[folder.name.replaceAll(" ", "-")]) {
console.log(hidden[folder.name.replaceAll(" ", "-")].isHidden)
if (hidden[folder.name.replaceAll(" ", "-")].isHidden) {
offset =
offset -
filecontainer.querySelectorAll(".imagesender_buttons").length;
continue;
} else {
emergency_load = true;
hidden[folder.name.replaceAll(" ", "-")] = null;
}
}
if (folder_content.length <= offset) {
offset = offset - folder_content.length;
continue;
}
let channelID = BdApi.findModuleByProps(
"getLastSelectedChannelId"
).getChannelId();
let index = 0;
for (const file of folder_content) {
if (index < offset) {
index++;
continue;
}
if (added > 11 && emergency_load == false) break;
const ext = path.extname(file.name).toLowerCase().replace(".", "");
if (["jpg", "jpeg", "png", "gif", "bmp", "webp"].includes(ext)) {
if (
(search != "" && contains(file.name.toLowerCase(), conditions)) ||
search == ""
) {
index++;
added++;
filecontainer.append(
create_images_button(channelID, folder.path, file.name, ext)
);
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
}
if (filecontainer.children.length == 0) {
hide_container_button.remove();
filecontainer.remove();
}
if (added > 11 || emergency_load == true) {
break;
} else {
offset = 0;
continue;
}
}
}
function create_container(name, length) {
//Button to hide container
const hide_container_button = document.createElement("div");
hide_container_button.className =
"header-1XpmZs interactive-MpGq2z imagesender_hide_container";
hide_container_button.id = `${name}-hide`;
const text = document.createElement("span");
text.className = "headerLabel-1g790w";
text.textContent = `${name} (${length})`;
hide_container_button.infos_length = length;
const svg = DOMTools.createElement(
``
);
const filecontainer = document.createElement("div");
filecontainer.id = name;
filecontainer.className = "imagesender_filecontainer";
hide_container_button.append(text, svg);
let body = document.querySelector("#imagesender_body")
hide_container_button.addEventListener("click", () => {
if (filecontainer.style.display == "none") {
svg.classList.remove("headerCollapseIconCollapsed-3C20LE");
filecontainer.style.display = "grid";
hidden[name] = {}
hidden[name].isHidden = false;
generateimages(
body,
document
.querySelector("#imagesender_searchBar")
.querySelector("input").value,
body.querySelectorAll(".imagesender_buttons").length
);
} else {
svg.classList.add("headerCollapseIconCollapsed-3C20LE");
filecontainer.style.display = "none";
hidden[name] = {}
hidden[name].isHidden = true;
generateimages(
body,
document
.querySelector("#imagesender_searchBar")
.querySelector("input").value,
body.querySelectorAll(".imagesender_buttons").length
);
}
});
return { hide_container_button, filecontainer_temp: filecontainer };
}
async function create_UI(open) {
await delay(200);
if (open || document.querySelector("#imagesender-picker-tab")) {
await delay(100);
document.querySelector("#imagesender-picker-tab").click();
return;
}
const emoji_picker = document.querySelector("button[id=emoji-picker-tab]");
const pickers = emoji_picker.parentElement;
const imagesender_picker = emoji_picker.cloneNode(true);
imagesender_picker.firstChild.textContent = "ImageSender";
imagesender_picker.id = "imagesender-picker-tab";
//Deselects the button if you click on another button in the list
pickers.childNodes.forEach((node) => {
node.addEventListener("click", navbar_button_compatibility);
});
remove_selected(imagesender_picker);
imagesender_picker.addEventListener("click", () => {
//Picker element
const full_UI = pickers.parentElement.parentElement;
//Actual opened panel (emoji for exemple)
const panel = full_UI.lastChild;
panel.style.display = "none";
if (document.querySelector("#imagesender-picker-tab-panel")) {
document.querySelector("#imagesender-picker-tab-panel").style.display =
null;
return;
}
const actually_selected = document.querySelector(
"button[class*=navButtonActive]"
);
const navButtonActive_class = Array.from(actually_selected.classList)
.filter((name) => name.includes("navButtonActive"))
.toString();
//Deselect selected
remove_selected(actually_selected);
//Select ImageSender
imagesender_picker.className =
imagesender_picker.className + " " + navButtonActive_class;
//Prepare search bar
const imagesender_searchBar = panel
.querySelector("div[class*=searchBar-]")
.cloneNode(true);
imagesender_searchBar.id = "imagesender_searchBar";
//Ui reconstruction
const panel_content = document.createElement("div");
panel_content.id = "imagesender-picker-tab-panel";
panel_content.setAttribute("class", "container-3u7RcY");
const header = document.createElement("div");
header.className = "header-2TLOnc";
header.append(imagesender_searchBar);
const body = document.createElement("div");
body.id = "imagesender_body";
body.className =
"scroller-2MALzE list-3V14yy thin-RnSY0a scrollerBase-1Pkza4";
body.addEventListener("scroll", image_swipe_load);
panel_content.append(header, body);
full_UI.append(panel_content);
//Search bar modifications
const searchbar_input = imagesender_searchBar.querySelector("input");
searchbar_input.className = "input-2m5SfJ";
searchbar_input.placeholder = "Search for images";
searchbar_input.addEventListener("change", (e) => {
generateimages(body, e.target.value);
});
generateimages(body);
});
pickers.append(imagesender_picker);
}
////////////////////////////////////
///// /////
///// BD functions /////
///// /////
////////////////////////////////////
return {
start: () => {
_settings = Object.assign(
{},
settings_default,
BdApi.loadData(meta.name, "settings")
);
Object.assign(settings, _settings);
BdApi.injectCSS(
meta.name,
`
.container {
display: grid;
grid-template-columns: 30% 60% 10%;
grid-gap: 2%;
margin-right: 2%
}
.container > button {
align-self: center;
justify-self: center;
width: 40px;
height: 40px;
}
#ImageSender_UI_Button {
all: unset;
background: url("https://raw.githubusercontent.com/CriosChan/ImageSender/main/logo.png");
background-size: cover;
background-position: center;
opacity: 70%;
height: 24px;
width: 24px;
cursor: pointer;
justify-self: center;
align-self: center;
margin: 5px;
margin-right: 10px;
}
#ImageSender_UI_Button:hover {
opacity: 100%;
}
#imagesender_body {
display: block;
margin-right: 10px;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
.imagesender_filecontainer {
display: grid;
grid-template-columns: 50% 50%;
margin-right: 10px;
height: fit-content;
}
#imagesender_message {
display: grid;
width: 100%;
height: 90%;
margin: 20px;
text-align: center;
align-self: center;
justify-self: center;
}
#imagesender_message div {
justify-self: center;
width: 100%;
height: 100%
}
.imagesender_buttons {
width: 95%;
height: 200px;
justify-self: center;
margin: 10px;
background-size: contain;
background-color: initial;
background-repeat: no-repeat;
background-position: center;
}
.imagesender_hide_container {
margin: 10px;
height: fit-content;
}
`
);
check_folders();
try {
add_button();
} catch (error) {
console.log("Isn't in a channel");
}
},
stop: () => {
//Delete all textarea buttons in case of multiple
document.querySelectorAll("#ImageSender_UI_Button").forEach((element) => {
element.remove();
});
try {
//Full delete UI and scripts
const tab_button = document.getElementById("imagesender-picker-tab");
if (!tab_button) return;
if (!document.getElementById("imagesender-picker-tab-panel"))
tab_button.remove();
const picker = document.getElementById(
"imagesender-picker-tab-panel"
).parentElement;
if (tab_button.className.includes("navButtonActive")) {
const navButtonActive = Array.from(tab_button.classList)
.filter((name) => name.includes("navButtonActive"))
.toString();
document.getElementById("imagesender-picker-tab-panel").remove();
picker.lastChild.style.display = null;
//Re "enable" nav real button
document.getElementById(
picker.lastChild.id.replace("-panel", "")
).className += " " + navButtonActive;
tab_button.remove();
}
//Path to navbar, remove event
picker.firstChild.firstChild.childNodes.forEach((element) => {
element.removeEventListener("click", navbar_button_compatibility);
});
} catch {}
BdApi.clearCSS(meta.name);
loaded_folders = [];
document
.querySelectorAll(".expression-picker-chat-input-button")
.forEach((node) => {
node.removeEventListener("click", reactions_picker_event);
});
},
onSwitch: () => {
//Add button to TextArea
add_button();
},
getSettingsPanel: () => {
const settingsPanel = document.createElement("div");
settingsPanel.id = "ImageSender_settingsPanel";
createSettings(settingsPanel);
return settingsPanel;
},
};
};