experimental tabs
This commit is contained in:
parent
1676ebf62b
commit
27383e1bc1
File diff suppressed because one or more lines are too long
|
@ -1,443 +1,442 @@
|
|||
import nodeFetch from "node-fetch"
|
||||
import * as electron from "electron"
|
||||
import * as crypto from "crypto"
|
||||
import BDV2 from "./v2"
|
||||
import tooltipWrap from "../ui/tooltipWrap"
|
||||
import Utils from "./utils"
|
||||
import { createReadStream, writeFileSync } from "fs"
|
||||
import { basename, join } from "path"
|
||||
import contentManager from "./contentManager"
|
||||
import { addonCache } from "./contentManager"
|
||||
|
||||
const cache = {}
|
||||
const cache2 = {}
|
||||
|
||||
export default new class PluginCertifier {
|
||||
constructor(){
|
||||
window.Lightcord.BetterDiscord.PluginCertifier = this
|
||||
}
|
||||
|
||||
patch(attachment, id){
|
||||
process.nextTick(() => {
|
||||
processAttachment(attachment, id)
|
||||
})
|
||||
}
|
||||
|
||||
start(){
|
||||
|
||||
}
|
||||
|
||||
isTrusted(hash){
|
||||
return cache[hash] && !cache[hash].suspect
|
||||
}
|
||||
}
|
||||
|
||||
export function checkViruses(hash, data, resultCallback, removeCallback, filename){
|
||||
data = data.toString("utf8")
|
||||
let isHarmful = false
|
||||
for(let keyword of data.split(/[^\w\d]+/g)){
|
||||
for(let oof of [
|
||||
"token",
|
||||
"email",
|
||||
"phone",
|
||||
"MFA",
|
||||
"2fa",
|
||||
"process",
|
||||
"child_process",
|
||||
"localStorage",
|
||||
"eval",
|
||||
"getGlobal",
|
||||
"BrowserWindow"
|
||||
]){
|
||||
if(keyword.toLowerCase().includes(oof.toLowerCase()) && !keyword.toLowerCase() === "domtokenlist"){
|
||||
console.log(oof, keyword)
|
||||
isHarmful = "token stealer/virus"
|
||||
break
|
||||
}
|
||||
}
|
||||
if(isHarmful)break
|
||||
}
|
||||
|
||||
if(!isHarmful){
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
const no_comments = data.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "").trim()// removing the META comment from plugins
|
||||
if((/var [\w\d_$]+=\["/gi).test(no_comments)){
|
||||
isHarmful = "obfuscation/hidden code"
|
||||
}
|
||||
|
||||
if(!isHarmful){
|
||||
const regexps = [
|
||||
/** hexadecimal */
|
||||
/_0x\w{4}\('0x[\dabcdef]+'\)/g,
|
||||
/_0x\w{4}\('0x[\dabcdef]+'[, ]+'[^']{4}'\)/g, // _0x8db7('0x0', 'x1]f')
|
||||
/** mangled */
|
||||
/\w+\('0x[\dabcdef]+'\)/g, // b('0x0')
|
||||
/\w+\('0x[\dabcdef]+'[, ]+'[^']{4}'\)/g, // b('0x0', 'x1]f')
|
||||
]
|
||||
for(let regex of regexps){
|
||||
if(isHarmful)break
|
||||
isHarmful = regex.test(no_comments) ? "obfuscation/hidden code" : false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!isHarmful)return removeCallback(hash)
|
||||
cache[hash] = {
|
||||
suspect: true,
|
||||
name: hashToUrl[hash].split("/").pop(),
|
||||
type: hashToUrl[hash].endsWith(".js") ? "Plugin" : "Theme",
|
||||
harm: isHarmful,
|
||||
hash: hash,
|
||||
filename
|
||||
}
|
||||
|
||||
console.log(`Found potentially dangerous ${cache[hash].type.toLowerCase()}: ${cache[hash].name}`)
|
||||
|
||||
resultCallback(cache[hash])
|
||||
}
|
||||
|
||||
const hashToUrl = {}
|
||||
|
||||
export function checkHash(hash, data, filename, resultCallback, removeCallback){
|
||||
console.log(`File: ${filename} hash: ${hash}`)
|
||||
if(!cache[hash]){
|
||||
nodeFetch("https://cdn.jsdelivr.net/gh/Lightcord/filehashes@master/hashes/"+hash, { // Using node-fetch to bypass cors
|
||||
headers: {
|
||||
"User-Agent": electron.remote.getCurrentWebContents().userAgent // have to set user-agent
|
||||
}
|
||||
}).then(async res => {
|
||||
if(res.status !== 200){
|
||||
if(filename.endsWith(".theme.css"))return removeCallback(hash)
|
||||
checkViruses(hash, data, resultCallback, removeCallback, filename)
|
||||
return
|
||||
}
|
||||
const result = await res.json()
|
||||
result.hash = hash
|
||||
result.filename = filename
|
||||
|
||||
cache[hash] = result
|
||||
|
||||
resultCallback(result)
|
||||
}).catch(console.error)
|
||||
}else{
|
||||
const result = cache[hash]
|
||||
|
||||
resultCallback(result)
|
||||
}
|
||||
}
|
||||
|
||||
export function processFile(__path, resultCallback, removeCallback = (hash) => {}, isFromLoader = false){
|
||||
const hash = crypto.createHash("sha256")
|
||||
let data = Buffer.alloc(0)
|
||||
|
||||
createReadStream(__path).on("data", chunk => {
|
||||
data = Buffer.concat([data, chunk])
|
||||
hash.update(chunk)
|
||||
}).on("end", () => {
|
||||
const hashResult = hash.digest("hex")
|
||||
|
||||
hashToUrl[hashResult] = __path
|
||||
|
||||
if(isFromLoader && addonCache[hashResult]){
|
||||
let value = addonCache[hashResult]
|
||||
if(value.timestamp < (Date.now() - 6.048e+8)){
|
||||
delete addonCache[hashResult]
|
||||
contentManager.saveAddonCache()
|
||||
}else{
|
||||
resultCallback(value.result)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
checkHash(hashResult, data, basename(__path), resultCallback, removeCallback)
|
||||
})
|
||||
}
|
||||
|
||||
export function processAttachment(attachment, id){
|
||||
if(!document.getElementById(id))return
|
||||
if(!attachment.url.startsWith("https://cdn.discordapp.com/"))return document.getElementById(id).remove()
|
||||
if(!attachment.filename.endsWith(".plugin.js") && !attachment.filename.endsWith(".theme.css"))return document.getElementById(id).remove()
|
||||
|
||||
nodeFetch(attachment.url, {
|
||||
headers: {
|
||||
"User-Agent": electron.remote.getCurrentWebContents().userAgent
|
||||
}
|
||||
}).then(res => {
|
||||
if(res.status !== 200)throw new Error("File doesn't exist.")
|
||||
const hash = crypto.createHash("sha256")
|
||||
let data = Buffer.alloc(0)
|
||||
res.body.on("data", chunk => {
|
||||
data = Buffer.concat([data, chunk])
|
||||
hash.update(chunk)
|
||||
})
|
||||
res.body.on("end", () => {
|
||||
const hashResult = hash.digest("hex")
|
||||
|
||||
cache2[attachment.url] = hashResult
|
||||
hashToUrl[hashResult] = attachment.url
|
||||
|
||||
checkHash(hashResult, data, attachment.filename, (result) => {
|
||||
renderToElements(id, result, attachment.filename)
|
||||
}, () => {
|
||||
let elem = document.getElementById(id)
|
||||
if(elem)elem.remove()
|
||||
})
|
||||
})
|
||||
}).catch(()=>{})
|
||||
}
|
||||
|
||||
let flowerStarModule = BDModules.get(e => e.flowerStarContainer)[0]
|
||||
let childModule = BDModules.get(e => e.childContainer)[0]
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLDivElement[]} elements
|
||||
* @param {{type: "Theme"|"Plugin", name: string, official?: boolean}|{suspect:true, type: "Theme"|"Plugin", name: string, harm: string}} result
|
||||
*/
|
||||
function renderToElements(id, result, filename){
|
||||
const div = document.getElementById(id)
|
||||
if(!div || div.childNodes.length > 0)return // already certified/div does not exist anymore.
|
||||
|
||||
if(!flowerStarModule)flowerStarModule = BDModules.get(e => e.flowerStarContainer)[0]
|
||||
if(!childModule)childModule = BDModules.get(e => e.childContainer)[0]
|
||||
|
||||
if(result.suspect){
|
||||
try{
|
||||
div.parentNode.style.borderColor = "rgb(240, 71, 71)"
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} node
|
||||
*/
|
||||
let nextNode = (node) => {
|
||||
for(let child of node.children){
|
||||
if(child.tagName === "A"){
|
||||
child.addEventListener("click", (e) => {
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
|
||||
Utils.showConfirmationModal(
|
||||
"Are you sure you want to download this ?",
|
||||
"The "+result.type.toLowerCase()+" **"+filename+"** might be dangerous **("+result.harm+")**. \n\n**We don't recommand to download it**. However, you can still do it below.",
|
||||
{
|
||||
confirmText: "Download Anyway",
|
||||
cancelText: "Don't !",
|
||||
danger: true,
|
||||
onCancel: () => {},
|
||||
onConfirm: () => {
|
||||
electron.remote.shell.openExternal(child.href)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}else if(["div"].includes(child.tagName.toLowerCase())){
|
||||
nextNode(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
nextNode(div.parentNode)
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
}
|
||||
BDV2.reactDom.render(BDV2.react.createElement(tooltipWrap, {text: result.type+" "+result.name+" is potentially dangerous."},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px"}},
|
||||
BDV2.react.createElement("svg", {className: BDModules.get(e => e.svg)[0].svg, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 40 32"},
|
||||
BDV2.react.createElement("rect", {
|
||||
x:"0",
|
||||
y:"0",
|
||||
width:"32",
|
||||
height:"32",
|
||||
mask:"url(#svg-mask-avatar-status-round-32)",
|
||||
fill:"#f04747",
|
||||
mask:"url(#svg-mask-status-dnd)",
|
||||
className:BDModules.get(e => e.pointerEvents)[0].pointerEvents
|
||||
})
|
||||
)
|
||||
)
|
||||
), div)
|
||||
}else if(!result.official){
|
||||
div.parentNode.style.borderColor = "#4087ed"
|
||||
let span = BDV2.react.createElement("span", {style: {display: "inherit"}}, [
|
||||
BDV2.react.createElement(tooltipWrap, {text: result.type+" "+result.name+" is certified by Lightcord."},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px", float: "left"}},
|
||||
BDV2.react.createElement("svg", {className: flowerStarModule.flowerStar, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 16 15.2"},
|
||||
BDV2.react.createElement("path", {fill:"#4f545c", "fill-rule":"evenodd",d:"m16 7.6c0 .79-1.28 1.38-1.52 2.09s.44 2 0 2.59-1.84.35-2.46.8-.79 1.84-1.54 2.09-1.67-.8-2.47-.8-1.75 1-2.47.8-.92-1.64-1.54-2.09-2-.18-2.46-.8.23-1.84 0-2.59-1.54-1.3-1.54-2.09 1.28-1.38 1.52-2.09-.44-2 0-2.59 1.85-.35 2.48-.8.78-1.84 1.53-2.12 1.67.83 2.47.83 1.75-1 2.47-.8.91 1.64 1.53 2.09 2 .18 2.46.8-.23 1.84 0 2.59 1.54 1.3 1.54 2.09z"})
|
||||
),
|
||||
BDV2.react.createElement("div", {className: childModule.childContainer},
|
||||
BDV2.react.createElement("svg", {"aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 16 15.2"},
|
||||
BDV2.react.createElement("path", {fill:"#ffffff",d:"M7.4,11.17,4,8.62,5,7.26l2,1.53L10.64,4l1.36,1Z"})
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
BDV2.react.createElement(tooltipWrap, {text: "Install this "+result.type.toLowerCase()+" on Lightcord."},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px"}, onClick(){
|
||||
Utils.showConfirmationModal(
|
||||
"Are you sure you want to download this "+result.type.toLowerCase()+" ?",
|
||||
"Lightcord will automatically install and launch this "+result.type.toLowerCase()+". You don't have anything to do.",
|
||||
{
|
||||
confirmText: "Download and Install",
|
||||
cancelText: "I've changed my mind",
|
||||
danger: false,
|
||||
onCancel: () => {},
|
||||
onConfirm: () => {
|
||||
let link = getKeyedArray(cache2).find(e => e[1] === result.hash)[0]
|
||||
console.log(link)
|
||||
nodeFetch(link)
|
||||
.then(async res => {
|
||||
if(res.status !== 200)throw new Error("Status was not 200")
|
||||
let content = await res.buffer()
|
||||
let installPath = join(result.type === "Plugin" ? contentManager._pluginsFolder : contentManager._themesFolder, result.filename)
|
||||
console.log(installPath)
|
||||
writeFileSync(installPath, content)
|
||||
Utils.showToast(result.type+" succesfully installed.")
|
||||
}).catch(err => {
|
||||
err = err instanceof Error ? err : new Error(err)
|
||||
Utils.showToast(err.message, {
|
||||
type: "error"
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}},
|
||||
BDV2.react.createElement("svg", {className: flowerStarModule.flowerStar, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 24 24",style:{
|
||||
color: "rgb(67, 181, 129)",
|
||||
cursor: "pointer"
|
||||
}},
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h24v24H0z"></path>
|
||||
<path class="fill" fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path>
|
||||
</g>
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
BDV2.reactDom.render(span, div)
|
||||
}else{
|
||||
div.parentNode.style.borderColor = "#4087ed"
|
||||
let span = BDV2.react.createElement("span", {style: {display: "inherit"}}, [
|
||||
BDV2.react.createElement(tooltipWrap, {text: result.type+" "+result.name+" was made by the developers of Lightcord.", style:"brand"},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px", float: "left"}},
|
||||
BDV2.react.createElement("svg", {className: flowerStarModule.flowerStar, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 16 15.2",stroke:"#36393f",style:{color:"#4087ed"}},
|
||||
BDV2.react.createElement("path", {fill:"currentColor", "fill-rule":"evenodd",d:"m16 7.6c0 .79-1.28 1.38-1.52 2.09s.44 2 0 2.59-1.84.35-2.46.8-.79 1.84-1.54 2.09-1.67-.8-2.47-.8-1.75 1-2.47.8-.92-1.64-1.54-2.09-2-.18-2.46-.8.23-1.84 0-2.59-1.54-1.3-1.54-2.09 1.28-1.38 1.52-2.09-.44-2 0-2.59 1.85-.35 2.48-.8.78-1.84 1.53-2.12 1.67.83 2.47.83 1.75-1 2.47-.8.91 1.64 1.53 2.09 2 .18 2.46.8-.23 1.84 0 2.59 1.54 1.3 1.54 2.09z"})
|
||||
),
|
||||
BDV2.react.createElement("div", {className: childModule.childContainer},
|
||||
BDV2.react.createElement("svg", {"aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 16 15.2"},
|
||||
BDV2.react.createElement("path", {fill:"#ffffff",d:"M10.7,5.28a2.9,2.9,0,0,0-2.11.86.11.11,0,0,0,0,.16l1.05.94a.11.11,0,0,0,.15,0,1.27,1.27,0,0,1,.9-.33c.65,0,.65.73.65.73a.64.64,0,0,1-.65.65,1.73,1.73,0,0,1-1.18-.54c-.31-.26-.36-.32-.73-.66S7.06,5.28,5.65,5.28A2.26,2.26,0,0,0,3.37,7.56,2.59,2.59,0,0,0,3.82,9a2.18,2.18,0,0,0,1.83.89,2.94,2.94,0,0,0,2.1-.81.11.11,0,0,0,0-.16L6.74,8A.11.11,0,0,0,6.6,8a1.58,1.58,0,0,1-.94.29h0A.71.71,0,0,1,5,7.56H5a.63.63,0,0,1,.65-.64c.71,0,1.42.75,1.94,1.27.75.76,1.66,1.79,3.11,1.74A2.28,2.28,0,0,0,13,7.64a2.59,2.59,0,0,0-.45-1.47A2.14,2.14,0,0,0,10.7,5.28Z"})
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
BDV2.react.createElement(tooltipWrap, {text: "Install this "+result.type.toLowerCase()+" on Lightcord."},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px"}, onClick(){
|
||||
Utils.showConfirmationModal(
|
||||
"Are you sure you want to download this "+result.type.toLowerCase()+" ?",
|
||||
"Lightcord will automatically download and load this "+result.type.toLowerCase()+". You must enable it in the settings.",
|
||||
{
|
||||
confirmText: "Download and Install",
|
||||
cancelText: "I've changed my mind",
|
||||
danger: false,
|
||||
onCancel: () => {},
|
||||
onConfirm: () => {
|
||||
let link = getKeyedArray(cache2).find(e => e[1] === result.hash)[0]
|
||||
|
||||
nodeFetch(link)
|
||||
.then(async res => {
|
||||
if(res.status !== 200)throw new Error("Status was not 200")
|
||||
let content = await res.buffer()
|
||||
let installPath = join(result.type === "Plugin" ? contentManager._pluginsFolder : contentManager._themesFolder, result.filename)
|
||||
|
||||
writeFileSync(installPath, content)
|
||||
Utils.showToast(result.type+" succesfully installed.")
|
||||
}).catch(err => {
|
||||
err = err instanceof Error ? err : new Error(err)
|
||||
Utils.showToast(err.message, {
|
||||
type: "error"
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}},
|
||||
BDV2.react.createElement("svg", {className: flowerStarModule.flowerStar, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 24 24",style:{
|
||||
color: "rgb(67, 181, 129)",
|
||||
cursor: "pointer"
|
||||
}},
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h24v24H0z"></path>
|
||||
<path class="fill" fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path>
|
||||
</g>
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
BDV2.reactDom.render(span, div)
|
||||
}
|
||||
}
|
||||
|
||||
function getKeyedArray(obj){
|
||||
let arr = []
|
||||
Object.keys(obj).forEach(k => {
|
||||
arr.push([k, obj[k]])
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
let key = null
|
||||
let save = null
|
||||
|
||||
window.Lightcord.Api.ensureExported(m=>m.ObjectStorage)
|
||||
.then(localStorageModule => {
|
||||
let localStorage = localStorageModule.impl
|
||||
save = function(){
|
||||
localStorage.set("PluginCertifierKeyEncryption__", btoa(JSON.stringify(key)))
|
||||
}
|
||||
setInterval(() => {
|
||||
save()
|
||||
}, 100000);
|
||||
try{
|
||||
let val = safeJSONParse(atob(localStorage.get("PluginCertifierKeyEncryption__")))
|
||||
if(val instanceof Error || !Array.isArray(val) || val.length !== 2 || val.find(e => typeof e !== "string") || Buffer.from(val[0], "base64").length !== 16 || Buffer.from(val[1], "base64").length !== 32){
|
||||
generateKey()
|
||||
save()
|
||||
return
|
||||
}
|
||||
key = val
|
||||
}catch(e){
|
||||
generateKey()
|
||||
save()
|
||||
}
|
||||
})
|
||||
|
||||
function generateKey(){
|
||||
key = [crypto.randomBytes(16).toString("base64"), crypto.randomBytes(32).toString("base64")]
|
||||
}
|
||||
|
||||
function safeJSONParse(json){
|
||||
try{
|
||||
return JSON.parse(json)
|
||||
}catch(e){
|
||||
return e instanceof Error ? new Error(e) : e
|
||||
}
|
||||
}
|
||||
|
||||
export function decryptSettingsCache(data){
|
||||
try{
|
||||
let decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.from(key[1], "base64"), Buffer.from(key[0], "base64"))
|
||||
let decrypted = decipher.update(Buffer.from(data, "base64"));
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||
return decrypted.toString("utf8")
|
||||
}catch(e){
|
||||
return "{}"
|
||||
}
|
||||
}
|
||||
export function encryptSettingsCache(data){
|
||||
let args = [Buffer.from(key[1], "base64"), Buffer.from(key[0], "base64")]
|
||||
|
||||
let cipher = crypto.createCipheriv('aes-256-cbc', ...args);
|
||||
let encrypted = cipher.update(Buffer.from(data, "utf8"));
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
return encrypted.toString("base64")
|
||||
import nodeFetch from "node-fetch"
|
||||
import * as electron from "electron"
|
||||
import * as crypto from "crypto"
|
||||
import BDV2 from "./v2"
|
||||
import tooltipWrap from "../ui/tooltipWrap"
|
||||
import Utils from "./utils"
|
||||
import { createReadStream, writeFileSync } from "fs"
|
||||
import { basename, join } from "path"
|
||||
import contentManager from "./contentManager"
|
||||
import { addonCache } from "./contentManager"
|
||||
|
||||
const cache = {}
|
||||
const cache2 = {}
|
||||
|
||||
export default new class PluginCertifier {
|
||||
constructor(){
|
||||
window.Lightcord.BetterDiscord.PluginCertifier = this
|
||||
}
|
||||
|
||||
patch(attachment, id){
|
||||
process.nextTick(() => {
|
||||
processAttachment(attachment, id)
|
||||
})
|
||||
}
|
||||
|
||||
start(){
|
||||
|
||||
}
|
||||
|
||||
isTrusted(hash){
|
||||
return cache[hash] && !cache[hash].suspect
|
||||
}
|
||||
}
|
||||
|
||||
export function checkViruses(hash, data, resultCallback, removeCallback, filename){
|
||||
data = data.toString("utf8")
|
||||
let isHarmful = false
|
||||
for(let keyword of data.split(/[^\w\d]+/g)){
|
||||
for(let oof of [
|
||||
"token",
|
||||
"email",
|
||||
"phone",
|
||||
"MFA",
|
||||
"2fa",
|
||||
"child_process",
|
||||
"localStorage",
|
||||
"eval",
|
||||
"getGlobal",
|
||||
"BrowserWindow"
|
||||
]){
|
||||
if(keyword.toLowerCase().includes(oof.toLowerCase()) && !keyword.toLowerCase() === "domtokenlist"){
|
||||
console.log(oof, keyword)
|
||||
isHarmful = "token stealer/virus"
|
||||
break
|
||||
}
|
||||
}
|
||||
if(isHarmful)break
|
||||
}
|
||||
|
||||
if(!isHarmful){
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
const no_comments = data.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "").trim()// removing the META comment from plugins
|
||||
if((/var [\w\d_$]+=\["/gi).test(no_comments)){
|
||||
isHarmful = "obfuscation/hidden code"
|
||||
}
|
||||
|
||||
if(!isHarmful){
|
||||
const regexps = [
|
||||
/** hexadecimal */
|
||||
/_0x\w{4}\('0x[\dabcdef]+'\)/g,
|
||||
/_0x\w{4}\('0x[\dabcdef]+'[, ]+'[^']{4}'\)/g, // _0x8db7('0x0', 'x1]f')
|
||||
/** mangled */
|
||||
/\w+\('0x[\dabcdef]+'\)/g, // b('0x0')
|
||||
/\w+\('0x[\dabcdef]+'[, ]+'[^']{4}'\)/g, // b('0x0', 'x1]f')
|
||||
]
|
||||
for(let regex of regexps){
|
||||
if(isHarmful)break
|
||||
if(regex.test(no_comments))isHarmful = "obfuscation/hidden code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!isHarmful)return removeCallback(hash)
|
||||
cache[hash] = {
|
||||
suspect: true,
|
||||
name: hashToUrl[hash].split("/").pop(),
|
||||
type: hashToUrl[hash].endsWith(".js") ? "Plugin" : "Theme",
|
||||
harm: isHarmful,
|
||||
hash: hash,
|
||||
filename
|
||||
}
|
||||
|
||||
console.log(`Found potentially dangerous ${cache[hash].type.toLowerCase()}: ${cache[hash].name}`)
|
||||
|
||||
resultCallback(cache[hash])
|
||||
}
|
||||
|
||||
const hashToUrl = {}
|
||||
|
||||
export function checkHash(hash, data, filename, resultCallback, removeCallback){
|
||||
console.log(`File: ${filename} hash: ${hash}`)
|
||||
if(!cache[hash]){
|
||||
nodeFetch("https://cdn.jsdelivr.net/gh/Lightcord/filehashes@master/hashes/"+hash, { // Using node-fetch to bypass cors
|
||||
headers: {
|
||||
"User-Agent": electron.remote.getCurrentWebContents().userAgent // have to set user-agent
|
||||
}
|
||||
}).then(async res => {
|
||||
if(res.status !== 200){
|
||||
if(filename.endsWith(".theme.css"))return removeCallback(hash)
|
||||
checkViruses(hash, data, resultCallback, removeCallback, filename)
|
||||
return
|
||||
}
|
||||
const result = await res.json()
|
||||
result.hash = hash
|
||||
result.filename = filename
|
||||
|
||||
cache[hash] = result
|
||||
|
||||
resultCallback(result)
|
||||
}).catch(console.error)
|
||||
}else{
|
||||
const result = cache[hash]
|
||||
|
||||
resultCallback(result)
|
||||
}
|
||||
}
|
||||
|
||||
export function processFile(__path, resultCallback, removeCallback = (hash) => {}, isFromLoader = false){
|
||||
const hash = crypto.createHash("sha256")
|
||||
let data = Buffer.alloc(0)
|
||||
|
||||
createReadStream(__path).on("data", chunk => {
|
||||
data = Buffer.concat([data, chunk])
|
||||
hash.update(chunk)
|
||||
}).on("end", () => {
|
||||
const hashResult = hash.digest("hex")
|
||||
|
||||
hashToUrl[hashResult] = __path
|
||||
|
||||
if(isFromLoader && addonCache[hashResult]){
|
||||
let value = addonCache[hashResult]
|
||||
if(value.timestamp < (Date.now() - 6.048e+8)){
|
||||
delete addonCache[hashResult]
|
||||
contentManager.saveAddonCache()
|
||||
}else{
|
||||
resultCallback(value.result)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
checkHash(hashResult, data, basename(__path), resultCallback, removeCallback)
|
||||
})
|
||||
}
|
||||
|
||||
export function processAttachment(attachment, id){
|
||||
if(!document.getElementById(id))return
|
||||
if(!attachment.url.startsWith("https://cdn.discordapp.com/"))return document.getElementById(id).remove()
|
||||
if(!attachment.filename.endsWith(".plugin.js") && !attachment.filename.endsWith(".theme.css"))return document.getElementById(id).remove()
|
||||
|
||||
nodeFetch(attachment.url, {
|
||||
headers: {
|
||||
"User-Agent": electron.remote.getCurrentWebContents().userAgent
|
||||
}
|
||||
}).then(res => {
|
||||
if(res.status !== 200)throw new Error("File doesn't exist.")
|
||||
const hash = crypto.createHash("sha256")
|
||||
let data = Buffer.alloc(0)
|
||||
res.body.on("data", chunk => {
|
||||
data = Buffer.concat([data, chunk])
|
||||
hash.update(chunk)
|
||||
})
|
||||
res.body.on("end", () => {
|
||||
const hashResult = hash.digest("hex")
|
||||
|
||||
cache2[attachment.url] = hashResult
|
||||
hashToUrl[hashResult] = attachment.url
|
||||
|
||||
checkHash(hashResult, data, attachment.filename, (result) => {
|
||||
renderToElements(id, result, attachment.filename)
|
||||
}, () => {
|
||||
let elem = document.getElementById(id)
|
||||
if(elem)elem.remove()
|
||||
})
|
||||
})
|
||||
}).catch(()=>{})
|
||||
}
|
||||
|
||||
let flowerStarModule = BDModules.get(e => e.flowerStarContainer)[0]
|
||||
let childModule = BDModules.get(e => e.childContainer)[0]
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLDivElement[]} elements
|
||||
* @param {{type: "Theme"|"Plugin", name: string, official?: boolean}|{suspect:true, type: "Theme"|"Plugin", name: string, harm: string}} result
|
||||
*/
|
||||
function renderToElements(id, result, filename){
|
||||
const div = document.getElementById(id)
|
||||
if(!div || div.childNodes.length > 0)return // already certified/div does not exist anymore.
|
||||
|
||||
if(!flowerStarModule)flowerStarModule = BDModules.get(e => e.flowerStarContainer)[0]
|
||||
if(!childModule)childModule = BDModules.get(e => e.childContainer)[0]
|
||||
|
||||
if(result.suspect){
|
||||
try{
|
||||
div.parentNode.style.borderColor = "rgb(240, 71, 71)"
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} node
|
||||
*/
|
||||
let nextNode = (node) => {
|
||||
for(let child of node.children){
|
||||
if(child.tagName === "A"){
|
||||
child.addEventListener("click", (e) => {
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
|
||||
Utils.showConfirmationModal(
|
||||
"Are you sure you want to download this ?",
|
||||
"The "+result.type.toLowerCase()+" **"+filename+"** might be dangerous **("+result.harm+")**. \n\n**We don't recommand to download it**. However, you can still do it below.",
|
||||
{
|
||||
confirmText: "Download Anyway",
|
||||
cancelText: "Don't !",
|
||||
danger: true,
|
||||
onCancel: () => {},
|
||||
onConfirm: () => {
|
||||
electron.remote.shell.openExternal(child.href)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}else if(["div"].includes(child.tagName.toLowerCase())){
|
||||
nextNode(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
nextNode(div.parentNode)
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
}
|
||||
BDV2.reactDom.render(BDV2.react.createElement(tooltipWrap, {text: result.type+" "+result.name+" is potentially dangerous."},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px"}},
|
||||
BDV2.react.createElement("svg", {className: BDModules.get(e => e.svg)[0].svg, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 40 32"},
|
||||
BDV2.react.createElement("rect", {
|
||||
x:"0",
|
||||
y:"0",
|
||||
width:"32",
|
||||
height:"32",
|
||||
mask:"url(#svg-mask-avatar-status-round-32)",
|
||||
fill:"#f04747",
|
||||
mask:"url(#svg-mask-status-dnd)",
|
||||
className:BDModules.get(e => e.pointerEvents)[0].pointerEvents
|
||||
})
|
||||
)
|
||||
)
|
||||
), div)
|
||||
}else if(!result.official){
|
||||
div.parentNode.style.borderColor = "#4087ed"
|
||||
let span = BDV2.react.createElement("span", {style: {display: "inherit"}}, [
|
||||
BDV2.react.createElement(tooltipWrap, {text: result.type+" "+result.name+" is certified by Lightcord."},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px", float: "left"}},
|
||||
BDV2.react.createElement("svg", {className: flowerStarModule.flowerStar, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 16 15.2"},
|
||||
BDV2.react.createElement("path", {fill:"#4f545c", "fill-rule":"evenodd",d:"m16 7.6c0 .79-1.28 1.38-1.52 2.09s.44 2 0 2.59-1.84.35-2.46.8-.79 1.84-1.54 2.09-1.67-.8-2.47-.8-1.75 1-2.47.8-.92-1.64-1.54-2.09-2-.18-2.46-.8.23-1.84 0-2.59-1.54-1.3-1.54-2.09 1.28-1.38 1.52-2.09-.44-2 0-2.59 1.85-.35 2.48-.8.78-1.84 1.53-2.12 1.67.83 2.47.83 1.75-1 2.47-.8.91 1.64 1.53 2.09 2 .18 2.46.8-.23 1.84 0 2.59 1.54 1.3 1.54 2.09z"})
|
||||
),
|
||||
BDV2.react.createElement("div", {className: childModule.childContainer},
|
||||
BDV2.react.createElement("svg", {"aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 16 15.2"},
|
||||
BDV2.react.createElement("path", {fill:"#ffffff",d:"M7.4,11.17,4,8.62,5,7.26l2,1.53L10.64,4l1.36,1Z"})
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
BDV2.react.createElement(tooltipWrap, {text: "Install this "+result.type.toLowerCase()+" on Lightcord."},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px"}, onClick(){
|
||||
Utils.showConfirmationModal(
|
||||
"Are you sure you want to download this "+result.type.toLowerCase()+" ?",
|
||||
"Lightcord will automatically install and launch this "+result.type.toLowerCase()+". You don't have anything to do.",
|
||||
{
|
||||
confirmText: "Download and Install",
|
||||
cancelText: "I've changed my mind",
|
||||
danger: false,
|
||||
onCancel: () => {},
|
||||
onConfirm: () => {
|
||||
let link = getKeyedArray(cache2).find(e => e[1] === result.hash)[0]
|
||||
console.log(link)
|
||||
nodeFetch(link)
|
||||
.then(async res => {
|
||||
if(res.status !== 200)throw new Error("Status was not 200")
|
||||
let content = await res.buffer()
|
||||
let installPath = join(result.type === "Plugin" ? contentManager._pluginsFolder : contentManager._themesFolder, result.filename)
|
||||
console.log(installPath)
|
||||
writeFileSync(installPath, content)
|
||||
Utils.showToast(result.type+" succesfully installed.")
|
||||
}).catch(err => {
|
||||
err = err instanceof Error ? err : new Error(err)
|
||||
Utils.showToast(err.message, {
|
||||
type: "error"
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}},
|
||||
BDV2.react.createElement("svg", {className: flowerStarModule.flowerStar, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 24 24",style:{
|
||||
color: "rgb(67, 181, 129)",
|
||||
cursor: "pointer"
|
||||
}},
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h24v24H0z"></path>
|
||||
<path class="fill" fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path>
|
||||
</g>
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
BDV2.reactDom.render(span, div)
|
||||
}else{
|
||||
div.parentNode.style.borderColor = "#4087ed"
|
||||
let span = BDV2.react.createElement("span", {style: {display: "inherit"}}, [
|
||||
BDV2.react.createElement(tooltipWrap, {text: result.type+" "+result.name+" was made by the developers of Lightcord.", style:"brand"},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px", float: "left"}},
|
||||
BDV2.react.createElement("svg", {className: flowerStarModule.flowerStar, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 16 15.2",stroke:"#36393f",style:{color:"#4087ed"}},
|
||||
BDV2.react.createElement("path", {fill:"currentColor", "fill-rule":"evenodd",d:"m16 7.6c0 .79-1.28 1.38-1.52 2.09s.44 2 0 2.59-1.84.35-2.46.8-.79 1.84-1.54 2.09-1.67-.8-2.47-.8-1.75 1-2.47.8-.92-1.64-1.54-2.09-2-.18-2.46-.8.23-1.84 0-2.59-1.54-1.3-1.54-2.09 1.28-1.38 1.52-2.09-.44-2 0-2.59 1.85-.35 2.48-.8.78-1.84 1.53-2.12 1.67.83 2.47.83 1.75-1 2.47-.8.91 1.64 1.53 2.09 2 .18 2.46.8-.23 1.84 0 2.59 1.54 1.3 1.54 2.09z"})
|
||||
),
|
||||
BDV2.react.createElement("div", {className: childModule.childContainer},
|
||||
BDV2.react.createElement("svg", {"aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 16 15.2"},
|
||||
BDV2.react.createElement("path", {fill:"#ffffff",d:"M10.7,5.28a2.9,2.9,0,0,0-2.11.86.11.11,0,0,0,0,.16l1.05.94a.11.11,0,0,0,.15,0,1.27,1.27,0,0,1,.9-.33c.65,0,.65.73.65.73a.64.64,0,0,1-.65.65,1.73,1.73,0,0,1-1.18-.54c-.31-.26-.36-.32-.73-.66S7.06,5.28,5.65,5.28A2.26,2.26,0,0,0,3.37,7.56,2.59,2.59,0,0,0,3.82,9a2.18,2.18,0,0,0,1.83.89,2.94,2.94,0,0,0,2.1-.81.11.11,0,0,0,0-.16L6.74,8A.11.11,0,0,0,6.6,8a1.58,1.58,0,0,1-.94.29h0A.71.71,0,0,1,5,7.56H5a.63.63,0,0,1,.65-.64c.71,0,1.42.75,1.94,1.27.75.76,1.66,1.79,3.11,1.74A2.28,2.28,0,0,0,13,7.64a2.59,2.59,0,0,0-.45-1.47A2.14,2.14,0,0,0,10.7,5.28Z"})
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
BDV2.react.createElement(tooltipWrap, {text: "Install this "+result.type.toLowerCase()+" on Lightcord."},
|
||||
BDV2.react.createElement("div", {className: flowerStarModule.flowerStarContainer, style: {width: "20px", height: "20px"}, onClick(){
|
||||
Utils.showConfirmationModal(
|
||||
"Are you sure you want to download this "+result.type.toLowerCase()+" ?",
|
||||
"Lightcord will automatically download and load this "+result.type.toLowerCase()+". You must enable it in the settings.",
|
||||
{
|
||||
confirmText: "Download and Install",
|
||||
cancelText: "I've changed my mind",
|
||||
danger: false,
|
||||
onCancel: () => {},
|
||||
onConfirm: () => {
|
||||
let link = getKeyedArray(cache2).find(e => e[1] === result.hash)[0]
|
||||
|
||||
nodeFetch(link)
|
||||
.then(async res => {
|
||||
if(res.status !== 200)throw new Error("Status was not 200")
|
||||
let content = await res.buffer()
|
||||
let installPath = join(result.type === "Plugin" ? contentManager._pluginsFolder : contentManager._themesFolder, result.filename)
|
||||
|
||||
writeFileSync(installPath, content)
|
||||
Utils.showToast(result.type+" succesfully installed.")
|
||||
}).catch(err => {
|
||||
err = err instanceof Error ? err : new Error(err)
|
||||
Utils.showToast(err.message, {
|
||||
type: "error"
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}},
|
||||
BDV2.react.createElement("svg", {className: flowerStarModule.flowerStar, "aria-hidden":"false",width:"20px",height:"20px",viewBox:"0 0 24 24",style:{
|
||||
color: "rgb(67, 181, 129)",
|
||||
cursor: "pointer"
|
||||
}},
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h24v24H0z"></path>
|
||||
<path class="fill" fill="currentColor" d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path>
|
||||
</g>
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
BDV2.reactDom.render(span, div)
|
||||
}
|
||||
}
|
||||
|
||||
function getKeyedArray(obj){
|
||||
let arr = []
|
||||
Object.keys(obj).forEach(k => {
|
||||
arr.push([k, obj[k]])
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
let key = null
|
||||
let save = null
|
||||
|
||||
window.Lightcord.Api.ensureExported(m=>m.ObjectStorage)
|
||||
.then(localStorageModule => {
|
||||
let localStorage = localStorageModule.impl
|
||||
save = function(){
|
||||
localStorage.set("PluginCertifierKeyEncryption__", btoa(JSON.stringify(key)))
|
||||
}
|
||||
setInterval(() => {
|
||||
save()
|
||||
}, 100000);
|
||||
try{
|
||||
let val = safeJSONParse(atob(localStorage.get("PluginCertifierKeyEncryption__")))
|
||||
if(val instanceof Error || !Array.isArray(val) || val.length !== 2 || val.find(e => typeof e !== "string") || Buffer.from(val[0], "base64").length !== 16 || Buffer.from(val[1], "base64").length !== 32){
|
||||
generateKey()
|
||||
save()
|
||||
return
|
||||
}
|
||||
key = val
|
||||
}catch(e){
|
||||
generateKey()
|
||||
save()
|
||||
}
|
||||
})
|
||||
|
||||
function generateKey(){
|
||||
key = [crypto.randomBytes(16).toString("base64"), crypto.randomBytes(32).toString("base64")]
|
||||
}
|
||||
|
||||
function safeJSONParse(json){
|
||||
try{
|
||||
return JSON.parse(json)
|
||||
}catch(e){
|
||||
return e instanceof Error ? new Error(e) : e
|
||||
}
|
||||
}
|
||||
|
||||
export function decryptSettingsCache(data){
|
||||
try{
|
||||
let decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.from(key[1], "base64"), Buffer.from(key[0], "base64"))
|
||||
let decrypted = decipher.update(Buffer.from(data, "base64"));
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||
return decrypted.toString("utf8")
|
||||
}catch(e){
|
||||
return "{}"
|
||||
}
|
||||
}
|
||||
export function encryptSettingsCache(data){
|
||||
let args = [Buffer.from(key[1], "base64"), Buffer.from(key[0], "base64")]
|
||||
|
||||
let cipher = crypto.createCipheriv('aes-256-cbc', ...args);
|
||||
let encrypted = cipher.update(Buffer.from(data, "utf8"));
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
return encrypted.toString("base64")
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -15,12 +15,8 @@ var electron = require('electron');
|
|||
|
||||
var _path = require('path');
|
||||
|
||||
var _path2 = _interopRequireDefault(_path);
|
||||
|
||||
var _url = require('url');
|
||||
|
||||
var _url2 = _interopRequireDefault(_url);
|
||||
|
||||
var _Backoff = require('../common/Backoff');
|
||||
|
||||
var _Backoff2 = _interopRequireDefault(_Backoff);
|
||||
|
@ -72,16 +68,22 @@ const connectionBackoff = new _Backoff2.default(1000, 20000);
|
|||
const events = exports.events = new EventEmitter()
|
||||
const DISCORD_NAMESPACE = 'DISCORD_';
|
||||
|
||||
let isTabs = true
|
||||
const getWebappEndpoint = () => {
|
||||
let endpoint = settings.get('WEBAPP_ENDPOINT');
|
||||
if (!endpoint) {
|
||||
if (_buildInfo2.default.releaseChannel === 'stable') {
|
||||
endpoint = 'https://discord.com';
|
||||
} else {
|
||||
endpoint = `https://${_buildInfo2.default.releaseChannel}.discord.com`;
|
||||
//isTabs = settings.get("isTabs", false)
|
||||
if(!isTabs){
|
||||
let endpoint = settings.get('WEBAPP_ENDPOINT');
|
||||
if (!endpoint) {
|
||||
if (_buildInfo2.default.releaseChannel === 'stable') {
|
||||
endpoint = 'https://discord.com';
|
||||
} else {
|
||||
endpoint = `https://${_buildInfo2.default.releaseChannel}.discord.com`;
|
||||
}
|
||||
}
|
||||
return endpoint;
|
||||
}else{
|
||||
return "file://"+_path.join(__dirname, "tabs", "index.html")
|
||||
}
|
||||
return endpoint;
|
||||
};
|
||||
|
||||
const WEBAPP_ENDPOINT = getWebappEndpoint();
|
||||
|
@ -90,13 +92,13 @@ function getSanitizedPath(path) {
|
|||
// using the whatwg URL api, get a sanitized pathname from given path
|
||||
// this is because url.parse's `path` may not always have a slash
|
||||
// in front of it
|
||||
return new _url2.default.URL(path, WEBAPP_ENDPOINT).pathname;
|
||||
return new _url.URL(path, WEBAPP_ENDPOINT).pathname;
|
||||
}
|
||||
|
||||
function extractPathFromArgs(args, fallbackPath) {
|
||||
if (args.length === 3 && args[0] === '--url' && args[1] === '--') {
|
||||
try {
|
||||
const parsedURL = _url2.default.parse(args[2]);
|
||||
const parsedURL = _url.parse(args[2]);
|
||||
if (parsedURL.protocol === 'discord:') {
|
||||
return getSanitizedPath(parsedURL.path);
|
||||
}
|
||||
|
@ -108,7 +110,7 @@ function extractPathFromArgs(args, fallbackPath) {
|
|||
// TODO: These should probably be thrown in constants.
|
||||
const INITIAL_PATH = extractPathFromArgs(process.argv.slice(1), '/app');
|
||||
const WEBAPP_PATH = settings.get('WEBAPP_PATH', `${INITIAL_PATH}?_=${Date.now()}`);
|
||||
const URL_TO_LOAD = `${WEBAPP_ENDPOINT}${WEBAPP_PATH}`;
|
||||
const URL_TO_LOAD = isTabs ? WEBAPP_ENDPOINT : `${WEBAPP_ENDPOINT}${WEBAPP_PATH}`;
|
||||
const MIN_WIDTH = settings.get('MIN_WIDTH', 940);
|
||||
const MIN_HEIGHT = settings.get('MIN_HEIGHT', 500);
|
||||
const DEFAULT_WIDTH = 1280;
|
||||
|
@ -347,12 +349,18 @@ function launchMainAppWindow(isVisible) {
|
|||
show: isVisible,
|
||||
webPreferences: {
|
||||
blinkFeatures: 'EnumerateDevices,AudioOutputDevices',
|
||||
nodeIntegration: false,
|
||||
preload: _path2.default.join(__dirname, 'mainScreenPreload.js'),
|
||||
nativeWindowOpen: true,
|
||||
enableRemoteModule: true // canary shit, just enable it
|
||||
enableRemoteModule: true,
|
||||
...(isTabs ? {
|
||||
nodeIntegration: true,
|
||||
webviewTag: true
|
||||
} : {
|
||||
nodeIntegration: false,
|
||||
webviewTag: false,
|
||||
preload: _path.join(__dirname, 'mainScreenPreload.js')
|
||||
})
|
||||
},
|
||||
icon: _path2.default.join(__dirname, 'discord.png')
|
||||
icon: _path.join(__dirname, 'discord.png')
|
||||
};
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
#title-bar-btns {
|
||||
-webkit-app-region: no-drag;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
right: 6px;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lightcord Tabs</title>
|
||||
<script src="index.js"></script>
|
||||
<style>
|
||||
.documentFull {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 39px;
|
||||
bottom: 0;
|
||||
overflow: auto;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
.discord-webview {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
display: none;
|
||||
}
|
||||
.active-webview {
|
||||
display: inherit
|
||||
}
|
||||
body {
|
||||
margin: 0
|
||||
}
|
||||
.surface {
|
||||
-webkit-app-region: drag
|
||||
}
|
||||
.chrome-tab {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="surface">
|
||||
<div class="chrome-tabs chrome-tabs-dark-theme" style="--tab-content-margin: 9px;margin-top: -8px">
|
||||
<div class="chrome-tabs-content">
|
||||
</div>
|
||||
<div class="chrome-tabs-bottom-bar"></div>
|
||||
<!-- Styles to prevent flash after JS initialization -->
|
||||
<style>
|
||||
.chrome-tabs .chrome-tab {
|
||||
width: 258px
|
||||
}
|
||||
|
||||
.chrome-tabs .chrome-tab:nth-child(1) {
|
||||
transform: translate3d(0px, 0, 0)
|
||||
}
|
||||
|
||||
.chrome-tabs .chrome-tab:nth-child(2) {
|
||||
transform: translate3d(239px, 0, 0)
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<div class="chrome-tabs-optional-shadow-below-bottom-bar"></div>
|
||||
</div>
|
||||
<div class="documentFull"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,82 @@
|
|||
const fs = require("fs")
|
||||
const { join } = require("path")
|
||||
const { pathToFileURL } = require("url")
|
||||
const { remote } = require("electron")
|
||||
|
||||
window.onload = () => {
|
||||
const ChromeTabs = require("chrome-tabs")
|
||||
require("chrome-tabs/css/chrome-tabs.css")
|
||||
require("chrome-tabs/css/chrome-tabs-dark-theme.css")
|
||||
require("./controls.css")
|
||||
|
||||
let tabs = document.querySelector(".chrome-tabs")
|
||||
let chromeTabs = new ChromeTabs()
|
||||
chromeTabs.init(tabs)
|
||||
|
||||
let webviews = new Map()
|
||||
window.webviews = webviews
|
||||
|
||||
tabs.addEventListener('activeTabChange', ({detail}) => {
|
||||
let webview = webviews.get(detail.tabEl)
|
||||
if(!webview){
|
||||
chromeTabs.removeTab(detail.tabEl)
|
||||
return
|
||||
}
|
||||
let active = Array.from(webviews.values()).find(e => e.classList.contains("active-webview"))
|
||||
if(active)active.classList.remove("active-webview")
|
||||
webview.classList.add("active-webview")
|
||||
})
|
||||
tabs.addEventListener('tabAdd', ({detail}) => {
|
||||
console.log('Tab added', detail.tabEl)
|
||||
detail.tabEl.querySelector(".chrome-tab-title").innerText = "Lightcord Loading..."
|
||||
let webview = document.createElement("webview")
|
||||
webview.src = "https://discord.com/app"
|
||||
webview.classList.add("discord-webview")
|
||||
webview.classList.add("webview-active")
|
||||
webview.setAttribute("preload", pathToFileURL(join(__dirname, "../mainScreenPreload.js")))
|
||||
webviews.set(detail.tabEl, webview)
|
||||
document.querySelector(".documentFull").appendChild(webview)
|
||||
webview.addEventListener("dom-ready", () => {
|
||||
remote.webContents.fromId(webview.getWebContentsId()).openDevTools()
|
||||
})
|
||||
})
|
||||
tabs.addEventListener('tabRemove', ({detail}) => {
|
||||
console.log('Tab removed', detail.tabEl)
|
||||
let webview = webviews.get(detail.tabEl)
|
||||
if(!webview)return
|
||||
webview.remove()
|
||||
webviews.delete(detail.tabEl)
|
||||
})
|
||||
|
||||
window.addEventListener('keydown', (event) => {
|
||||
if(event.ctrlKey){
|
||||
if(event.key === 't'){
|
||||
chromeTabs.addTab({
|
||||
title: 'Lightcord',
|
||||
favicon: faviconURL
|
||||
})
|
||||
}else if(event.key === "w"){
|
||||
let active = document.querySelector("div.chrome-tab[active]")
|
||||
if(!active)return
|
||||
chromeTabs.removeTab(active)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
require.extensions[".css"] = (m, filename) => {
|
||||
let content = fs.readFileSync(filename, "binary")
|
||||
let style = document.createElement("style")
|
||||
style.id = btoa(filename)
|
||||
style.innerHTML = content
|
||||
document.head.appendChild(style)
|
||||
m.exports = {
|
||||
id: style.id,
|
||||
remove(){
|
||||
return style.remove()
|
||||
}
|
||||
}
|
||||
return m.exports
|
||||
}
|
||||
|
||||
const faviconURL = pathToFileURL(join(__dirname, "../images/discord.png"))
|
File diff suppressed because it is too large
Load Diff
|
@ -1,16 +1,17 @@
|
|||
{
|
||||
"name": "discord_desktop_core",
|
||||
"description": "Discord Client for Desktop - Light version - Core App",
|
||||
"main": "app/index.js",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"discordrpcgenerator": "^1.2.0",
|
||||
"invariant": "2.2.4",
|
||||
"lodash": "4.17.13",
|
||||
"mkdirp": "^1.0.4",
|
||||
"promise.allsettled": "^1.0.0",
|
||||
"request": "2.88.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"yauzl": "^2.10.0"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "discord_desktop_core",
|
||||
"description": "Discord Client for Desktop - Light version - Core App",
|
||||
"main": "app/index.js",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"chrome-tabs": "^5.4.0",
|
||||
"discordrpcgenerator": "^1.2.0",
|
||||
"invariant": "2.2.4",
|
||||
"lodash": "4.17.13",
|
||||
"mkdirp": "^1.0.4",
|
||||
"promise.allsettled": "^1.0.0",
|
||||
"request": "2.88.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"yauzl": "^2.10.0"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue