parent
9aaa47ea4e
commit
8161a07dba
@ -1,12 +1,41 @@
|
|||||||
export * as Plugins from "./plugins";
|
export * as Plugins from "./plugins";
|
||||||
export * as Webpack from "./webpack";
|
export * as Webpack from "./webpack";
|
||||||
export * as Api from "./api";
|
export * as Api from "./api";
|
||||||
export { Settings } from "./api/settings";
|
import { popNotice, showNotice } from "./api/Notices";
|
||||||
|
import { Settings } from "./api/settings";
|
||||||
|
import { startAllPlugins } from "./plugins";
|
||||||
|
|
||||||
|
export { Settings };
|
||||||
|
|
||||||
import "./utils/patchWebpack";
|
import "./utils/patchWebpack";
|
||||||
import "./utils/quickCss";
|
import "./utils/quickCss";
|
||||||
import { waitFor } from "./webpack";
|
import { checkForUpdates, UpdateLogger } from './utils/updater';
|
||||||
|
import { onceReady } from "./webpack";
|
||||||
|
import { Router } from "./webpack/common";
|
||||||
|
|
||||||
export let Components;
|
export let Components;
|
||||||
|
|
||||||
waitFor("useState", () => setTimeout(() => import("./components").then(mod => Components = mod), 0));
|
async function init() {
|
||||||
|
await onceReady;
|
||||||
|
startAllPlugins();
|
||||||
|
Components = await import("./components");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isOutdated = await checkForUpdates();
|
||||||
|
if (isOutdated && Settings.notifyAboutUpdates)
|
||||||
|
setTimeout(() => {
|
||||||
|
showNotice(
|
||||||
|
"A Vencord update is available!",
|
||||||
|
"View Update",
|
||||||
|
() => {
|
||||||
|
popNotice();
|
||||||
|
Router.open("Vencord");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, 10000);
|
||||||
|
} catch (err) {
|
||||||
|
UpdateLogger.error("Failed to check for updates", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { waitFor } from "../webpack";
|
||||||
|
|
||||||
|
let NoticesModule: any;
|
||||||
|
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);
|
||||||
|
|
||||||
|
export const noticesQueue = [] as any[];
|
||||||
|
export let currentNotice: any = null;
|
||||||
|
|
||||||
|
export function popNotice() {
|
||||||
|
NoticesModule.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextNotice() {
|
||||||
|
currentNotice = noticesQueue.shift();
|
||||||
|
|
||||||
|
if (currentNotice) {
|
||||||
|
NoticesModule.show(...currentNotice, "VencordNotice");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showNotice(message: string, buttonText: string, onOkClick: () => void) {
|
||||||
|
noticesQueue.push(["GENERIC", message, buttonText, onOkClick]);
|
||||||
|
if (!currentNotice) nextNotice();
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
export * as MessageEvents from "./MessageEvents";
|
export * as MessageEvents from "./MessageEvents";
|
||||||
|
export * as Notices from "./Notices";
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import { React } from "../webpack/common";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
href: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Link(props: React.PropsWithChildren<Props>) {
|
||||||
|
if (props.disabled) {
|
||||||
|
props.style ??= {};
|
||||||
|
props.style.pointerEvents = "none";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<a href={props.href} target="_blank" style={props.style}>
|
||||||
|
{props.children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
import gitHash from "git-hash";
|
||||||
|
import { changes, checkForUpdates, getRepo, rebuild, update, UpdateLogger } from "../utils/updater";
|
||||||
|
import { React, Forms, Button, Margins, Alerts, Card, Parser } from '../webpack/common';
|
||||||
|
import { Flex } from "./Flex";
|
||||||
|
import { useAwaiter } from '../utils/misc';
|
||||||
|
import { Link } from "./Link";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
setIsOutdated(b: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) {
|
||||||
|
return async () => {
|
||||||
|
dispatcher(true);
|
||||||
|
try {
|
||||||
|
await action();
|
||||||
|
} catch (e: any) {
|
||||||
|
UpdateLogger.error("Failed to update", e);
|
||||||
|
if (!e) {
|
||||||
|
var err = "An unknown error occurred (error is undefined).\nPlease try again.";
|
||||||
|
} else if (e.code && e.cmd) {
|
||||||
|
const { code, path, cmd, stderr } = e;
|
||||||
|
|
||||||
|
if (code === "ENOENT")
|
||||||
|
var err = `Command \`${path}\` not found.\nPlease install it and try again`;
|
||||||
|
else {
|
||||||
|
var err = `An error occured while running \`${cmd}\`:\n`;
|
||||||
|
err += stderr || `Code \`${code}\`. See the console for more info`;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var err = "An unknown error occurred. See the console for more info.";
|
||||||
|
}
|
||||||
|
Alerts.show({
|
||||||
|
title: "Oops!",
|
||||||
|
body: err.split("\n").map(line => <div>{Parser.parse(line)}</div>)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
dispatcher(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Updater(p: Props) {
|
||||||
|
const [repo, err, repoPending] = useAwaiter(getRepo, "Loading...");
|
||||||
|
const [isChecking, setIsChecking] = React.useState(false);
|
||||||
|
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||||
|
const [updates, setUpdates] = React.useState(changes);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (err)
|
||||||
|
UpdateLogger.error("Failed to retrieve repo", err);
|
||||||
|
}, [err]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Forms.FormText>Repo: {repoPending ? repo : err ? "Failed to retrieve - check console" : (
|
||||||
|
<Link href={repo}>
|
||||||
|
{repo.split("/").slice(-2).join("/")}
|
||||||
|
</Link>
|
||||||
|
)} ({gitHash})</Forms.FormText>
|
||||||
|
|
||||||
|
<Forms.FormText className={Margins.marginBottom8}>
|
||||||
|
There are {updates.length} Updates
|
||||||
|
</Forms.FormText>
|
||||||
|
|
||||||
|
<Card style={{ padding: ".5em" }}>
|
||||||
|
{updates.map(({ hash, author, message }) => (
|
||||||
|
<div>
|
||||||
|
<Link href={`${repo}/commit/${hash}`} disabled={repoPending}>
|
||||||
|
<code>{hash}</code>
|
||||||
|
</Link>
|
||||||
|
<span style={{
|
||||||
|
marginLeft: "0.5em",
|
||||||
|
color: "var(--text-normal)"
|
||||||
|
}}>{message} - {author}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Flex className={`${Margins.marginBottom8} ${Margins.marginTop8}`}>
|
||||||
|
<Button
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
disabled={isUpdating || isChecking}
|
||||||
|
onClick={withDispatcher(setIsUpdating, async () => {
|
||||||
|
if (await update()) {
|
||||||
|
p.setIsOutdated(false);
|
||||||
|
const needFullRestart = await rebuild();
|
||||||
|
await new Promise<void>(r => {
|
||||||
|
Alerts.show({
|
||||||
|
title: "Update Success!",
|
||||||
|
body: "Successfully updated. Restart now to apply the changes?",
|
||||||
|
confirmText: "Restart",
|
||||||
|
cancelText: "Not now!",
|
||||||
|
onConfirm() {
|
||||||
|
if (needFullRestart)
|
||||||
|
window.DiscordNative.app.relaunch();
|
||||||
|
else
|
||||||
|
location.reload();
|
||||||
|
r();
|
||||||
|
},
|
||||||
|
onCancel: r
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
disabled={isUpdating || isChecking}
|
||||||
|
onClick={withDispatcher(setIsChecking, async () => {
|
||||||
|
const res = await checkForUpdates();
|
||||||
|
if (res) {
|
||||||
|
setUpdates(changes);
|
||||||
|
} else {
|
||||||
|
p.setIsOutdated(false);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ApiNotices",
|
||||||
|
description: "Fixes notices being automatically dismissed",
|
||||||
|
author: "Vendicated",
|
||||||
|
required: true,
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "updateNotice:",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /;(.{1,2}=null;)(?=.{0,50}updateNotice)/g,
|
||||||
|
replace:
|
||||||
|
';if(Vencord.Api.Notices.currentNotice)return !1;$1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /(?<=NOTICE_DISMISS:function.+?){(?=if\(null==(.+?)\))/,
|
||||||
|
replace: '{if($1?.id=="VencordNotice")return ($1=null,Vencord.Api.Notices.nextNotice(),true);'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
@ -0,0 +1,51 @@
|
|||||||
|
import IpcEvents from "./IpcEvents";
|
||||||
|
import Logger from "./logger";
|
||||||
|
import { IpcRes } from './types';
|
||||||
|
|
||||||
|
export const UpdateLogger = new Logger("Updater", "white");
|
||||||
|
export let isOutdated = false;
|
||||||
|
export let changes: Record<"hash" | "author" | "message", string>[];
|
||||||
|
|
||||||
|
async function Unwrap<T>(p: Promise<IpcRes<T>>) {
|
||||||
|
const res = await p;
|
||||||
|
|
||||||
|
if (res.ok) return res.value;
|
||||||
|
throw res.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkForUpdates() {
|
||||||
|
changes = await Unwrap(VencordNative.ipc.invoke<IpcRes<typeof changes>>(IpcEvents.GET_UPDATES));
|
||||||
|
return (isOutdated = changes.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function update() {
|
||||||
|
if (!isOutdated) return true;
|
||||||
|
|
||||||
|
const res = await Unwrap(VencordNative.ipc.invoke<IpcRes<boolean>>(IpcEvents.UPDATE));
|
||||||
|
|
||||||
|
if (res)
|
||||||
|
isOutdated = false;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRepo() {
|
||||||
|
return Unwrap(VencordNative.ipc.invoke<IpcRes<string>>(IpcEvents.GET_REPO));
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hashes = Record<"patcher.js" | "preload.js" | "renderer.js", string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns true if hard restart is required
|
||||||
|
*/
|
||||||
|
export async function rebuild() {
|
||||||
|
const oldHashes = await Unwrap(VencordNative.ipc.invoke<IpcRes<Hashes>>(IpcEvents.GET_HASHES));
|
||||||
|
|
||||||
|
if (!await Unwrap(VencordNative.ipc.invoke<IpcRes<boolean>>(IpcEvents.BUILD)))
|
||||||
|
throw new Error("The Build failed. Please try manually building the new update");
|
||||||
|
|
||||||
|
const newHashes = await Unwrap(VencordNative.ipc.invoke<IpcRes<Hashes>>(IpcEvents.GET_HASHES));
|
||||||
|
|
||||||
|
return oldHashes["patcher.js"] !== newHashes["patcher.js"] ||
|
||||||
|
oldHashes["preload.js"] !== newHashes["preload.js"];
|
||||||
|
}
|
Loading…
Reference in new issue