//META{"name":"FriendNotifications","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/FriendNotifications","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/FriendNotifications/FriendNotifications.plugin.js"}*// class FriendNotifications { getName () {return "FriendNotifications";} getVersion () {return "1.2.6";} getAuthor () {return "DevilBro";} getDescription () {return "Notifies you when a Friend or a User your choose to observe changing his online status, can be configured individually in the settings.";} initConstructor () { this.changelog = { "improved":[["Notification Message","You can now customize the message depending on the status"]], "fixed":[["Mute Sound Bug","Fixed the bug where desktop notifications could not be muted"],["Settings Bug","Fixed the bug where disabling/changing settings for users would not work, unless the plugin was restarted"],["Log Bug","Fixed the bug where the time log would display weirdly"]] }; this.patchModules = { "FriendsOnline":"componentDidMount", "StandardSidebarView":"componentWillUnmount" }; this.userStatusStore = {}; this.checkInterval = null; this.timeLog = []; this.timeLogModalMarkup = `

Friends LogIn/-Out Timelog

`; this.logEntryMarkup = `

`; this.css = ` ${BDFDB.dotCN.guilds} > ${BDFDB.dotCN.friendsonline} { cursor: pointer; } .${this.name}-modal .log-time { width: 110px; } .${this.name}-modal .log-avatar { width: 35px; height: 35px; background-size: cover; background-position: center; border-radius: 50%; } .${this.name}-settings .type-toast, .${this.name}-settings .type-desktop { border-radius: 3px; padding: 0 3px; } .${this.name}-settings .type-toast { background-color: #7289DA; } .${this.name}-settings .type-desktop { background-color: #43B581; } .${this.name}-settings .settings-avatar.desktop { border-color: #43B581; } .${this.name}-settings .settings-avatar { margin: 5px; width: 35px; height: 35px; background-size: cover; background-position: center; border: 3px solid #7289DA; border-radius: 50%; box-sizing: border-box; cursor: pointer; } .${this.name}-settings .settings-avatar.desktop { border-color: #43B581; } .${this.name}-settings .settings-avatar.disabled { border-color: #36393F; filter: grayscale(100%) brightness(50%); } .${this.name}-settings .settings-avatar.disabled ~ * { filter: grayscale(100%) brightness(50%); }`; this.defaults = { settings: { muteOnDND: {value:false, description:"Do not notify me when I am DnD:"}, openOnClick: {value:false, description:"Open the DM when you click a Notification:"} }, notificationstrings: { online: {value:"$user changed status to '$status'", libstring:"STATUS_ONLINE", init:true}, mobile: {value:"$user changed status to '$status'", libstring:"STATUS_ONLINE_MOBILE", init:true}, idle: {value:"$user changed status to '$status'", libstring:"STATUS_IDLE", init:false}, dnd: {value:"$user changed status to '$status'", libstring:"STATUS_DND", init:false}, streaming: {value:"$user changed status to '$status'", libstring:"STATUS_STREAMING", init:false}, offline: {value:"$user changed status to '$status'", libstring:"STATUS_OFFLINE", init:true} }, notificationsounds: { toastonline: {value:{url:null,song:null,mute:false}}, toastmobile: {value:{url:null,song:null,mute:false}}, toastidle: {value:{url:null,song:null,mute:false}}, toastdnd: {value:{url:null,song:null,mute:false}}, toaststreaming: {value:{url:null,song:null,mute:false}}, toastoffline: {value:{url:null,song:null,mute:false}}, desktoponline: {value:{url:null,song:null,mute:false}}, desktopmobile: {value:{url:null,song:null,mute:false}}, desktopidle: {value:{url:null,song:null,mute:false}}, desktopdnd: {value:{url:null,song:null,mute:false}}, desktopstreaming: {value:{url:null,song:null,mute:false}}, desktopoffline: {value:{url:null,song:null,mute:false}} }, amounts: { checkInterval: {value:10, min:5, description:"Check Users every X seconds:"} } }; } getSettingsPanel () { if (!global.BDFDB || typeof BDFDB != "object" || !BDFDB.loaded || !this.started) return; let settings = BDFDB.getAllData(this, "settings"); let notificationstrings = BDFDB.getAllData(this, "notificationstrings"); let notificationsounds = BDFDB.getAllData(this, "notificationsounds"); let amounts = BDFDB.getAllData(this, "amounts"); let friendIDs = this.FriendUtils.getFriendIDs(); let friends = BDFDB.loadAllData(this, "friends"); let nonfriends = BDFDB.loadAllData(this, "nonfriends"); let settingshtml = `
${this.name}
`; settingshtml += `
General Settings
`; for (let key in settings) settingshtml += `

${this.defaults.settings[key].description}

`; for (let key in amounts) settingshtml += `

${this.defaults.amounts[key].description}

`; settingshtml += `
`; settingshtml += `
Friend-List
`; settingshtml += `

Click on an Icon to toggle Notifications for that User:

`; if ("Notification" in window) settingshtml += `

Rightclick on an Icon to toggle Notifications for that User:

`; settingshtml += `

Click/Rightclick on the table headers to batch set all Friends

`; settingshtml += `

TYPE
DISABLE

`; for (let config in this.defaults.notificationstrings) settingshtml += `
${config.toUpperCase()}
`; settingshtml += `
`; for (let id of friendIDs) { let user = this.UserUtils.getUser(id); if (user) { let friend = friends[id] || (friends[id] = nonfriends[id] || this.createDefaultConfig()); settingshtml += this.createHoverCard(user, friend, "friends"); } } settingshtml += `
`; settingshtml += `
`; settingshtml += `
Non-Friend-List
`; settingshtml += `

Click on a Icon to toggle Notifications for that User:

`; if ("Notification" in window) settingshtml += `

Rightclick on a Icon to toggle Notifications for that User:

`; settingshtml += `

Click/Rightclick on the table headers to batch set all Non-Friends

`; settingshtml += `

Add Non-Friend:

`; settingshtml += `

TYPE
DISABLE

`; for (let config in this.defaults.notificationstrings) settingshtml += `
${config.toUpperCase()}
`; settingshtml += `
`; for (let id in nonfriends) if (!friendIDs.includes(id)) { let user = this.UserUtils.getUser(id); if (user) { let nonfriend = nonfriends[id] || (nonfriends[id] = this.createDefaultConfig()); settingshtml += this.createHoverCard(user, nonfriend, "nonfriends"); } } settingshtml += `
`; settingshtml += `
`; settingshtml += `
Timelog
`; settingshtml += `

Timelog of LogIns/-Outs:

`; settingshtml += `
`; settingshtml += `
Notification Message Settings
`; settingshtml += `

Allows you to configure your own message strings for the different statuses. $user is the placeholder for the username and $status for the statusname.

`; for (let config in notificationstrings) { settingshtml += `

${config.charAt(0).toUpperCase() + config.slice(1)} Message:

`; } settingshtml += `
`; settingshtml += `
Notification Sound Settings
`; for (let config in notificationsounds) if (config.indexOf("desktop") == -1 || "Notification" in window) settingshtml += `
${config} notification sound:
Mute:
`; settingshtml += `
`; settingshtml += `
`; BDFDB.saveAllData(friends, this, "friends"); let settingspanel = BDFDB.htmlToElement(settingshtml); BDFDB.initElements(settingspanel, this); BDFDB.addEventListener(this, settingspanel, "keyup", ".input-notificationstring", e => {this.saveNotificationString(e.currentTarget);}); BDFDB.addEventListener(this, settingspanel, "click", ".btn-savesong", e => {this.saveNotificationSound(e.currentTarget.parentElement.querySelector(BDFDB.dotCN.input));}); BDFDB.addEventListener(this, settingspanel, "click", ".mute-checkbox", e => { let config = e.currentTarget.getAttribute("config"); if (config) { let notificationsound = BDFDB.getData(config, this, "notificationsounds"); notificationsound.mute = e.currentTarget.checked; BDFDB.saveData(config, notificationsound, this, "notificationsounds"); } }); BDFDB.addEventListener(this, settingspanel, "click", ".settings-avatar", e => { this.changeNotificationType(e.currentTarget, false, !BDFDB.containsClass(e.currentTarget, "disabled", "desktop", false)); }); BDFDB.addEventListener(this, settingspanel, "contextmenu", ".settings-avatar", e => { if (!("Notification" in window)) return; this.changeNotificationType(e.currentTarget, true, !(BDFDB.containsClass(e.currentTarget, "disabled") || !BDFDB.containsClass(e.currentTarget, "desktop"))); }); BDFDB.addEventListener(this, settingspanel, "click", ".btn-batch", e => { this.changeAllNotificationTypes(settingspanel, e.currentTarget, true); }); BDFDB.addEventListener(this, settingspanel, "contextmenu", ".btn-batch", e => { this.changeAllNotificationTypes(settingspanel, e.currentTarget, false); }); BDFDB.addEventListener(this, settingspanel, "click", BDFDB.dotCN.checkboxinput, e => { if (BDFDB.containsClass(e.target, "remove-user")) return; this.changeNotificationConfig(e.currentTarget); }); BDFDB.addEventListener(this, settingspanel, "click", ".BDFDB-tableheadercolumn", e => { this.changeAllNotificationConfigs(settingspanel, e.currentTarget, true); }); BDFDB.addEventListener(this, settingspanel, "contextmenu", ".BDFDB-tableheadercolumn", e => { this.changeAllNotificationConfigs(settingspanel, e.currentTarget, false); }); BDFDB.addEventListener(this, settingspanel, "click", ".remove-user", e => { let id = e.currentTarget.getAttribute("user-id"); let group = e.currentTarget.getAttribute("group"); if (id && group) { BDFDB.removeData(id, this, group); BDFDB.removeEles(BDFDB.getParentEle(BDFDB.dotCN.hovercard, e.currentTarget)); this.SettingsUpdated = true; } }); BDFDB.addEventListener(this, settingspanel, "click", ".btn-adduser", e => { let idinput = settingspanel.querySelector("#input-userid"); let id = idinput.value; idinput.value = ""; if (friendIDs.includes(id)) BDFDB.showToast("User is already a friend of yours. Please use the 'Friends' area to configure him/her.", {type:"error"}); else if (BDFDB.loadData(id, this, "nonfriends")) BDFDB.showToast("User is already being observed as a 'Non-Friend'.", {type:"error"}); else { let user = this.UserUtils.getUser(id); if (user) { let data = this.createDefaultConfig(); BDFDB.saveData(user.id, data, this, "nonfriends"); let hovercard = BDFDB.htmlToElement(this.createHoverCard(user, data, "nonfriends")); settingspanel.querySelector(".nonfriend-list").appendChild(hovercard); BDFDB.initElements(hovercard); this.SettingsUpdated = true; } else if (/.+#[0-9]{4}/.test(id)) BDFDB.showToast("A UserID does not consist of the username and discriminator.", {type:"error"}); else BDFDB.showToast("Please enter a valid UserID of a user that has been loaded in your client.", {type:"error"}); } }); BDFDB.addEventListener(this, settingspanel, "click", ".btn-timelog", () => {this.showTimeLog();}); return settingspanel; } //legacy load () {} start () { if (!global.BDFDB) global.BDFDB = {myPlugins:{}}; if (global.BDFDB && global.BDFDB.myPlugins && typeof global.BDFDB.myPlugins == "object") global.BDFDB.myPlugins[this.getName()] = this; var libraryScript = document.querySelector('head script#BDFDBLibraryScript'); if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { if (libraryScript) libraryScript.remove(); libraryScript = document.createElement("script"); libraryScript.setAttribute("id", "BDFDBLibraryScript"); libraryScript.setAttribute("type", "text/javascript"); libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.js"); libraryScript.setAttribute("date", performance.now()); libraryScript.addEventListener("load", () => {this.initialize();}); document.head.appendChild(libraryScript); this.libLoadTimeout = setTimeout(() => { libraryScript.remove(); require("request")("https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.js", (error, response, body) => { if (body) { libraryScript = document.createElement("script"); libraryScript.setAttribute("id", "BDFDBLibraryScript"); libraryScript.setAttribute("type", "text/javascript"); libraryScript.setAttribute("date", performance.now()); libraryScript.innerText = body; document.head.appendChild(libraryScript); } this.initialize(); }); }, 15000); } else if (global.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); this.startTimeout = setTimeout(() => {this.initialize();}, 30000); } initialize () { if (global.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { if (this.started) return; BDFDB.loadMessage(this); this.FriendUtils = BDFDB.WebModules.findByProperties("getFriendIDs", "getRelationships"); this.ChannelUtils = BDFDB.WebModules.findByProperties("getDMFromUserId"); this.ChannelSwitchUtils = BDFDB.WebModules.findByProperties("selectPrivateChannel"); this.PrivateChannelUtils = BDFDB.WebModules.findByProperties("openPrivateChannel"); this.MobileUtils = BDFDB.WebModules.findByProperties("isMobileOnline"); this.UserUtils = BDFDB.WebModules.findByProperties("getUsers", "getUsers"); this.APIUtils = BDFDB.WebModules.findByProperties("getAPIBaseURL"); this.DiscordConstants = BDFDB.WebModules.findByProperties("Permissions", "ActivityTypes", "StatusTypes"); /* REMOVE AFTER SOME TIME - 22.08.2019 */ let oldFriendDataDesktop = BDFDB.loadAllData("FriendNotifications", "desktop"); let oldFriendDataDisabled = BDFDB.loadAllData("FriendNotifications", "disabled"); if (!BDFDB.isObjectEmpty(oldFriendDataDesktop) || !BDFDB.isObjectEmpty(oldFriendDataDisabled)) { let friends = BDFDB.loadAllData(this, "friends") for (let id in oldFriendDataDesktop) friends[id] = Object.assign(this.createDefaultConfig(), (friends[id] || {}), {desktop: oldFriendDataDesktop[id]}); for (let id in oldFriendDataDisabled) friends[id] = Object.assign(this.createDefaultConfig(), (friends[id] || {}), {disabled: oldFriendDataDesktop[id]}); BDFDB.saveAllData(friends, this, "friends"); BDFDB.removeAllData("FriendNotifications", "desktop"); BDFDB.removeAllData("FriendNotifications", "disabled"); } let oldStalkerData = BDFDB.loadAllData("StalkerNotifications", "users"); if (!BDFDB.isObjectEmpty(oldStalkerData)) { let nonfriends = BDFDB.loadAllData(this, "nonfriends") for (let id in oldStalkerData) nonfriends[id] = Object.assign(this.createDefaultConfig(), oldStalkerData[id]); BDFDB.saveAllData(nonfriends, this, "nonfriends"); require("fs").unlinkSync(require("path").join(BDFDB.getPluginsFolder(), "StalkerNotifications.config.json")); } this.startInterval(); BDFDB.WebModules.forceAllUpdates(this); } else { console.error(`%c[${this.getName()}]%c`, 'color: #3a71c1; font-weight: 700;', '', 'Fatal Error: Could not load BD functions!'); } } stop () { if (global.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { clearInterval(this.checkInterval); BDFDB.unloadMessage(this); } } // begin of own functions createHoverCard (user, data, group) { let EUdata = BDFDB.loadData(user.id, "EditUsers", "users") || {}; var hovercardhtml = `
${BDFDB.encodeToHTML(EUdata.name || user.username)}
`; for (let config in this.defaults.notificationstrings) { hovercardhtml += `
`; } return hovercardhtml + `
${group == "nonfriends" ? `
` : ''}
` } changeNotificationType (avatar, desktopon, disableon) { let id = avatar.getAttribute("user-id"); let group = avatar.getAttribute("group"); if (id && group) { let data = BDFDB.loadData(id, this, group) || this.createDefaultConfig(); data.desktop = desktopon; data.disabled = disableon; BDFDB.toggleClass(avatar, "desktop", desktopon); BDFDB.toggleClass(avatar, "disabled", disableon); BDFDB.saveData(id, data, this, group); this.SettingsUpdated = true; } } changeAllNotificationTypes (settingspanel, tableheader, enable) { let config = tableheader.getAttribute("config"); let group = tableheader.getAttribute("group"); if (config && group) { let data = BDFDB.loadAllData(this, group); if (config == "desktop") { enable = !enable; for (let id in data) data[id].disabled = false; for (let avatar of settingspanel.querySelectorAll(`.settings-avatar[group="${group}"]`)) BDFDB.removeClass(avatar, "disabled"); } for (let id in data) data[id][config] = enable; for (let avatar of settingspanel.querySelectorAll(`.settings-avatar[group="${group}"]`)) BDFDB.toggleClass(avatar, config, enable); BDFDB.saveAllData(data, this, group); this.SettingsUpdated = true; } } changeNotificationConfig (checkbox) { let id = checkbox.getAttribute("user-id"); let config = checkbox.getAttribute("config"); let group = checkbox.getAttribute("group"); if (id && config && group) { let data = BDFDB.loadData(id, this, group) || this.createDefaultConfig(); data[config] = checkbox.checked; BDFDB.saveData(id, data, this, group); this.SettingsUpdated = true; } } changeAllNotificationConfigs (settingspanel, tableheader, enable) { let config = tableheader.getAttribute("config"); let group = tableheader.getAttribute("group"); if (config && group) { let data = BDFDB.loadAllData(this, group); for (let id in data) data[id][config] = enable; BDFDB.saveAllData(data, this, group); for (let checkbox of settingspanel.querySelectorAll(`${BDFDB.dotCN.checkboxinput}[config="${config}"][group="${group}"]`)) { checkbox.checked = enable; if (typeof checkbox.BDFDBupdateElement == "function") checkbox.BDFDBupdateElement(); } this.SettingsUpdated = true; } } createDefaultConfig () { return Object.assign({desktop: false, disabled: false}, BDFDB.mapObject(this.defaults.notificationstrings, "init")); } saveNotificationString (input) { let config = input.getAttribute("config"); if (config) { BDFDB.saveData(config, input.value, this, "notificationstrings"); this.SettingsUpdated = true; } } saveNotificationSound (input) { let config = input.getAttribute("config"); if (config) { let successSavedAudio = (parsedurl, parseddata) => { if (parsedurl && parseddata) BDFDB.showToast(`Sound was saved successfully.`, {type:"success"}); let notificationsound = BDFDB.getData(config, this, "notificationsounds"); notificationsound.url = parsedurl; notificationsound.song = parseddata; BDFDB.saveData(config, notificationsound, this, "notificationsounds"); this.SettingsUpdated = true; }; let url = input.value; if (url.length == 0) { BDFDB.showToast(`Sound file was removed.`, {type:"warn"}); successSavedAudio(url, url); } else if (url.indexOf("http") == 0) { require("request")(url, (error, response, result) => { if (response) { let type = response.headers["content-type"]; if (type && (type.indexOf("octet-stream") > -1 || type.indexOf("audio") > -1 || type.indexOf("video") > -1)) { successSavedAudio(url, url); return; } } BDFDB.showToast("Use a valid direct link to a video or audio source. They usually end on something like .mp3, .mp4 or .wav.", {type:"danger"}); }); } else { require("fs").readFile(url, (error, response) => { if (error) BDFDB.showToast("Could not fetch file. Please make sure the file exists.", {type:"danger"}); else successSavedAudio(url, `data:audio/mpeg;base64,${response.toString("base64")}`); }); } } } processFriendsOnline (instance, wrapper) { BDFDB.addEventListener(this, wrapper, "mouseenter", () => {BDFDB.createTooltip("Timelog", wrapper, {type:"right"});}); BDFDB.addEventListener(this, wrapper, "click", () => {this.showTimeLog();}); } processStandardSidebarView (instance, wrapper) { if (this.SettingsUpdated) { delete this.SettingsUpdated; this.startInterval(); } } getStatusWithMobile (id) { let status = BDFDB.getUserStatus(id); return status == "online" && this.MobileUtils.isMobileOnline(id) ? "mobile" : status; } startInterval () { clearInterval(this.checkInterval); let settings = BDFDB.getAllData(this, "settings"); let notificationstrings = BDFDB.getAllData(this, "notificationstrings"); let notificationsounds = BDFDB.getAllData(this, "notificationsounds"); let users = Object.assign(BDFDB.loadAllData(this, "nonfriends"), BDFDB.loadAllData(this, "friends")); for (let id in users) this.userStatusStore[id] = this.getStatusWithMobile(id); this.checkInterval = setInterval(() => { for (let id in users) if (!users[id].disabled) { let user = this.UserUtils.getUser(id); let status = this.getStatusWithMobile(id); if (user && this.userStatusStore[id] != status && users[id][status]) { let EUdata = BDFDB.loadData(user.id, "EditUsers", "users") || {}; let libstring = (BDFDB.LanguageStrings[this.defaults.notificationstrings[status].libstring] || "").toLowerCase(); let string = notificationstrings[status] || "$user changed status to $status"; let toaststring = BDFDB.encodeToHTML(string).replace(/\$user/g, `${BDFDB.encodeToHTML(EUdata.name || user.username)}`).replace(/\$status/g, `${libstring}`); let avatar = EUdata.removeIcon ? "" : (EUdata.url ? EUdata.url : BDFDB.getUserAvatar(user.id)); this.timeLog.push({string:toaststring, avatar, time: new Date()}); if (!(settings.muteOnDND && BDFDB.getUserStatus() == "dnd")) { let openChannel = () => { if (settings.openOnClick) { let DMid = this.ChannelUtils.getDMFromUserId(user.id) if (DMid) this.ChannelSwitchUtils.selectPrivateChannel(DMid); else this.PrivateChannelUtils.openPrivateChannel(BDFDB.myData.id, user.id); require("electron").remote.getCurrentWindow().maximize(); } }; if (!users[id].desktop) { let toast = BDFDB.showToast(`
${toaststring}
`, {html:true, timeout:5000, color:BDFDB.getUserStatusColor(status), icon:false, selector:`friendnotifications-${status}-toast`}); toast.addEventListener("click", openChannel); let notificationsound = notificationsounds["toast" + status] || {}; if (!notificationsound.mute && notificationsound.song) { let audio = new Audio(); audio.src = notificationsound.song; audio.play(); } } else { let desktopstring = string.replace(/\$user/g, EUdata.name || user.username).replace(/\$status/g, libstring); let notificationsound = notificationsounds["desktop" + status] || {}; BDFDB.showDesktopNotification(desktopstring, {icon:avatar, timeout:5000, click:openChannel, silent:notificationsound.mute, sound:notificationsound.song}); } } } this.userStatusStore[id] = status; } },BDFDB.getData("checkInterval", this, "amounts") * 1000); } showTimeLog () { let timeLogModal = BDFDB.htmlToElement(this.timeLogModalMarkup); let container = timeLogModal.querySelector(".entries"); if (!container) return; let logs = this.timeLog.slice(0).reverse(); for (let log of logs) { if (container.childElementCount) container.appendChild(BDFDB.htmlToElement(`
`)); let entry = BDFDB.htmlToElement(this.logEntryMarkup); entry.querySelector(".log-time").innerText = `[${log.time.toLocaleTimeString()}]`; entry.querySelector(".log-avatar").style.setProperty("background-image", `url(${log.avatar})`); entry.querySelector(".log-description").innerHTML = log.string; container.appendChild(entry) } BDFDB.appendModal(timeLogModal); } }