diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts
index da5157f8..3243a22a 100644
--- a/browser/VencordNativeStub.ts
+++ b/browser/VencordNativeStub.ts
@@ -58,7 +58,7 @@ window.VencordNative = {
},
updater: {
- getRepo: async () => ({ ok: true, value: "https://github.com/Equicord/Equicord" }),
+ getRepo: async () => ({ ok: true, value: "https://github.com/Rayanzay/ryncord" }),
getUpdates: async () => ({ ok: true, value: [] }),
update: async () => ({ ok: true, value: false }),
rebuild: async () => ({ ok: true, value: true }),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ea629061..bc31ea33 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,10 +6,10 @@ settings:
patchedDependencies:
'@types/less@3.0.6':
- hash: krcufrsfhsuxuoj7hocqugs6zi
+ hash: 641e6c93bb737bac7fc283416857bd095cd85bcbcba63becb7a8bbcc78f73076
path: patches/@types__less@3.0.6.patch
eslint@9.20.1:
- hash: xm46kqcmdgzlmm4aifkfpxaho4
+ hash: 4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215
path: patches/eslint@9.20.1.patch
importers:
@@ -30,7 +30,7 @@ importers:
version: 0.3.5
'@types/less':
specifier: ^3.0.6
- version: 3.0.6(patch_hash=krcufrsfhsuxuoj7hocqugs6zi)
+ version: 3.0.6(patch_hash=641e6c93bb737bac7fc283416857bd095cd85bcbcba63becb7a8bbcc78f73076)
'@types/stylus':
specifier: ^0.48.42
version: 0.48.42
@@ -79,7 +79,7 @@ importers:
version: 3.2.10
'@stylistic/eslint-plugin':
specifier: ^4.2.0
- version: 4.2.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
+ version: 4.2.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
'@types/chrome':
specifier: ^0.0.312
version: 0.0.312
@@ -112,22 +112,22 @@ importers:
version: 0.25.1
eslint:
specifier: 9.20.1
- version: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ version: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
eslint-import-resolver-alias:
specifier: ^1.1.2
- version: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)))
+ version: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)))
eslint-plugin-react:
specifier: ^7.37.3
- version: 7.37.4(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
+ version: 7.37.4(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))
eslint-plugin-simple-header:
specifier: ^1.2.1
- version: 1.2.2(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
+ version: 1.2.2(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))
eslint-plugin-simple-import-sort:
specifier: ^12.1.1
- version: 12.1.1(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
+ version: 12.1.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))
eslint-plugin-unused-imports:
specifier: ^4.1.4
- version: 4.1.4(@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
+ version: 4.1.4(@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))
highlight.js:
specifier: 11.11.1
version: 11.11.1
@@ -169,7 +169,7 @@ importers:
version: 5.8.2
typescript-eslint:
specifier: ^8.28.0
- version: 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
+ version: 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
typescript-transform-paths:
specifier: ^3.5.5
version: 3.5.5(typescript@5.8.2)
@@ -2665,9 +2665,9 @@ snapshots:
'@esbuild/win32-x64@0.25.1':
optional: true
- '@eslint-community/eslint-utils@4.5.1(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))':
+ '@eslint-community/eslint-utils@4.5.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))':
dependencies:
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
@@ -2791,10 +2791,10 @@ snapshots:
'@socket.io/component-emitter@3.1.2': {}
- '@stylistic/eslint-plugin@4.2.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)':
+ '@stylistic/eslint-plugin@4.2.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)':
dependencies:
- '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
eslint-visitor-keys: 4.2.0
espree: 10.3.0
estraverse: 5.3.0
@@ -2839,7 +2839,7 @@ snapshots:
dependencies:
'@types/node': 22.13.13
- '@types/less@3.0.6(patch_hash=krcufrsfhsuxuoj7hocqugs6zi)': {}
+ '@types/less@3.0.6(patch_hash=641e6c93bb737bac7fc283416857bd095cd85bcbcba63becb7a8bbcc78f73076)': {}
'@types/lodash@4.17.15': {}
@@ -2901,15 +2901,15 @@ snapshots:
dependencies:
'@types/node': 22.13.13
- '@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)':
+ '@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)':
dependencies:
'@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
+ '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
'@typescript-eslint/scope-manager': 8.28.0
- '@typescript-eslint/type-utils': 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
- '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
+ '@typescript-eslint/type-utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
+ '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
'@typescript-eslint/visitor-keys': 8.28.0
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
graphemer: 1.4.0
ignore: 5.3.1
natural-compare: 1.4.0
@@ -2918,14 +2918,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)':
+ '@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)':
dependencies:
'@typescript-eslint/scope-manager': 8.28.0
'@typescript-eslint/types': 8.28.0
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2)
'@typescript-eslint/visitor-keys': 8.28.0
debug: 4.4.0
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
@@ -2935,12 +2935,12 @@ snapshots:
'@typescript-eslint/types': 8.28.0
'@typescript-eslint/visitor-keys': 8.28.0
- '@typescript-eslint/type-utils@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)':
+ '@typescript-eslint/type-utils@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)':
dependencies:
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2)
- '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
+ '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
debug: 4.4.0
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
ts-api-utils: 2.1.0(typescript@5.8.2)
typescript: 5.8.2
transitivePeerDependencies:
@@ -2962,13 +2962,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)':
+ '@typescript-eslint/utils@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)':
dependencies:
- '@eslint-community/eslint-utils': 4.5.1(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
+ '@eslint-community/eslint-utils': 4.5.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))
'@typescript-eslint/scope-manager': 8.28.0
'@typescript-eslint/types': 8.28.0
'@typescript-eslint/typescript-estree': 8.28.0(typescript@5.8.2)
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
@@ -3564,9 +3564,9 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
- eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))):
+ eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))):
dependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))
eslint-import-resolver-node@0.3.9:
dependencies:
@@ -3576,17 +3576,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)):
dependencies:
debug: 3.2.7
optionalDependencies:
- '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
eslint-import-resolver-node: 0.3.9
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -3595,9 +3595,9 @@ snapshots:
array.prototype.flatmap: 1.3.3
debug: 3.2.7
doctrine: 2.1.0
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -3609,13 +3609,13 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
+ '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-react@7.37.4(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)):
+ eslint-plugin-react@7.37.4(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)):
dependencies:
array-includes: 3.1.8
array.prototype.findlast: 1.2.5
@@ -3623,7 +3623,7 @@ snapshots:
array.prototype.tosorted: 1.1.4
doctrine: 2.1.0
es-iterator-helpers: 1.2.1
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
estraverse: 5.3.0
hasown: 2.0.2
jsx-ast-utils: 3.3.5
@@ -3637,19 +3637,19 @@ snapshots:
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
- eslint-plugin-simple-header@1.2.2(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)):
+ eslint-plugin-simple-header@1.2.2(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)):
dependencies:
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
- eslint-plugin-simple-import-sort@12.1.1(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)):
+ eslint-plugin-simple-import-sort@12.1.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)):
dependencies:
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
- eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)):
+ eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)):
dependencies:
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
optionalDependencies:
- '@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
+ '@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
eslint-scope@8.3.0:
dependencies:
@@ -3660,9 +3660,9 @@ snapshots:
eslint-visitor-keys@4.2.0: {}
- eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4):
+ eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215):
dependencies:
- '@eslint-community/eslint-utils': 4.5.1(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
+ '@eslint-community/eslint-utils': 4.5.1(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.19.2
'@eslint/core': 0.11.0
@@ -5028,12 +5028,12 @@ snapshots:
typed-query-selector@2.12.0: {}
- typescript-eslint@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2):
+ typescript-eslint@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2))(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
- '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
- '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.8.2)
- eslint: 9.20.1(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4)
+ '@typescript-eslint/eslint-plugin': 8.28.0(@typescript-eslint/parser@8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2))(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
+ '@typescript-eslint/parser': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
+ '@typescript-eslint/utils': 8.28.0(eslint@9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215))(typescript@5.8.2)
+ eslint: 9.20.1(patch_hash=4f22e92770bf528b2448fbec0984b9c0761dd588ed0e83dcc41edfc9af711215)
typescript: 5.8.2
transitivePeerDependencies:
- supports-color
diff --git a/scripts/runInstaller.mjs b/scripts/runInstaller.mjs
index fee48402..3080653a 100644
--- a/scripts/runInstaller.mjs
+++ b/scripts/runInstaller.mjs
@@ -62,7 +62,7 @@ async function ensureBinary() {
const res = await fetch(BASE_URL + filename, {
headers: {
- "User-Agent": "Equicord (https://github.com/Equicord/Equicord)",
+ "User-Agent": "Equicord (https://github.com/Rayanzay/ryncord)",
"If-None-Match": etag
}
});
diff --git a/src/components/PluginSettings/ContributorModal.tsx b/src/components/PluginSettings/ContributorModal.tsx
index 8cc8cacd..3cd1dc7b 100644
--- a/src/components/PluginSettings/ContributorModal.tsx
+++ b/src/components/PluginSettings/ContributorModal.tsx
@@ -60,7 +60,7 @@ function ContributorModal({ user }: { user: User; }) {
.sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false));
}, [user.id, user.username]);
- const ContributedHyperLink = contributed;
+ const ContributedHyperLink = contributed;
return (
<>
diff --git a/src/equicordplugins/anammox/index.ts b/src/equicordplugins/anammox/index.ts
index 275aaedf..f77ce705 100644
--- a/src/equicordplugins/anammox/index.ts
+++ b/src/equicordplugins/anammox/index.ts
@@ -36,8 +36,8 @@ export const settings = definePluginSettings({
});
export default definePlugin({
- name: "Anammox",
- description: "A microbial process that plays an important part in the nitrogen cycle",
+ name: "RemoveCrap",
+ description: "Remove Discord's bloatware from your client.",
authors: [Devs.Kyuuhachi],
settings,
diff --git a/src/equicordplugins/betterforwardmeta/.prettierrc.json b/src/equicordplugins/betterforwardmeta/.prettierrc.json
new file mode 100644
index 00000000..c92f75eb
--- /dev/null
+++ b/src/equicordplugins/betterforwardmeta/.prettierrc.json
@@ -0,0 +1,6 @@
+{
+ "useTabs": false,
+ "tabWidth": 4,
+ "semi": true,
+ "singleQuote": false
+}
diff --git a/src/equicordplugins/betterforwardmeta/LICENSE b/src/equicordplugins/betterforwardmeta/LICENSE
new file mode 100644
index 00000000..e69de29b
diff --git a/src/equicordplugins/betterforwardmeta/index.tsx b/src/equicordplugins/betterforwardmeta/index.tsx
new file mode 100644
index 00000000..c7b48853
--- /dev/null
+++ b/src/equicordplugins/betterforwardmeta/index.tsx
@@ -0,0 +1,99 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 nin0
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import "./style.css";
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+import { ChannelStore, DateUtils, GuildStore, IconUtils, NavigationRouter, Popout, SelectedGuildStore, SnowflakeUtils, Text, UserStore, useStateFromStores } from "@webpack/common";
+
+import { ArrowSvg, checkForIconExistence, cl, ServerProfileComponent } from "./utils";
+
+export default definePlugin({
+ name: "BetterForwardMeta",
+ description: "Access server profile under forwarded messages (if available) and always show time",
+ authors: [Devs.nin0dev],
+ ForwardFooter(message: any) {
+ const { guild_id, channel_id, message_id } = message.message.messageReference;
+ const guild = useStateFromStores([GuildStore], () => GuildStore.getGuild(guild_id));
+ const channel = useStateFromStores([ChannelStore], () => ChannelStore.getChannel(channel_id));
+
+ return
+ {
+ guild_id && <>
+ {
+ guild_id !== SelectedGuildStore.getGuildId() &&
}
+ >
+ {popoutProps =>
+ {
+ checkForIconExistence(guild) &&

+ }
+
{guild ? guild.name : "View server"}
+
+
+ }
+
+ }
+ >
+ }
+ {
+ channel &&
NavigationRouter.transitionTo(`/channels/${guild_id ?? "@me"}/${channel_id}/${message_id}`)} >
+
{(() => {
+ /*
+ - Text channel
+ - Voice channel
+ - Announcement channel
+ - Stage channel
+ - Forum channel
+ - Media channel
+ */
+ if ([0, 2, 5, 13, 15, 16].includes(channel.type)) return `#${channel.name}`;
+ // DMs
+ if (channel.type === 1) return `@${(() => {
+ const user = UserStore.getUser(channel.recipients[0]);
+ // @ts-expect-error
+ return user.globalName || user.username;
+ })()}`;
+ // GDMs
+ if (channel.type === 3) return channel.name || (() => {
+ const users = channel.recipients.map(r => UserStore.getUser(r));
+ // @ts-expect-error
+ return users.map(u => u.globalName || u.username).join(", ");
+ })();
+ // Threads
+ if ([10, 11, 12].includes(channel.type)) return channel.name;
+ })()}
+
+
+ }
+
+
+ {DateUtils.calendarFormat(new Date(SnowflakeUtils.extractTimestamp(message_id)))}
+
+
+
;
+ },
+ patches: [
+ {
+ find: "originLabel,\" • \"",
+ replacement: {
+ match: /(let{message:\i,snapshot:\i,index:\i}=(\i))(.{0,400})return .+TEXT_LOW_CONTRAST}\)]}\)/,
+ replace: "$1$3return $self.ForwardFooter($2)"
+ }
+ }
+ ]
+});
diff --git a/src/equicordplugins/betterforwardmeta/style.css b/src/equicordplugins/betterforwardmeta/style.css
new file mode 100644
index 00000000..03325f4e
--- /dev/null
+++ b/src/equicordplugins/betterforwardmeta/style.css
@@ -0,0 +1,47 @@
+.vc-serverprofileforward-footer {
+ margin-top: 3px;
+
+ svg {
+ width: 12px !important;
+ }
+
+ .vc-serverprofileforward-footer-text {
+ font-weight: 400;
+ font-size: 14px;
+ color: var(--text-low-contrast);
+ }
+
+ .vc-serverprofileforward-guild-icon {
+ margin-top: 0;
+ margin-bottom: 0;
+ position: absolute;
+ border-radius: 4px;
+ width: 16px;
+ }
+
+ .vc-serverprofileforward-footer-element {
+ &:hover {
+ .vc-serverprofileforward-footer-text {
+ color: var(--interactive-hover);
+ }
+
+ svg path {
+ fill: var(--interactive-hover) !important;
+ }
+
+ background-color: var(--background-modifier-hover);
+ cursor: pointer;
+ }
+
+ &:first-of-type {
+ margin-left: 0;
+ }
+
+ align-items: center;
+ align-self: flex-start;
+ display: inline-flex;
+ padding: 1px 0;
+ border-radius: 4px;
+ margin-left: 4px;
+ }
+}
diff --git a/src/equicordplugins/betterforwardmeta/utils.tsx b/src/equicordplugins/betterforwardmeta/utils.tsx
new file mode 100644
index 00000000..cc62147f
--- /dev/null
+++ b/src/equicordplugins/betterforwardmeta/utils.tsx
@@ -0,0 +1,20 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { classNameFactory } from "@api/Styles";
+import { findComponentByCodeLazy } from "@webpack";
+import { Guild } from "discord-types/general";
+
+export const ServerProfileComponent = findComponentByCodeLazy("{guildProfile:", "GUILD_PROFILE");
+export const cl = classNameFactory("vc-serverprofileforward-");
+
+export const ArrowSvg = () => ;
+
+export const checkForIconExistence = (guild: Guild) => {
+ if (!guild) return false;
+ if (!guild.icon) return false;
+ return true;
+};
diff --git a/src/equicordplugins/demonstration/index.tsx b/src/equicordplugins/demonstration/index.tsx
deleted file mode 100644
index 54c2b459..00000000
--- a/src/equicordplugins/demonstration/index.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { definePluginSettings } from "@api/Settings";
-import { classNameFactory } from "@api/Styles";
-import { Devs, EquicordDevs } from "@utils/constants";
-import { closeModal, ModalCloseButton, ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
-import definePlugin, { OptionType } from "@utils/types";
-import { Button, Forms, Switch, Text } from "@webpack/common";
-
-// definitely not stolen from glide :P
-async function injectCSS() {
- var elementToRemove = document.getElementById("DemonstrationStyle");
- if (elementToRemove) {
- elementToRemove.remove();
- }
- const styleElement = document.createElement("style");
- styleElement.id = "DemonstrationStyle";
- const content = await fetch("https://minidiscordthemes.github.io/Demonstration/Demonstration.theme.css").then(e => e.text());
- styleElement.textContent = content;
- document.documentElement.appendChild(styleElement);
-}
-
-const validKeycodes = [
- "Backspace", "Tab", "Enter", "ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "Pause", "CapsLock",
- "Escape", "Space", "PageUp", "PageDown", "End", "Home", "ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown", "PrintScreen", "Insert",
- "Delete", "Digit0", "Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9", "KeyA", "KeyB", "KeyC",
- "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT",
- "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ", "MetaLeft", "MetaRight", "ContextMenu", "Numpad0", "Numpad1", "Numpad2", "Numpad3",
- "Numpad4", "Numpad5", "Numpad6", "Numpad7", "Numpad8", "Numpad9", "NumpadMultiply", "NumpadAdd", "NumpadSubtract", "NumpadDecimal",
- "NumpadDivide", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "NumLock", "ScrollLock"
-];
-
-const settings = definePluginSettings({
- keyBind: {
- description: "The key to toggle the theme when pressed",
- type: OptionType.STRING,
- default: "F6",
- isValid: (value: string) => {
- if (validKeycodes.includes(value)) {
- return true;
- }
- return false;
- }
- },
- soundVolume: {
- description: "How loud the toggle sound is (0 to disable)",
- type: OptionType.SLIDER,
- default: 0.5,
- markers: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
- },
- showConfirmationModal: {
- description: "Show modal to remind shortcut",
- type: OptionType.BOOLEAN,
- default: true,
- }
-});
-
-function ToggleModal() {
- const value = !settings.use(["showConfirmationModal"]).showConfirmationModal;
- return (
- { settings.store.showConfirmationModal = !v; }}>
- Disable modal?
-
- );
-}
-
-function handleToggle() {
- const style = document.getElementById("DemonstrationStyle");
- if (style != null) {
- style.remove();
- playSound("https://github.com/Equicord/Equibored/raw/main/sounds/demonstration/wp5rpz.wav");
- }
- else {
- if (settings.store.showConfirmationModal) {
- const cl = classNameFactory("vc-demonstration-modal");
-
- const key = openModal(props => (
-
-
- Demonstration
- closeModal(key)}>
-
-
-
- This will censor all text! To disable this, remember the shortcut:
-
-
- {settings.store.keyBind}
-
-
-
-
-
- ));
- } else {
- injectCSS();
- playSound("https://github.com/Equicord/Equibored/raw/main/sounds/demonstration/ckz46t.wav");
- }
- }
-}
-
-function handleKeydown(event: KeyboardEvent) {
- if (event.code !== settings.store.keyBind) { return; }
- handleToggle();
-}
-
-async function playSound(url: string) {
- const audio = new Audio(url);
- audio.volume = settings.store.soundVolume;
- await audio.play().catch(error => {
- console.error("Error playing sound:", error);
- });
- audio.remove();
-}
-
-export default definePlugin({
- name: "Demonstration",
- description: "Plugin for taking theme screenshots - censors identifying images and text.",
- authors: [Devs.Samwich, EquicordDevs.Panniku],
- settings,
- toolboxActions: {
- "Toggle Demonstration": (() => handleToggle())
- },
- settingsAboutComponent: () => {
- return (
- <>
- To change your keycode, check out this tool!
- >
- );
- },
- start() {
- document.addEventListener("keydown", handleKeydown);
- },
- stop() {
- document.removeEventListener("keydown", handleKeydown);
- }
-});
diff --git a/src/equicordplugins/forceRoleColor/index.tsx b/src/equicordplugins/forceRoleColor/index.tsx
new file mode 100644
index 00000000..1b1b309f
--- /dev/null
+++ b/src/equicordplugins/forceRoleColor/index.tsx
@@ -0,0 +1,179 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { definePluginSettings } from "@api/Settings";
+import { Devs } from "@utils/constants";
+import definePlugin, { OptionType } from "@utils/types";
+import { Text } from "@webpack/common";
+import { Common } from "webpack";
+
+type Author = {
+ nick: string;
+ colorString: string;
+ colorStrings: {
+ primaryColor?: string;
+ secondaryColor?: string;
+ tertiaryColor?: string;
+ };
+ colorRoleName: string;
+ guildId?: string;
+};
+
+const settings = definePluginSettings({
+ hint: {
+ type: OptionType.COMPONENT,
+ component: function () {
+ return
+ Colors must be in hex (in the format of #XXXXXX) i.e. #ff0000, #123456, etc.
+ Primary: Controls base color
+ Secondary & Tertiary: Set for gradient
+
Switch channels for the color to update
+
+ Gradient role colors require the experiment 2025-03_enhanced_role_colors
to be enabled!
+
+ ;
+ }
+ },
+ primaryColor: {
+ type: OptionType.STRING,
+ description: "",
+ default: undefined,
+ placeholder: "#000000"
+ },
+ secondaryColor: {
+ type: OptionType.STRING,
+ description: "",
+ default: undefined,
+ placeholder: "#000000"
+ },
+ tertiaryColor: {
+ type: OptionType.STRING,
+ description: "",
+ default: undefined,
+ placeholder: "#000000"
+ },
+ dmsOnly: {
+ type: OptionType.BOOLEAN,
+ description: "Applies your color only in DMs",
+ default: false,
+ }
+});
+
+export default definePlugin({
+ name: "ForceRoleColor",
+ description: "Forces a specific role color on yourself globally",
+ authors: [Devs.surgedevs],
+ settings,
+ patches: [
+ {
+ find: ".tertiaryColor,roleStyle:\"username\",includeConvenienceGlow:!0",
+ replacement: [
+ // Override message author role color
+ {
+ match: /(?<=let{author:\i,message:)(\i)(.*?)(?<=colorStrings:\i,colorRoleName:\i}=)(\i)/,
+ replace: "$1$2$self.getColorsForMessages($1,$3)"
+ },
+ // Always enable gradient roles
+ {
+ match: /\(0,\i\.\i\)\(null!=\i\?\i:\i,"BaseUsername"\)/,
+ replace: "true"
+ }
+ ]
+ },
+ {
+ find: ".name,roleColors:",
+ replacement: [
+ // Override member list role color
+ {
+ match: /(?<=let{colorRoleName.*?colorString:(\i).*?roleColorStrings:(\i).*?user:(\i).*?}=\i;)/,
+ replace: "let{colorString:_$1,roleColorStrings:_$2}=$self.getColorsForMemberList($3,$1,$2);$1=_$1;$2=_$2;"
+ }
+ ]
+ },
+ // @TODO: find a better `find` here ??? not sure how stable this is lmao
+ {
+ find: ".showThreadPromptOnReply&&",
+ replacement: [
+ // Override reply role color, uses getColorsForMessage since the keys are the same
+ {
+ match: /(?<=message:(\i).*?colorString:(\i).*?,(\i)=\(0,\i\.\i\)\(\i,\i\)),/,
+ replace: ";let{colorString:_$2,colorStrings:_$3}=$self.getColorsForMessages($1,{colorString:$2,colorStrings:$3});$2=_$2;$3=_$3;let "
+ }
+ ]
+ },
+ {
+ find: "memberNameText}),(0,",
+ replacement: [
+ // Override color in guild member search
+ {
+ match: /(?<=let{member:(\i),user:(\i).*(\i)=\(0,.*?colorStrings\);)/,
+ replace: "$1=$self.getColorsForMemberSearch($2,$1);$3=$1.colorStrings;"
+ }
+ ]
+ }
+ ],
+
+ getColorsForMessages(message: any, old: Author): Author {
+ if (
+ message.author.id !== Common.UserStore.getCurrentUser().id
+ || (settings.store.dmsOnly && old.guildId)
+ ) {
+ return old;
+ }
+
+ return {
+ ...old,
+ colorString: settings.store.primaryColor || old?.colorString,
+ colorStrings: {
+ primaryColor: settings.store.primaryColor || old?.colorStrings?.primaryColor,
+ secondaryColor: settings.store.secondaryColor || old?.colorStrings?.secondaryColor,
+ tertiaryColor: settings.store.tertiaryColor || old?.colorStrings?.tertiaryColor,
+ }
+ };
+ },
+
+ getColorsForMemberList(user: any, colorString: string, old: any) {
+ if (user.id !== Common.UserStore.getCurrentUser().id || settings.store.dmsOnly) {
+ return {
+ colorString,
+ roleColorStrings: old
+ };
+ }
+
+ return {
+ colorString: settings.store.primaryColor || colorString,
+ roleColorStrings: {
+ primaryColor: settings.store.primaryColor || old?.primaryColor,
+ secondaryColor: settings.store.secondaryColor || old?.secondaryColor,
+ tertiaryColor: settings.store.tertiaryColor || old?.tertiaryColor,
+ }
+ };
+ },
+
+ getColorsForMemberSearch(user: any, old: any) {
+ // can just call getColorsForMessages since keys are the same
+ return this.getColorsForMessages({ author: user }, old);
+ }
+});
diff --git a/src/equicordplugins/lastActive/index.tsx b/src/equicordplugins/lastActive/index.tsx
deleted file mode 100644
index 838d5759..00000000
--- a/src/equicordplugins/lastActive/index.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2025 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { NavContextMenuPatchCallback } from "@api/ContextMenu";
-import { EquicordDevs } from "@utils/constants";
-import definePlugin from "@utils/types";
-import { Menu, MessageActions, MessageStore, NavigationRouter, Toasts, UserStore } from "@webpack/common";
-
-async function findLastMessageFromUser(channelId: string, userId: string) {
- try {
- const messageCollection = MessageStore.getMessages(channelId);
- let messages = messageCollection?.toArray() || [];
- let userMessage = messages.filter(m => m?.author?.id === userId).pop();
- if (userMessage) return userMessage.id;
- try {
- await MessageActions.fetchMessages({
- channelId: channelId,
- limit: 50
- });
-
- const updatedCollection = MessageStore.getMessages(channelId);
- messages = updatedCollection?.toArray() || [];
- userMessage = messages.filter(m => m?.author?.id === userId).pop();
-
- if (userMessage) return userMessage.id;
- } catch (fetchError) {
- console.error("Error fetching messages:", fetchError);
- }
-
- Toasts.show({
- type: Toasts.Type.FAILURE,
- message: "Couldn't find any recent messages from this user.",
- id: Toasts.genId()
- });
- return null;
- } catch (error) {
- console.error("Error finding last message:", error);
- Toasts.show({
- type: Toasts.Type.FAILURE,
- message: "Failed to find messages. Check console for details.",
- id: Toasts.genId()
- });
- return null;
- }
-}
-async function jumpToLastActive(channel: any, targetUserId?: string) {
- try {
- if (!channel) {
- Toasts.show({
- type: Toasts.Type.FAILURE,
- message: "Channel information not available.",
- id: Toasts.genId()
- });
- return;
- }
- const guildId = channel.guild_id !== null ? channel.guild_id : "@me";
- const channelId = channel.id;
- let userId: string;
- if (targetUserId) {
-
- userId = targetUserId;
- } else {
- const currentUser = UserStore.getCurrentUser();
- userId = currentUser.id;
- }
- const messageId = await findLastMessageFromUser(channelId, userId);
- if (messageId) {
- const url = `/channels/${guildId}/${channelId}/${messageId}`;
- NavigationRouter.transitionTo(url);
- }
- } catch (error) {
- console.error("Error in jumpToLastActive:", error);
- Toasts.show({
- type: Toasts.Type.FAILURE,
- message: "Failed to jump to message. Check console for details.",
- id: Toasts.genId()
- });
- }
-}
-const ChannelContextMenuPatch: NavContextMenuPatchCallback = (children, { channel }) => {
- children.push(
- Your Last Message}
- icon={LastActiveIcon}
- action={() => {
- jumpToLastActive(channel);
- }}
- />
- );
-};
-const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user, channel }) => {
- if (!channel || !user?.id) return;
-
- children.push(
- User's Last Message}
- icon={UserLastActiveIcon}
- action={() => {
- jumpToLastActive(channel, user.id);
- }}
- />
- );
-};
-export function UserLastActiveIcon() {
- return (
-
- );
-}
-
-export function LastActiveIcon() {
- return (
-
- );
-}
-export default definePlugin({
- name: "LastActive",
- description: "A plugin to jump to last active message from yourself or another user in a channel/server.",
- authors: [EquicordDevs.Crxa],
- contextMenus: {
- "channel-context": ChannelContextMenuPatch,
- "user-context": UserContextMenuPatch,
- "thread-context": ChannelContextMenuPatch
- }
-});
diff --git a/src/equicordplugins/meow/index.tsx b/src/equicordplugins/meow/index.tsx
deleted file mode 100644
index 3a0d9309..00000000
--- a/src/equicordplugins/meow/index.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
-import { Devs } from "@utils/constants";
-import { getCurrentChannel, sendMessage } from "@utils/discord";
-import definePlugin from "@utils/types";
-
-async function handleButtonClick() {
- // @ts-expect-error typing issue
- sendMessage(getCurrentChannel().id, { content: "meow" });
-}
-
-const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
- if (!isMainChat) return null;
- return (
-
-
-
- );
-};
-
-export default definePlugin({
- name: "Meow",
- description: "Adds a chatbar button to meow in chat",
- authors:
- [Devs.Samwich],
- start: () => addChatBarButton("Meow", ChatBarIcon),
- stop: () => removeChatBarButton("Meow")
-});
diff --git a/src/equicordplugins/messageLoggerEnhanced/LoggedMessageManager.ts b/src/equicordplugins/messageLoggerEnhanced/LoggedMessageManager.ts
deleted file mode 100644
index e83a30e6..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/LoggedMessageManager.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { Flogger, settings } from ".";
-import { addMessageIDB, db, DBMessageStatus, deleteMessagesBulkIDB, getOldestMessagesIDB } from "./db";
-import { LoggedMessage, LoggedMessageJSON } from "./types";
-import { cleanupMessage } from "./utils";
-import { cacheMessageImages } from "./utils/saveImage";
-
-export const addMessage = async (message: LoggedMessage | LoggedMessageJSON, status: DBMessageStatus) => {
- if (settings.store.saveImages && status === DBMessageStatus.DELETED)
- await cacheMessageImages(message);
- const finalMessage = cleanupMessage(message);
-
- await addMessageIDB(finalMessage, status);
-
- if (settings.store.messageLimit > 0) {
- const currentMessageCount = await db.count("messages");
- if (currentMessageCount > settings.store.messageLimit) {
- const messagesToDelete = currentMessageCount - settings.store.messageLimit;
- if (messagesToDelete <= 0 || messagesToDelete >= settings.store.messageLimit) return;
-
- const oldestMessages = await getOldestMessagesIDB(messagesToDelete);
-
- Flogger.info(`Deleting ${messagesToDelete} oldest messages`);
- await deleteMessagesBulkIDB(oldestMessages.map(m => m.message_id));
- }
- }
-};
diff --git a/src/equicordplugins/messageLoggerEnhanced/components/FolderSelectInput.tsx b/src/equicordplugins/messageLoggerEnhanced/components/FolderSelectInput.tsx
deleted file mode 100644
index ab71ff79..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/components/FolderSelectInput.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { classNameFactory } from "@api/Styles";
-import { copyWithToast } from "@utils/misc";
-import { Button, Forms, Toasts } from "@webpack/common";
-
-import { Native, settings } from "..";
-import { DEFAULT_IMAGE_CACHE_DIR } from "../utils/constants";
-
-const cl = classNameFactory("folder-upload");
-
-function createDirSelector(settingKey: "logsDir" | "imageCacheDir", successMessage: string) {
- return function DirSelector({ option }) {
- if (IS_WEB) return null;
-
- return (
-
- {option.description}
-
-
- );
- };
-}
-
-export const ImageCacheDir = createDirSelector("imageCacheDir", "Successfully updated Image Cache Dir");
-export const LogsDir = createDirSelector("logsDir", "Successfully updated Logs Dir");
-
-interface Props {
- settingsKey: "imageCacheDir" | "logsDir",
- successMessage: string,
-}
-
-export function SelectFolderInput({ settingsKey, successMessage }: Props) {
- const path = settings.store[settingsKey];
-
- function getDirName(path: string) {
- const parts = path.split("\\").length > 1 ? path.split("\\") : path.split("/");
-
- return parts.slice(parts.length - 2, parts.length).join("\\");
- }
-
- async function onFolderSelect() {
- try {
- const res = await Native.chooseDir(settingsKey);
- settings.store[settingsKey] = res;
-
- return Toasts.show({
- id: Toasts.genId(),
- type: Toasts.Type.SUCCESS,
- message: successMessage
- });
- } catch (err) {
- Toasts.show({
- id: Toasts.genId(),
- type: Toasts.Type.FAILURE,
- message: "Failed to update directory"
- });
- }
- }
-
- return (
-
-
copyWithToast(path)} className={cl("-input")}>
- {path == null || path === DEFAULT_IMAGE_CACHE_DIR ? "Choose Folder" : getDirName(path)}
-
-
-
- );
-
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/components/LogsButton.tsx b/src/equicordplugins/messageLoggerEnhanced/components/LogsButton.tsx
deleted file mode 100644
index 2a12ee8d..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/components/LogsButton.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { findComponentByCodeLazy } from "@webpack";
-
-import { openLogModal } from "./LogsModal";
-
-const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
-
-export function OpenLogsIcon() {
- return (
-
- );
-}
-
-export function OpenLogsButton() {
- return (
- openLogModal()}
- tooltip={"Open Logs"}
- icon={OpenLogsIcon}
- />
- );
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/components/LogsModal.tsx b/src/equicordplugins/messageLoggerEnhanced/components/LogsModal.tsx
deleted file mode 100644
index 6e8ce014..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/components/LogsModal.tsx
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { classNameFactory } from "@api/Styles";
-import { Flex } from "@components/Flex";
-import { InfoIcon } from "@components/Icons";
-import { openUserProfile } from "@utils/discord";
-import { copyWithToast } from "@utils/misc";
-import { closeAllModals, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
-import { LazyComponent } from "@utils/react";
-import { find, findByCode, findByCodeLazy } from "@webpack";
-import { Alerts, Button, ChannelStore, ContextMenuApi, FluxDispatcher, Menu, NavigationRouter, React, TabBar, Text, TextInput, Tooltip, useMemo, useRef, useState } from "@webpack/common";
-import { User } from "discord-types/general";
-
-import { clearMessagesIDB, DBMessageRecord, deleteMessageIDB, deleteMessagesBulkIDB } from "../db";
-import { settings } from "../index";
-import { LoggedMessage, LoggedMessageJSON } from "../types";
-import { messageJsonToMessageClass } from "../utils";
-import { importLogs } from "../utils/settingsUtils";
-import { useMessages } from "./hooks";
-
-export interface MessagePreviewProps {
- className: string;
- author: User;
- message: LoggedMessage;
- compact: boolean;
- isGroupStart: boolean;
- hideSimpleEmbedContent: boolean;
-
- childrenAccessories: any;
-}
-
-export interface ChildrenAccProops {
- channelMessageProps: {
- compact: boolean;
- channel: any;
- message: LoggedMessage;
- groupId: string;
- id: string;
- isLastItem: boolean;
- isHighlight: boolean;
- renderContentOnly: boolean;
- };
- hasSpoilerEmbeds: boolean;
- isInteracting: boolean;
- isAutomodBlockedMessage: boolean;
- showClydeAiEmbeds: boolean;
-}
-
-const PrivateChannelRecord = findByCodeLazy(".is_message_request_timestamp,");
-const MessagePreview = LazyComponent(() => find(m => m?.type?.toString().includes("previewLinkTarget:") && !m?.type?.toString().includes("HAS_THREAD")));
-const ChildrenAccessories = LazyComponent(() => findByCode("channelMessageProps:{message:"));
-
-const cl = classNameFactory("msg-logger-modal-");
-
-export enum LogTabs {
- DELETED = "Deleted",
- EDITED = "Edited",
- GHOST_PING = "Ghost Pinged"
-}
-
-interface Props {
- modalProps: ModalProps;
- initalQuery?: string;
-}
-
-export function LogsModal({ modalProps, initalQuery }: Props) {
- const [currentTab, setCurrentTab] = useState(LogTabs.DELETED);
- const [queryEh, setQuery] = useState(initalQuery ?? "");
- const [sortNewest, setSortNewest] = useState(settings.store.sortNewest);
- const [numDisplayedMessages, setNumDisplayedMessages] = useState(settings.store.messagesToDisplayAtOnceInLogs);
- const contentRef = useRef(null);
-
- const { messages, total, statusTotal, pending, reset } = useMessages(queryEh, currentTab, sortNewest, numDisplayedMessages);
-
- return (
-
-
- setQuery(e)} style={{ width: "100%" }} placeholder="Filter Messages" />
- {
- setCurrentTab(e);
- setNumDisplayedMessages(settings.store.messagesToDisplayAtOnceInLogs);
- contentRef.current?.firstElementChild?.scrollTo(0, 0);
- // forceUpdate();
- }}
- >
-
- Deleted
-
-
- Edited
-
-
- Ghost Pinged
-
-
-
-
- {
- modalProps.transitionState === 1 &&
-
- {messages != null && total === 0 && (
-
- )}
-
- {!pending && messages != null && (
- = settings.store.messagesToDisplayAtOnceInLogs}
- tab={currentTab}
- sortNewest={sortNewest}
- reset={reset}
- handleLoadMore={() => setNumDisplayedMessages(e => e + settings.store.messagesToDisplayAtOnceInLogs)}
- />
- )}
-
- }
-
-
-
-
-
-
-
- );
-}
-
-interface LogContentProps {
- sortNewest: boolean;
- tab: LogTabs;
- visibleMessages: DBMessageRecord[];
- canLoadMore: boolean;
- reset: () => void;
- handleLoadMore: () => void;
-}
-
-function LogsContent({ visibleMessages, canLoadMore, sortNewest, tab, reset, handleLoadMore }: LogContentProps) {
- if (visibleMessages.length === 0)
- return ;
-
- return (
-
- {visibleMessages
- .map(({ message }, i) => (
-
- ))}
- {
- canLoadMore &&
-
- }
-
- );
-}
-
-const LogsContentMemo = LazyComponent(() => React.memo(LogsContent));
-
-
-function NoResults({ tab }: { tab: LogTabs; }) {
- const generateSuggestedTabs = (tab: LogTabs) => {
- switch (tab) {
- case LogTabs.DELETED:
- return { nextTab: LogTabs.EDITED, lastTab: LogTabs.GHOST_PING };
- case LogTabs.EDITED:
- return { nextTab: LogTabs.GHOST_PING, lastTab: LogTabs.DELETED };
- case LogTabs.GHOST_PING:
- return { nextTab: LogTabs.DELETED, lastTab: LogTabs.EDITED };
- default:
- return { nextTab: "", lastTab: "" };
- }
- };
-
- const { nextTab, lastTab } = generateSuggestedTabs(tab);
-
- return (
-
-
- No results in {tab}.
-
-
- Maybe try {nextTab} or {lastTab}
-
-
- );
-}
-
-function EmptyLogs({ hasQuery, reset: forceUpdate }: { hasQuery: boolean; reset: () => void; }) {
- return (
-
-
-
-
- Empty eh
-
-
- {!hasQuery && (
- <>
-
- {({ onMouseEnter, onMouseLeave }) => (
-
-
-
- )}
-
-
-
- >
- )}
-
-
- );
-
-}
-
-interface LMessageProps {
- log: { message: LoggedMessageJSON; };
- isGroupStart: boolean,
- reset: () => void;
-}
-function LMessage({ log, isGroupStart, reset, }: LMessageProps) {
- const message = useMemo(() => messageJsonToMessageClass(log), [log]);
-
- // console.log(message);
-
- if (!message) return null;
-
- return (
- {
- ContextMenuApi.openContextMenu(e, () =>
-
FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
- aria-label="Message Logger"
- >
-
- {
- NavigationRouter.transitionTo(`/channels/${ChannelStore.getChannel(message.channel_id)?.guild_id ?? "@me"}/${message.channel_id}${message.id ? "/" + message.id : ""}`);
- closeAllModals();
- }}
- />
- {
- closeAllModals();
- openUserProfile(message.author.id);
- }}
- />
-
- copyWithToast(message.content)}
- />
-
- copyWithToast(message.author.id)}
- />
-
- copyWithToast(message.id)}
- />
-
- copyWithToast(message.channel_id)}
- />
-
- {
- log.message.guildId != null
- && (
- copyWithToast(log.message.guildId!)}
- />
- )
- }
-
-
- deleteMessageIDB(log.message.id).then(() => reset())
- }
- />
-
-
- );
- }}>
-
- }
-
- />
-
- );
-}
-
-export const openLogModal = (initalQuery?: string) => openModal(modalProps => );
-
-function isGroupStart(
- currentMessage: LoggedMessageJSON | undefined,
- previousMessage: LoggedMessageJSON | undefined,
- sortNewest: boolean
-) {
- if (!currentMessage || !previousMessage) return true;
-
- if (currentMessage.id === previousMessage.id) return true;
-
- const [newestMessage, oldestMessage] = sortNewest
- ? [previousMessage, currentMessage]
- : [currentMessage, previousMessage];
-
- if (newestMessage.author.id !== oldestMessage.author.id) return true;
-
- const timeDifferenceInMinutes = Math.abs(
- (new Date(newestMessage.timestamp)?.getTime() - new Date(oldestMessage.timestamp)?.getTime()) / (1000 * 60)
- );
-
- return timeDifferenceInMinutes >= 5;
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/components/hooks.ts b/src/equicordplugins/messageLoggerEnhanced/components/hooks.ts
deleted file mode 100644
index fad128cf..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/components/hooks.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { useEffect, useState } from "@webpack/common";
-
-import { countMessagesByStatusIDB, countMessagesIDB, DBMessageRecord, DBMessageStatus, getDateStortedMessagesByStatusIDB } from "../db";
-import { doesMatch, tokenizeQuery } from "../utils/parseQuery";
-import { LogTabs } from "./LogsModal";
-
-function useDebouncedValue(value: T, delay: number): T {
- const [debouncedValue, setDebouncedValue] = useState(value);
-
- useEffect(() => {
- const handler = setTimeout(() => {
- setDebouncedValue(value);
- }, delay);
-
- return () => {
- clearTimeout(handler);
- };
- }, [value, delay]);
-
- return debouncedValue;
-}
-
-// this is so shit
-export function useMessages(query: string, currentTab: LogTabs, sortNewest: boolean, numDisplayedMessages: number) {
- // only for initial load
- const [pending, setPending] = useState(true);
- const [messages, setMessages] = useState([]);
- const [statusTotal, setStatusTotal] = useState(0);
- const [total, setTotal] = useState(0);
-
- const debouncedQuery = useDebouncedValue(query, 300);
-
- useEffect(() => {
- countMessagesIDB().then(x => setTotal(x));
- }, [pending]);
-
- useEffect(() => {
- let isMounted = true;
-
- const loadMessages = async () => {
- const status = getStatus(currentTab);
-
- if (debouncedQuery === "") {
- const [messages, statusTotal] = await Promise.all([
- getDateStortedMessagesByStatusIDB(sortNewest, numDisplayedMessages, status),
- countMessagesByStatusIDB(status),
- ]);
-
-
- if (isMounted) {
- setMessages(messages);
- setStatusTotal(statusTotal);
- }
-
- setPending(false);
- } else {
- const allMessages = await getDateStortedMessagesByStatusIDB(sortNewest, Number.MAX_SAFE_INTEGER, status);
- const { queries, rest } = tokenizeQuery(debouncedQuery);
-
- const filteredMessages = allMessages.filter(record => {
- for (const query of queries) {
- const matching = doesMatch(query.key, query.value, record.message);
- if (query.negate ? matching : !matching) {
- return false;
- }
- }
-
- return rest.every(r =>
- record.message.content.toLowerCase().includes(r.toLowerCase())
- );
- });
-
- if (isMounted) {
- setMessages(filteredMessages);
- setStatusTotal(Number.MAX_SAFE_INTEGER);
- }
- setPending(false);
- }
- };
-
- loadMessages();
-
- return () => {
- isMounted = false;
- };
-
- }, [debouncedQuery, sortNewest, numDisplayedMessages, currentTab, pending]);
-
-
- return { messages, statusTotal, total, pending, reset: () => setPending(true) };
-}
-
-
-function getStatus(currentTab: LogTabs) {
- switch (currentTab) {
- case LogTabs.DELETED:
- return DBMessageStatus.DELETED;
- case LogTabs.EDITED:
- return DBMessageStatus.EDITED;
- default:
- return DBMessageStatus.GHOST_PINGED;
- }
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/db.ts b/src/equicordplugins/messageLoggerEnhanced/db.ts
deleted file mode 100644
index 6288f1d5..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/db.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { DBSchema, IDBPDatabase, openDB } from "idb";
-
-import { LoggedMessageJSON } from "./types";
-import { getMessageStatus } from "./utils";
-import { DB_NAME, DB_VERSION } from "./utils/constants";
-import { getAttachmentBlobUrl } from "./utils/saveImage";
-
-export enum DBMessageStatus {
- DELETED = "DELETED",
- EDITED = "EDITED",
- GHOST_PINGED = "GHOST_PINGED",
-}
-
-export interface DBMessageRecord {
- message_id: string;
- channel_id: string;
- status: DBMessageStatus;
- message: LoggedMessageJSON;
-}
-
-export interface MLIDB extends DBSchema {
- messages: {
- key: string;
- value: DBMessageRecord;
- indexes: {
- by_channel_id: string;
- by_status: DBMessageStatus;
- by_timestamp: string;
- by_timestamp_and_message_id: [string, string];
- };
- };
-
-}
-
-export let db: IDBPDatabase;
-export const cachedMessages = new Map();
-
-// this is probably not the best way to do this
-async function cacheRecords(records: DBMessageRecord[]) {
- for (const r of records) {
- cacheRecord(r);
-
- for (const att of r.message.attachments) {
- const blobUrl = await getAttachmentBlobUrl(att);
- if (blobUrl) {
- att.url = blobUrl + "#";
- att.proxy_url = blobUrl + "#";
- }
- }
- }
- return records;
-}
-
-async function cacheRecord(record?: DBMessageRecord | null) {
- if (!record) return record;
-
- cachedMessages.set(record.message_id, record.message);
- return record;
-}
-
-export async function initIDB() {
- db = await openDB(DB_NAME, DB_VERSION, {
- upgrade(db) {
- const messageStore = db.createObjectStore("messages", { keyPath: "message_id" });
- messageStore.createIndex("by_channel_id", "channel_id");
- messageStore.createIndex("by_status", "status");
- messageStore.createIndex("by_timestamp", "message.timestamp");
- messageStore.createIndex("by_timestamp_and_message_id", ["channel_id", "message.timestamp"]);
- }
- });
-}
-initIDB();
-
-export async function hasMessageIDB(message_id: string) {
- return cachedMessages.has(message_id) || (await db.count("messages", message_id)) > 0;
-}
-
-export async function countMessagesIDB() {
- return db.count("messages");
-}
-
-export async function countMessagesByStatusIDB(status: DBMessageStatus) {
- return db.countFromIndex("messages", "by_status", status);
-}
-
-export async function getAllMessagesIDB() {
- return cacheRecords(await db.getAll("messages"));
-}
-
-export async function getMessagesForChannelIDB(channel_id: string) {
- return cacheRecords(await db.getAllFromIndex("messages", "by_channel_id", channel_id));
-}
-
-export async function getMessageIDB(message_id: string) {
- return cacheRecord(await db.get("messages", message_id));
-}
-
-export async function getMessagesByStatusIDB(status: DBMessageStatus) {
- return cacheRecords(await db.getAllFromIndex("messages", "by_status", status));
-}
-
-export async function getOldestMessagesIDB(limit: number) {
- return cacheRecords(await db.getAllFromIndex("messages", "by_timestamp", undefined, limit));
-}
-
-export async function getDateStortedMessagesByStatusIDB(newest: boolean, limit: number, status: DBMessageStatus) {
- const tx = db.transaction("messages", "readonly");
- const { store } = tx;
- const index = store.index("by_status");
-
- const direction = newest ? "prev" : "next";
- const cursor = await index.openCursor(IDBKeyRange.only(status), direction);
-
- if (!cursor) {
- console.log("No messages found");
- return [];
- }
-
- const messages: DBMessageRecord[] = [];
- for await (const c of cursor) {
- messages.push(c.value);
- if (messages.length >= limit) break;
- }
-
- return cacheRecords(messages);
-}
-
-export async function getMessagesByChannelAndAfterTimestampIDB(channel_id: string, start: string) {
- const tx = db.transaction("messages", "readonly");
- const { store } = tx;
- const index = store.index("by_timestamp_and_message_id");
-
- const cursor = await index.openCursor(IDBKeyRange.bound([channel_id, start], [channel_id, "\uffff"]));
-
- if (!cursor) {
- console.log("No messages found in range");
- return [];
- }
-
- const messages: DBMessageRecord[] = [];
- for await (const c of cursor) {
- messages.push(c.value);
- }
-
- return cacheRecords(messages);
-}
-
-export async function addMessageIDB(message: LoggedMessageJSON, status: DBMessageStatus) {
- await db.put("messages", {
- channel_id: message.channel_id,
- message_id: message.id,
- status,
- message,
- });
-
- cachedMessages.set(message.id, message);
-}
-
-export async function addMessagesBulkIDB(messages: LoggedMessageJSON[], status?: DBMessageStatus) {
- const tx = db.transaction("messages", "readwrite");
- const { store } = tx;
-
- await Promise.all([
- ...messages.map(message => store.add({
- channel_id: message.channel_id,
- message_id: message.id,
- status: status ?? getMessageStatus(message),
- message,
- })),
- tx.done
- ]);
-
- messages.forEach(message => cachedMessages.set(message.id, message));
-}
-
-
-export async function deleteMessageIDB(message_id: string) {
- await db.delete("messages", message_id);
-
- cachedMessages.delete(message_id);
-}
-
-export async function deleteMessagesBulkIDB(message_ids: string[]) {
- const tx = db.transaction("messages", "readwrite");
- const { store } = tx;
-
- await Promise.all([...message_ids.map(id => store.delete(id)), tx.done]);
- message_ids.forEach(id => cachedMessages.delete(id));
-}
-
-export async function clearMessagesIDB() {
- await db.clear("messages");
- cachedMessages.clear();
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/index.tsx b/src/equicordplugins/messageLoggerEnhanced/index.tsx
deleted file mode 100644
index e4c560d8..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/index.tsx
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-export const VERSION = "4.0.0";
-
-export const Native = getNative();
-
-import "./styles.css";
-
-import ErrorBoundary from "@components/ErrorBoundary";
-import { Devs } from "@utils/constants";
-import { Logger } from "@utils/Logger";
-import definePlugin from "@utils/types";
-import { findByPropsLazy } from "@webpack";
-import { FluxDispatcher, MessageStore, React, UserStore } from "@webpack/common";
-
-import { OpenLogsButton } from "./components/LogsButton";
-import { openLogModal } from "./components/LogsModal";
-import * as idb from "./db";
-import { addMessage } from "./LoggedMessageManager";
-import * as LoggedMessageManager from "./LoggedMessageManager";
-import { settings } from "./settings";
-import { FetchMessagesResponse, LoadMessagePayload, LoggedMessage, LoggedMessageJSON, MessageCreatePayload, MessageDeleteBulkPayload, MessageDeletePayload, MessageUpdatePayload } from "./types";
-import { cleanUpCachedMessage, cleanupUserObject, getNative, isGhostPinged, mapTimestamp, messageJsonToMessageClass, reAddDeletedMessages } from "./utils";
-import { removeContextMenuBindings, setupContextMenuPatches } from "./utils/contextMenu";
-import { shouldIgnore } from "./utils/index";
-import { LimitedMap } from "./utils/LimitedMap";
-import { doesMatch } from "./utils/parseQuery";
-import * as imageUtils from "./utils/saveImage";
-import * as ImageManager from "./utils/saveImage/ImageManager";
-export { settings };
-
-export const Flogger = new Logger("MessageLoggerEnhanced", "#f26c6c");
-
-export const cacheSentMessages = new LimitedMap();
-
-const cacheThing = findByPropsLazy("commit", "getOrCreate");
-
-let oldGetMessage: typeof MessageStore.getMessage;
-
-const handledMessageIds = new Set();
-async function messageDeleteHandler(payload: MessageDeletePayload & { isBulk: boolean; }) {
- if (payload.mlDeleted) {
- if (settings.store.permanentlyRemoveLogByDefault)
- await idb.deleteMessageIDB(payload.id);
-
- return;
- }
-
- if (handledMessageIds.has(payload.id)) {
- // Flogger.warn("skipping duplicate message", payload.id);
- return;
- }
-
- try {
- handledMessageIds.add(payload.id);
-
- let message: LoggedMessage | LoggedMessageJSON | null =
- oldGetMessage?.(payload.channelId, payload.id);
- if (message == null) {
- // most likely an edited message
- const cachedMessage = cacheSentMessages.get(`${payload.channelId},${payload.id}`);
- if (!cachedMessage) return; // Flogger.log("no message to save");
-
- message = { ...cacheSentMessages.get(`${payload.channelId},${payload.id}`), deleted: true } as LoggedMessageJSON;
- }
-
- const ghostPinged = isGhostPinged(message as any);
-
- if (
- shouldIgnore({
- channelId: message?.channel_id ?? payload.channelId,
- guildId: payload.guildId ?? (message as any).guildId ?? (message as any).guild_id,
- authorId: message?.author?.id,
- bot: message?.bot || message?.author?.bot,
- flags: message?.flags,
- ghostPinged,
- isCachedByUs: (message as LoggedMessageJSON).ourCache,
- webhookId: message?.webhookId
- })
- ) {
- // Flogger.log("IGNORING", message, payload);
- return FluxDispatcher.dispatch({
- type: "MESSAGE_DELETE",
- channelId: payload.channelId,
- id: payload.id,
- mlDeleted: true
- });
- }
-
-
- if (message == null || message.channel_id == null || !message.deleted) return;
- // Flogger.log("ADDING MESSAGE (DELETED)", message);
- if (payload.isBulk)
- return message;
-
- await addMessage(message, ghostPinged ? idb.DBMessageStatus.GHOST_PINGED : idb.DBMessageStatus.DELETED);
- }
- finally {
- handledMessageIds.delete(payload.id);
- }
-}
-
-async function messageDeleteBulkHandler({ channelId, guildId, ids }: MessageDeleteBulkPayload) {
- // is this bad? idk man
- const messages = [] as LoggedMessageJSON[];
- for (const id of ids) {
- const msg = await messageDeleteHandler({ type: "MESSAGE_DELETE", channelId, guildId, id, isBulk: true });
- if (msg) messages.push(msg as LoggedMessageJSON);
- }
-
- await idb.addMessagesBulkIDB(messages);
-}
-
-async function messageUpdateHandler(payload: MessageUpdatePayload) {
- const cachedMessage = cacheSentMessages.get(`${payload.message.channel_id},${payload.message.id}`);
- if (
- shouldIgnore({
- channelId: payload.message?.channel_id,
- guildId: payload.guildId ?? (payload as any).guild_id,
- authorId: payload.message?.author?.id,
- bot: (payload.message?.author as any)?.bot,
- flags: payload.message?.flags,
- ghostPinged: isGhostPinged(payload.message as any),
- isCachedByUs: cachedMessage?.ourCache ?? false
- })
- ) {
- const cache = cacheThing.getOrCreate(payload.message.channel_id);
- const message = cache.get(payload.message.id);
- if (message) {
- message.editHistory = [];
- cacheThing.commit(cache);
- }
- return;// Flogger.log("this message has been ignored", payload);
- }
-
- let message = oldGetMessage?.(payload.message.channel_id, payload.message.id) as LoggedMessage | LoggedMessageJSON | null;
-
- if (message == null) {
- // MESSAGE_UPDATE gets dispatched when emebeds change too and content becomes null
- if (cachedMessage != null && payload.message.content != null && cachedMessage.content !== payload.message.content) {
- message = {
- ...cachedMessage,
- content: payload.message.content,
- editHistory: [
- ...(cachedMessage.editHistory ?? []),
- {
- content: cachedMessage.content,
- timestamp: (new Date()).toISOString()
- }
- ]
- };
-
- cacheSentMessages.set(`${payload.message.channel_id},${payload.message.id}`, message);
- }
- }
-
- if (message == null || message.channel_id == null || message.editHistory == null || message.editHistory.length === 0) return;
-
- // Flogger.log("ADDING MESSAGE (EDITED)", message, payload);
- await addMessage(message, idb.DBMessageStatus.EDITED);
-}
-
-function messageCreateHandler(payload: MessageCreatePayload) {
- // we do this here because cache is limited and to save memory
- if (!settings.store.cacheMessagesFromServers && payload.guildId != null) {
- const ids = [payload.channelId, payload.message?.author?.id, payload.guildId];
- const isWhitelisted =
- settings.store.whitelistedIds
- .split(",")
- .some(e => ids.includes(e));
- if (!isWhitelisted) {
- return; // dont cache messages from servers when cacheMessagesFromServers is disabled and not whitelisted.
- }
- }
-
- cacheSentMessages.set(`${payload.message.channel_id},${payload.message.id}`, cleanUpCachedMessage(payload.message));
- // Flogger.log(`cached\nkey:${payload.message.channel_id},${payload.message.id}\nvalue:`, payload.message);
-}
-
-async function processMessageFetch(response: FetchMessagesResponse) {
- try {
- if (!response.ok || response.body.length === 0) {
- Flogger.error("Failed to fetch messages", response);
- return;
- }
-
- const firstMessage = response.body[response.body.length - 1];
- // console.time("fetching messages from idb");
- const messages = await idb.getMessagesByChannelAndAfterTimestampIDB(firstMessage.channel_id, firstMessage.timestamp);
- // console.timeEnd("fetching messages from idb");
-
- if (!messages.length) return;
-
- const deletedMessages = messages.filter(m => m.status === idb.DBMessageStatus.DELETED);
-
- for (const recivedMessage of response.body) {
- const record = messages.find(m => m.message_id === recivedMessage.id);
-
- if (record == null) continue;
-
- if (record.message.editHistory && record.message.editHistory.length > 0) {
- recivedMessage.editHistory = record.message.editHistory;
- }
- }
-
- const fetchUser = (id: string) => UserStore.getUser(id) || response.body.find(e => e.author.id === id);
-
- for (let i = 0, len = messages.length; i < len; i++) {
- const record = messages[i];
- if (!record) continue;
-
- const { message } = record;
-
- for (let j = 0, len2 = message.mentions.length; j < len2; j++) {
- const user = message.mentions[j];
- const cachedUser = fetchUser((user as any).id || user);
- if (cachedUser) (message.mentions[j] as any) = cleanupUserObject(cachedUser);
- }
-
- const author = fetchUser(message.author.id);
- if (!author) continue;
- (message.author as any) = cleanupUserObject(author);
- }
-
- response.body.extra = deletedMessages.map(m => m.message);
-
- } catch (e) {
- Flogger.error("Failed to fetch messages", e);
- }
-}
-
-export default definePlugin({
- name: "MessageLoggerEnhanced",
- authors: [Devs.Aria],
- description: "G'day",
- dependencies: ["MessageLogger"],
-
- patches: [
- {
- find: "_tryFetchMessagesCached",
- replacement: [
- {
- match: /(?<=\.get\({url.+?then\()(\i)=>\(/,
- replace: "async $1=>(await $self.processMessageFetch($1),"
- },
- {
- match: /(?<=type:"LOAD_MESSAGES_SUCCESS",.{1,100})messages:(\i)/,
- replace: "get messages() {return $self.coolReAddDeletedMessages($1, this);}"
- }
-
- ]
- },
- {
- find: "THREAD_STARTER_MESSAGE?null==",
- replacement: {
- match: /deleted:\i\.deleted, editHistory:\i\.editHistory,/,
- replace: "deleted:$self.getDeleted(...arguments), editHistory:$self.getEdited(...arguments),"
- }
- },
- {
- find: "toolbar:function",
- predicate: () => settings.store.ShowLogsButton,
- replacement: {
- match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
- replace: "$1$self.addIconToToolBar(arguments[0]);$2"
- }
- },
-
- {
- find: "childrenMessageContent:null",
- replacement: {
- match: /(cozyMessage.{1,50},)childrenHeader:/,
- replace: "$1childrenAccessories:arguments[0].childrenAccessories || null,childrenHeader:"
- }
- },
-
- // https://regex101.com/r/S3IVGm/1
- // fix vidoes failing because there are no thumbnails
- {
- find: ".handleImageLoad)",
- replacement: {
- match: /(componentDidMount\(\){)(.{1,150}===(.+?)\.LOADING)/,
- replace:
- "$1if(this.props?.src?.startsWith('blob:') && this.props?.item?.type === 'VIDEO')" +
- "return this.setState({readyState: $3.READY});$2"
- }
- },
-
- // dont fetch messages for channels in modal
- {
- find: "Using PollReferenceMessageContext without",
- replacement: {
- match: /(?:\i\.)?\i\.(?:default\.)?focusMessage\(/,
- replace: "!(arguments[0]?.message?.deleted || arguments[0]?.message?.editHistory?.length > 0) && $&"
- }
- },
-
- // only check for expired attachments if the message is not deleted
- {
- find: "\"/ephemeral-attachments/\"",
- replacement: {
- match: /\i\.attachments\.some\(\i\)\|\|\i\.embeds\.some/,
- replace: "!arguments[0].deleted && $&"
- }
- }
- ],
- settings,
-
- toolboxActions: {
- "Message Logger"() {
- openLogModal();
- }
- },
-
- addIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
- if (Array.isArray(e.toolbar))
- return e.toolbar.push(
-
-
-
- );
-
- e.toolbar = [
-
-
- ,
- e.toolbar,
- ];
- },
-
- processMessageFetch,
- openLogModal,
- doesMatch,
- reAddDeletedMessages,
- LoggedMessageManager,
- ImageManager,
- imageUtils,
- idb,
-
- coolReAddDeletedMessages: (messages: LoggedMessageJSON[] & { extra: LoggedMessageJSON[]; }, payload: LoadMessagePayload) => {
- try {
- if (messages.extra)
- reAddDeletedMessages(messages, messages.extra, !payload.hasMoreAfter && !payload.isBefore, !payload.hasMoreBefore && !payload.isAfter);
- }
- catch (e) {
- Flogger.error("Failed to re-add deleted messages", e);
- }
- finally {
- return messages;
- }
- },
-
- isDeletedMessage: (id: string) => cacheSentMessages.get(id)?.deleted ?? false,
-
- getDeleted(m1, m2) {
- const deleted = m2?.deleted;
- if (deleted == null && m1?.deleted != null) return m1.deleted;
- return deleted;
- },
-
- getEdited(m1, m2) {
- const editHistory = m2?.editHistory;
- if (editHistory == null && m1?.editHistory != null && m1.editHistory.length > 0)
- return m1.editHistory.map(mapTimestamp);
- return editHistory;
- },
-
- flux: {
- "MESSAGE_DELETE": messageDeleteHandler as any,
- "MESSAGE_DELETE_BULK": messageDeleteBulkHandler,
- "MESSAGE_UPDATE": messageUpdateHandler,
- "MESSAGE_CREATE": messageCreateHandler
- },
-
- async start() {
- this.oldGetMessage = oldGetMessage = MessageStore.getMessage;
-
- // we have to do this because the original message logger fetches the message from the store now
- MessageStore.getMessage = (channelId: string, messageId: string) => {
- const MLMessage = idb.cachedMessages.get(messageId);
- if (MLMessage) return messageJsonToMessageClass({ message: MLMessage });
-
- return this.oldGetMessage(channelId, messageId);
- };
-
- Native.init();
-
- const { imageCacheDir, logsDir } = await Native.getSettings();
- settings.store.imageCacheDir = imageCacheDir;
- settings.store.logsDir = logsDir;
-
- setupContextMenuPatches();
- },
-
- stop() {
- removeContextMenuBindings();
- MessageStore.getMessage = this.oldGetMessage;
- }
-});
diff --git a/src/equicordplugins/messageLoggerEnhanced/native/index.ts b/src/equicordplugins/messageLoggerEnhanced/native/index.ts
deleted file mode 100644
index c5060888..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/native/index.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2023 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { readdir, readFile, unlink, writeFile } from "node:fs/promises";
-import path from "node:path";
-
-import { DATA_DIR } from "@main/utils/constants";
-import { dialog, IpcMainInvokeEvent, shell } from "electron";
-
-import { getSettings, saveSettings } from "./settings";
-export * from "./updater";
-
-import { LoggedAttachment } from "../types";
-import { LOGS_DATA_FILENAME } from "../utils/constants";
-import { ensureDirectoryExists, getAttachmentIdFromFilename, sleep } from "./utils";
-
-export { getSettings };
-
-// so we can filter the native helpers by this key
-export function messageLoggerEnhancedUniqueIdThingyIdkMan() { }
-
-// Map()
-const nativeSavedImages = new Map();
-export const getNativeSavedImages = () => nativeSavedImages;
-
-let logsDir: string;
-let imageCacheDir: string;
-
-const getImageCacheDir = async () => imageCacheDir ?? await getDefaultNativeImageDir();
-const getLogsDir = async () => logsDir ?? await getDefaultNativeDataDir();
-
-
-
-export async function initDirs() {
- const { logsDir: ld, imageCacheDir: icd } = await getSettings();
-
- logsDir = ld || await getDefaultNativeDataDir();
- imageCacheDir = icd || await getDefaultNativeImageDir();
-}
-initDirs();
-
-export async function init(_event: IpcMainInvokeEvent) {
- const imageDir = await getImageCacheDir();
-
- await ensureDirectoryExists(imageDir);
- const files = await readdir(imageDir);
- for (const filename of files) {
- const attachmentId = getAttachmentIdFromFilename(filename);
- nativeSavedImages.set(attachmentId, path.join(imageDir, filename));
- }
-}
-
-export async function getImageNative(_event: IpcMainInvokeEvent, attachmentId: string): Promise {
- const imagePath = nativeSavedImages.get(attachmentId);
- if (!imagePath) return null;
-
- try {
- return await readFile(imagePath);
- } catch (error: any) {
- console.error(error);
- return null;
- }
-}
-
-export async function writeImageNative(_event: IpcMainInvokeEvent, filename: string, content: Uint8Array) {
- if (!filename || !content) return;
- const imageDir = await getImageCacheDir();
-
- // returns the file name
- // ../../someMalicousPath.png -> someMalicousPath
- const attachmentId = getAttachmentIdFromFilename(filename);
-
- const existingImage = nativeSavedImages.get(attachmentId);
- if (existingImage) return;
-
- const imagePath = path.join(imageDir, filename);
- await ensureDirectoryExists(imageDir);
- await writeFile(imagePath, content);
-
- nativeSavedImages.set(attachmentId, imagePath);
-}
-
-export async function deleteFileNative(_event: IpcMainInvokeEvent, attachmentId: string) {
- const imagePath = nativeSavedImages.get(attachmentId);
- if (!imagePath) return;
-
- await unlink(imagePath);
-}
-
-
-export async function writeLogs(_event: IpcMainInvokeEvent, contents: string) {
- const logsDir = await getLogsDir();
-
- writeFile(path.join(logsDir, LOGS_DATA_FILENAME), contents);
-}
-
-
-export async function getDefaultNativeImageDir(): Promise {
- return path.join(await getDefaultNativeDataDir(), "savedImages");
-}
-
-export async function getDefaultNativeDataDir(): Promise {
- return path.join(DATA_DIR, "MessageLoggerData");
-}
-
-export async function chooseDir(event: IpcMainInvokeEvent, logKey: "logsDir" | "imageCacheDir") {
- const settings = await getSettings();
- const defaultPath = settings[logKey] || await getDefaultNativeDataDir();
-
- const res = await dialog.showOpenDialog({ properties: ["openDirectory"], defaultPath: defaultPath });
- const dir = res.filePaths[0];
-
- if (!dir) throw Error("Invalid Directory");
-
- settings[logKey] = dir;
-
- await saveSettings(settings);
-
- switch (logKey) {
- case "logsDir": logsDir = dir; break;
- case "imageCacheDir": imageCacheDir = dir; break;
- }
-
- if (logKey === "imageCacheDir")
- await init(event);
-
- return dir;
-}
-
-export async function showItemInFolder(_event: IpcMainInvokeEvent, filePath: string) {
- shell.showItemInFolder(filePath);
-}
-
-export async function chooseFile(_event: IpcMainInvokeEvent, title: string, filters: Electron.FileFilter[], defaultPath?: string) {
- const res = await dialog.showOpenDialog({ title, filters, properties: ["openFile"], defaultPath });
- const [path] = res.filePaths;
-
- if (!path) throw Error("Invalid file");
-
- return await readFile(path, "utf-8");
-}
-
-// doing it in native because you can only fetch images from the renderer
-// other types of files will cause cors issues
-export async function downloadAttachment(_event: IpcMainInvokeEvent, attachemnt: LoggedAttachment, attempts = 0, useOldUrl = false): Promise<{ error: string | null; path: string | null; }> {
- try {
- if (!attachemnt?.url || !attachemnt.oldUrl || !attachemnt?.id || !attachemnt?.fileExtension)
- return { error: "Invalid Attachment", path: null };
-
- if (attachemnt.id.match(/[\\/.]/)) {
- return { error: "Invalid Attachment ID", path: null };
- }
-
- const existingImage = nativeSavedImages.get(attachemnt.id);
- if (existingImage)
- return {
- error: null,
- path: existingImage
- };
-
- const res = await fetch(useOldUrl ? attachemnt.oldUrl : attachemnt.url);
-
- if (res.status !== 200) {
- if (res.status === 404 || res.status === 403 || res.status === 415)
- useOldUrl = true;
-
- attempts++;
- if (attempts > 3) {
- return {
- error: `Failed to get attachment ${attachemnt.id} for caching. too many attempts, error code ${res.status}`,
- path: null,
- };
- }
-
- await sleep(1000);
- return downloadAttachment(_event, attachemnt, attempts, useOldUrl);
- }
-
- const ab = await res.arrayBuffer();
- const imageCacheDir = await getImageCacheDir();
- await ensureDirectoryExists(imageCacheDir);
-
- const finalPath = path.join(imageCacheDir, `${attachemnt.id}${attachemnt.fileExtension}`);
- await writeFile(finalPath, Buffer.from(ab));
-
- nativeSavedImages.set(attachemnt.id, finalPath);
-
- return {
- error: null,
- path: finalPath
- };
-
- } catch (error: any) {
- console.error(error);
- return { error: error.message, path: null };
- }
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/native/settings.ts b/src/equicordplugins/messageLoggerEnhanced/native/settings.ts
deleted file mode 100644
index df541844..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/native/settings.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2023 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import fs from "fs/promises";
-import path from "path";
-
-import { getDefaultNativeDataDir, getDefaultNativeImageDir } from ".";
-import { ensureDirectoryExists } from "./utils";
-
-interface MLSettings {
- logsDir: string;
- imageCacheDir: string;
-}
-export async function getSettings(): Promise {
- try {
- const settings = await fs.readFile(await getSettingsFilePath(), "utf8");
- return JSON.parse(settings);
- } catch (err) {
- // probably doesnt exist
- // time to create it
- const settings = {
- logsDir: await getDefaultNativeDataDir(),
- imageCacheDir: await getDefaultNativeImageDir(),
- };
- try {
- await saveSettings(settings);
- } catch (err) { }
-
- return settings;
- }
-}
-
-// dont expose this to renderer future me
-export async function saveSettings(settings: MLSettings) {
- if (!settings) return;
- await fs.writeFile(await getSettingsFilePath(), JSON.stringify(settings, null, 4), "utf8");
-}
-
-async function getSettingsFilePath() {
- // mlSettings.json will always in that folder
- const MlDataDir = await getDefaultNativeDataDir();
- await ensureDirectoryExists(MlDataDir);
- const mlSettingsDir = path.join(MlDataDir, "mlSettings.json");
-
- return mlSettingsDir;
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/native/updater.ts b/src/equicordplugins/messageLoggerEnhanced/native/updater.ts
deleted file mode 100644
index 9055fc96..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/native/updater.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { execFile as cpExecFile, ExecFileOptions } from "node:child_process";
-
-import { readdir } from "fs/promises";
-import { join } from "path";
-import { promisify } from "util";
-
-import type { GitResult } from "../types";
-import { memoize } from "../utils/memoize";
-
-const execFile = promisify(cpExecFile);
-
-const isFlatpak = process.platform === "linux" && Boolean(process.env.FLATPAK_ID?.includes("discordapp") || process.env.FLATPAK_ID?.includes("Discord"));
-if (process.platform === "darwin") process.env.PATH = `/usr/local/bin:${process.env.PATH}`;
-
-
-const VENCORD_USER_PLUGIN_DIR = join(__dirname, "..", "src", "userplugins");
-const getCwd = memoize(async () => {
- const dirs = await readdir(VENCORD_USER_PLUGIN_DIR, { withFileTypes: true });
-
- for (const dir of dirs) {
- if (!dir.isDirectory()) continue;
-
- const pluginDir = join(VENCORD_USER_PLUGIN_DIR, dir.name);
- const files = await readdir(pluginDir);
-
- if (files.includes("LoggedMessageManager.ts")) return join(VENCORD_USER_PLUGIN_DIR, dir.name);
- }
-
- return;
-});
-
-async function git(...args: string[]): Promise {
- const opts: ExecFileOptions = { cwd: await getCwd(), shell: true };
-
- try {
- let result;
- if (isFlatpak) {
- result = await execFile("flatpak-spawn", ["--host", "git", ...args], opts);
- } else {
- result = await execFile("git", args, opts);
- }
-
- return { value: result.stdout.trim(), stderr: result.stderr, ok: true };
- } catch (error: any) {
- return {
- ok: false,
- cmd: error.cmd as string,
- message: error.stderr as string,
- error
- };
- }
-}
-
-export async function update() {
- return await git("pull");
-}
-
-export async function getCommitHash() {
- return await git("rev-parse", "HEAD");
-}
-
-export interface GitInfo {
- repo: string;
- gitHash: string;
-}
-
-export async function getRepoInfo(): Promise {
- const res = await git("remote", "get-url", "origin");
- if (!res.ok) {
- return res;
- }
-
- const gitHash = await getCommitHash();
- if (!gitHash.ok) {
- return gitHash;
- }
-
- return {
- ok: true,
- value: {
- repo: res.value
- .replace(/git@(.+):/, "https://$1/")
- .replace(/\.git$/, ""),
- gitHash: gitHash.value
- }
- };
-}
-
-export interface Commit {
- hash: string;
- longHash: string;
- message: string;
- author: string;
-}
-
-export async function getNewCommits(): Promise {
- const branch = await git("branch", "--show-current");
- if (!branch.ok) {
- return branch;
- }
-
- const logFormat = "%H;%an;%s";
- const branchRange = `HEAD..origin/${branch.value}`;
-
- try {
- await git("fetch");
-
- const logOutput = await git("log", `--format="${logFormat}"`, branchRange);
-
- if (!logOutput.ok) {
- return logOutput;
- }
-
- if (logOutput.value.trim() === "") {
- return { ok: true, value: [] };
- }
-
- const commitLines = logOutput.value.trim().split("\n");
- const commits: Commit[] = commitLines.map(line => {
- const [hash, author, ...rest] = line.split(";");
- return { longHash: hash, hash: hash.slice(0, 7), author, message: rest.join(";") } satisfies Commit;
- });
-
- return { ok: true, value: commits };
- } catch (error: any) {
- return { ok: false, cmd: error.cmd, message: error.message, error };
- }
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/native/utils.ts b/src/equicordplugins/messageLoggerEnhanced/native/utils.ts
deleted file mode 100644
index 691747f0..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/native/utils.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2023 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { access, mkdir } from "fs/promises";
-import path from "path";
-
-export async function exists(filename: string) {
- try {
- await access(filename);
- return true;
- } catch (error) {
- return false;
- }
-}
-
-export async function ensureDirectoryExists(cacheDir: string) {
- if (!await exists(cacheDir))
- await mkdir(cacheDir);
-}
-
-export function getAttachmentIdFromFilename(filename: string) {
- return path.parse(filename).name;
-}
-
-export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
diff --git a/src/equicordplugins/messageLoggerEnhanced/settings.tsx b/src/equicordplugins/messageLoggerEnhanced/settings.tsx
deleted file mode 100644
index d21ea4b0..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/settings.tsx
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { definePluginSettings } from "@api/Settings";
-import ErrorBoundary from "@components/ErrorBoundary";
-import { OptionType } from "@utils/types";
-import { Alerts, Button } from "@webpack/common";
-import { Settings } from "Vencord";
-
-import { Native } from ".";
-import { ImageCacheDir, LogsDir } from "./components/FolderSelectInput";
-import { openLogModal } from "./components/LogsModal";
-import { clearMessagesIDB } from "./db";
-import { DEFAULT_IMAGE_CACHE_DIR } from "./utils/constants";
-import { exportLogs, importLogs } from "./utils/settingsUtils";
-
-export const settings = definePluginSettings({
- saveMessages: {
- default: true,
- type: OptionType.BOOLEAN,
- description: "Wether to save the deleted and edited messages.",
- },
-
- saveImages: {
- type: OptionType.BOOLEAN,
- description: "Save deleted attachments.",
- default: false
- },
-
- sortNewest: {
- default: true,
- type: OptionType.BOOLEAN,
- description: "Sort logs by newest.",
- },
-
- cacheMessagesFromServers: {
- default: false,
- type: OptionType.BOOLEAN,
- description: "Usually message logger only logs from whitelisted ids and dms, enabling this would mean it would log messages from all servers as well. Note that this may cause the cache to exceed its limit, resulting in some messages being missed. If you are in a lot of servers, this may significantly increase the chances of messages being logged, which can result in a large message record and the inclusion of irrelevant messages.",
- },
-
- ignoreBots: {
- type: OptionType.BOOLEAN,
- description: "Whether to ignore messages by bots",
- default: false,
- onChange() {
- // we will be handling the ignoreBots now (enabled or not) so the original messageLogger shouldnt
- Settings.plugins.MessageLogger.ignoreBots = false;
- }
- },
-
- ignoreWebhooks: {
- type: OptionType.BOOLEAN,
- description: "Whether to ignore messages by webhooks",
- default: false,
- },
-
- ignoreSelf: {
- type: OptionType.BOOLEAN,
- description: "Whether to ignore messages by yourself",
- default: false,
- onChange() {
- Settings.plugins.MessageLogger.ignoreSelf = false;
- }
- },
-
- ignoreMutedGuilds: {
- default: false,
- type: OptionType.BOOLEAN,
- description: "Messages in muted guilds will not be logged. Whitelisted users/channels in muted guilds will still be logged."
- },
-
- ignoreMutedCategories: {
- default: false,
- type: OptionType.BOOLEAN,
- description: "Messages in channels belonging to muted categories will not be logged. Whitelisted users/channels in muted guilds will still be logged."
- },
-
- ignoreMutedChannels: {
- default: false,
- type: OptionType.BOOLEAN,
- description: "Messages in muted channels will not be logged. Whitelisted users/channels in muted guilds will still be logged."
- },
-
- alwaysLogDirectMessages: {
- default: true,
- type: OptionType.BOOLEAN,
- description: "Always log DMs",
- },
-
- alwaysLogCurrentChannel: {
- default: true,
- type: OptionType.BOOLEAN,
- description: "Always log current selected channel. Blacklisted channels/users will still be ignored.",
- },
-
- permanentlyRemoveLogByDefault: {
- default: false,
- type: OptionType.BOOLEAN,
- description: "Vencord's base MessageLogger remove log button wiil delete logs permanently",
- },
-
- hideMessageFromMessageLoggers: {
- default: false,
- type: OptionType.BOOLEAN,
- description: "When enabled, a context menu button will be added to messages to allow you to delete messages without them being logged by other loggers. Might not be safe, use at your own risk."
- },
-
- ShowLogsButton: {
- default: true,
- type: OptionType.BOOLEAN,
- description: "Toggle to whenever show the toolbox or not",
- restartNeeded: true,
- },
-
- messagesToDisplayAtOnceInLogs: {
- default: 100,
- type: OptionType.NUMBER,
- description: "Number of messages to display at once in logs & number of messages to load when loading more messages in logs.",
- },
-
- hideMessageFromMessageLoggersDeletedMessage: {
- default: "redacted eh",
- type: OptionType.STRING,
- description: "The message content to replace the message with when using the hide message from message loggers feature.",
- },
-
- messageLimit: {
- default: 200,
- type: OptionType.NUMBER,
- description: "Maximum number of messages to save. Older messages are deleted when the limit is reached. 0 means there is no limit"
- },
-
- attachmentSizeLimitInMegabytes: {
- default: 12,
- type: OptionType.NUMBER,
- description: "Maximum size of an attachment in megabytes to save. Attachments larger than this size will not be saved."
- },
-
- attachmentFileExtensions: {
- default: "png,jpg,jpeg,gif,webp,mp4,webm,mp3,ogg,wav",
- type: OptionType.STRING,
- description: "Comma separated list of file extensions to save. Attachments with file extensions not in this list will not be saved. Leave empty to save all attachments."
- },
-
- cacheLimit: {
- default: 1000,
- type: OptionType.NUMBER,
- description: "Maximum number of messages to store in the cache. Older messages are deleted when the limit is reached. This helps reduce memory usage and improve performance. 0 means there is no limit",
- },
-
- whitelistedIds: {
- default: "",
- type: OptionType.STRING,
- description: "Whitelisted server, channel, or user IDs."
- },
-
- blacklistedIds: {
- default: "",
- type: OptionType.STRING,
- description: "Blacklisted server, channel, or user IDs."
- },
-
- imageCacheDir: {
- type: OptionType.COMPONENT,
- description: "Select saved images directory",
- component: ErrorBoundary.wrap(ImageCacheDir) as any
- },
-
- logsDir: {
- type: OptionType.COMPONENT,
- description: "Select logs directory",
- component: ErrorBoundary.wrap(LogsDir) as any
- },
-
- importLogs: {
- type: OptionType.COMPONENT,
- description: "Import Logs From File",
- component: () =>
-
- },
-
- exportLogs: {
- type: OptionType.COMPONENT,
- description: "Export Logs From IndexedDB",
- component: () =>
-
- },
-
- openLogs: {
- type: OptionType.COMPONENT,
- description: "Open Logs",
- component: () =>
-
- },
- openImageCacheFolder: {
- type: OptionType.COMPONENT,
- description: "Opens the image cache directory",
- component: () =>
-
- },
-
- clearLogs: {
- type: OptionType.COMPONENT,
- description: "Clear Logs",
- component: () =>
-
- },
-
-});
diff --git a/src/equicordplugins/messageLoggerEnhanced/styles.css b/src/equicordplugins/messageLoggerEnhanced/styles.css
deleted file mode 100644
index 97d1cada..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/styles.css
+++ /dev/null
@@ -1,85 +0,0 @@
-.msg-logger-modal-root {
- max-height: 80vh;
- min-height: 80vh;
- min-width: 85vw;
- max-width: 85vw;
-}
-
-.msg-logger-modal-empty-logs {
- display: grid;
- place-content: center;
- height: 100%;
-}
-
-.msg-logger-modal-info-icon {
- position: absolute;
- top: -24px;
- right: -24px;
- color: var(--interactive-normal);
- cursor: pointer;
-}
-
-.msg-logger-modal-header {
- flex-direction: column;
-
- /* width: 100%; */
-}
-
-.msg-logger-modal-content-container {
- /* max-height: 80vh; */
- overflow: hidden;
- height: 100%;
- transition: opacity 100ms ease-in;
-}
-
-.msg-logger-modal-content {
- padding-bottom: 20px;
- height: 100%;
-}
-
-.msg-logger-modal-content-inner {
- padding-top: 1rem;
- padding-bottom: 1rem;
- height: 100%;
-}
-
-.msg-logger-modal-header > div:has(input) {
- width: 100%;
-}
-
-.msg-logger-modal-tab-bar-item {
- padding-bottom: 16px;
- margin-bottom: -2px;
-}
-
-.msg-logger-modal-tab-bar {
- margin-top: 20px;
- width: 100%;
- justify-content: space-around;
-}
-
-.vc-log-toolbox-btn svg {
- color: var(--interactive-normal);
-}
-
-:is(.vc-log-toolbox-btn:hover, .vc-log-toolbox-btn[class*="selected"]) svg {
- color: var(--interactive-active);
-}
-
-.folder-upload-container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- background-color: var(--background-secondary);
-}
-
-.folder-upload-input {
- cursor: pointer;
- padding: 10px 0 10px 10px;
- color: var(--header-secondary);
-}
-
-.folder-upload-button {
- margin: 6px;
- padding: 4px 8px;
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/types.ts b/src/equicordplugins/messageLoggerEnhanced/types.ts
deleted file mode 100644
index 371c48b2..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/types.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { Message, MessageAttachment, MessageJSON } from "discord-types/general";
-
-export interface LoggedAttachment extends MessageAttachment {
- fileExtension?: string | null;
- path?: string | null;
- blobUrl?: string;
- nativefileSystem?: boolean;
- oldUrl?: string;
- oldProxyUrl?: string;
-}
-
-export type RefrencedMessage = LoggedMessageJSON & { message_id: string; };
-export interface LoggedMessageJSON extends Omit {
- mention_everyone?: string;
- guildId?: string;
- guild_id?: string;
- ghostPinged?: boolean;
- timestamp: string;
- ourCache?: boolean;
- referenced_message: RefrencedMessage;
- message_reference: RefrencedMessage;
-}
-
-export interface LoggedMessage extends Message {
- attachments: LoggedAttachment[];
- deleted?: boolean;
- deletedTimestamp?: string;
- editHistory?: {
- timestamp: string;
- content: string;
- }[];
-}
-
-export interface MessageDeletePayload {
- type: string;
- guildId: string;
- id: string;
- channelId: string;
- mlDeleted?: boolean;
-}
-
-export interface MessageDeleteBulkPayload {
- type: string;
- guildId: string;
- ids: string[];
- channelId: string;
-}
-
-
-export interface MessageUpdatePayload {
- type: string;
- guildId: string;
- message: MessageJSON;
-}
-
-export interface MessageCreatePayload {
- type: string;
- guildId: string;
- channelId: string;
- message: MessageJSON;
- optimistic: boolean;
- isPushNotification: boolean;
-}
-
-export interface LoadMessagePayload {
- type: string;
- channelId: string;
- messages: LoggedMessageJSON[];
- isBefore: boolean;
- isAfter: boolean;
- hasMoreBefore: boolean;
- hasMoreAfter: boolean;
- limit: number;
- isStale: boolean;
-}
-
-export interface FetchMessagesResponse {
- ok: boolean;
- headers: Headers;
- body: LoggedMessageJSON[] & {
- extra?: LoggedMessageJSON[];
- };
- text: string;
- status: number;
-}
-
-export interface PatchAttachmentItem {
- uniqueId: string;
- originalItem: LoggedAttachment;
- type: string;
- downloadUrl: string;
- height: number;
- width: number;
- spoiler: boolean;
- contentType: string;
-}
-
-export interface AttachmentData {
- messageId: string;
- attachmentId: string;
-}
-
-export type SavedImages = Record;
-
-export type LoggedMessageIds = {
- // [channel_id: string]: message_id
- deletedMessages: Record;
- editedMessages: Record;
-};
-
-export type MessageRecord = { message: LoggedMessageJSON; };
-
-export type LoggedMessages = LoggedMessageIds & { [message_id: string]: { message?: LoggedMessageJSON; }; };
-
-export type GitValue = {
- value: any;
- stderr?: string;
- ok: true;
-};
-
-export type GitError = {
- ok: false;
- cmd: string;
- message: string;
- error: any;
-};
-
-export type GitResult = GitValue | GitError;
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/LimitedMap.ts b/src/equicordplugins/messageLoggerEnhanced/utils/LimitedMap.ts
deleted file mode 100644
index ac340899..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/LimitedMap.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { settings } from "../index";
-
-export class LimitedMap {
- public map: Map = new Map();
- constructor() { }
-
- set(key: K, value: V) {
- if (settings.store.cacheLimit > 0 && this.map.size >= settings.store.cacheLimit) {
- const firstKey = this.map.keys().next().value;
- if (firstKey !== undefined) {
- this.map.delete(firstKey);
- }
- }
- this.map.set(key, value);
- }
-
- get(key: K) {
- return this.map.get(key);
- }
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/cleanUp.ts b/src/equicordplugins/messageLoggerEnhanced/utils/cleanUp.ts
deleted file mode 100644
index 7f47ff36..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/cleanUp.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { MessageStore } from "@webpack/common";
-import { User } from "discord-types/general";
-
-import { LoggedMessageJSON, RefrencedMessage } from "../types";
-import { getGuildIdByChannel, isGhostPinged } from "./index";
-
-export function cleanupMessage(message: any, removeDetails: boolean = true): LoggedMessageJSON {
- const ret: LoggedMessageJSON = typeof message.toJS === "function" ? JSON.parse(JSON.stringify(message.toJS())) : { ...message };
- if (removeDetails) {
- ret.author.phone = undefined;
- ret.author.email = undefined;
- }
-
- ret.ghostPinged = ret.mentioned ?? isGhostPinged(message);
- ret.guildId = ret.guild_id ?? getGuildIdByChannel(ret.channel_id);
- ret.embeds = (ret.embeds ?? []).map(cleanupEmbed);
- ret.deleted = ret.deleted ?? false;
- ret.deletedTimestamp = ret.deleted ? (new Date()).toISOString() : undefined;
- ret.editHistory = ret.editHistory ?? [];
- if (ret.type === 19) {
- ret.message_reference = message.message_reference || message.messageReference;
- if (ret.message_reference) {
- if (message.referenced_message) {
- ret.referenced_message = cleanupMessage(message.referenced_message) as RefrencedMessage;
- } else if (MessageStore.getMessage(ret.message_reference.channel_id, ret.message_reference.message_id)) {
- ret.referenced_message = cleanupMessage(MessageStore.getMessage(ret.message_reference.channel_id, ret.message_reference.message_id)) as RefrencedMessage;
- }
- }
- }
-
- return ret;
-}
-
-export function cleanUpCachedMessage(message: any) {
- const ret = cleanupMessage(message, false);
- ret.ourCache = true;
- return ret;
-}
-
-// stolen from mlv2
-export function cleanupEmbed(embed) {
- /* backported code from MLV2 rewrite */
- if (!embed.id) return embed; /* already cleaned */
- const retEmbed: any = {};
- if (typeof embed.rawTitle === "string") retEmbed.title = embed.rawTitle;
- if (typeof embed.rawDescription === "string") retEmbed.description = embed.rawDescription;
- if (typeof embed.referenceId !== "undefined") retEmbed.reference_id = embed.referenceId;
- // if (typeof embed.color === "string") retEmbed.color = ZeresPluginLibrary.ColorConverter.hex2int(embed.color);
- if (typeof embed.type !== "undefined") retEmbed.type = embed.type;
- if (typeof embed.url !== "undefined") retEmbed.url = embed.url;
- if (typeof embed.provider === "object") retEmbed.provider = { name: embed.provider.name, url: embed.provider.url };
- if (typeof embed.footer === "object") retEmbed.footer = { text: embed.footer.text, icon_url: embed.footer.iconURL, proxy_icon_url: embed.footer.iconProxyURL };
- if (typeof embed.author === "object") retEmbed.author = { name: embed.author.name, url: embed.author.url, icon_url: embed.author.iconURL, proxy_icon_url: embed.author.iconProxyURL };
- if (typeof embed.timestamp === "object" && embed.timestamp._isAMomentObject) retEmbed.timestamp = embed.timestamp.milliseconds();
- if (typeof embed.thumbnail === "object") {
- if (typeof embed.thumbnail.proxyURL === "string" || (typeof embed.thumbnail.url === "string" && !embed.thumbnail.url.endsWith("?format=jpeg"))) {
- retEmbed.thumbnail = {
- url: embed.thumbnail.url,
- proxy_url: typeof embed.thumbnail.proxyURL === "string" ? embed.thumbnail.proxyURL.split("?format")[0] : undefined,
- width: embed.thumbnail.width,
- height: embed.thumbnail.height
- };
- }
- }
- if (typeof embed.image === "object") {
- retEmbed.image = {
- url: embed.image.url,
- proxy_url: embed.image.proxyURL,
- width: embed.image.width,
- height: embed.image.height
- };
- }
- if (typeof embed.video === "object") {
- retEmbed.video = {
- url: embed.video.url,
- proxy_url: embed.video.proxyURL,
- width: embed.video.width,
- height: embed.video.height
- };
- }
- if (Array.isArray(embed.fields) && embed.fields.length) {
- retEmbed.fields = embed.fields.map(e => ({ name: e.rawName, value: e.rawValue, inline: e.inline }));
- }
- return retEmbed;
-}
-
-// stolen from mlv2
-export function cleanupUserObject(user: User) {
- /* backported from MLV2 rewrite */
- return {
- discriminator: user.discriminator,
- username: user.username,
- avatar: user.avatar,
- id: user.id,
- bot: user.bot,
- public_flags: typeof user.publicFlags !== "undefined" ? user.publicFlags : (user as any).public_flags
- };
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/constants.ts b/src/equicordplugins/messageLoggerEnhanced/utils/constants.ts
deleted file mode 100644
index 7fe6a219..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/constants.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-export const DEFAULT_IMAGE_CACHE_DIR = "savedImages";
-
-export const DB_NAME = "MessageLoggerIDB";
-export const DB_VERSION = 1;
-export const LOGS_DATA_FILENAME = "message-logger-logs.json";
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/contextMenu.tsx b/src/equicordplugins/messageLoggerEnhanced/utils/contextMenu.tsx
deleted file mode 100644
index e33ea2b9..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/contextMenu.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
-import { FluxDispatcher, Menu, MessageActions, React, Toasts, UserStore } from "@webpack/common";
-
-import { openLogModal } from "../components/LogsModal";
-import { deleteMessageIDB } from "../db";
-import { settings } from "../index";
-import { addToXAndRemoveFromOpposite, ListType, removeFromX } from ".";
-
-const idFunctions = {
- Server: props => props?.guild?.id,
- User: props => props?.message?.author?.id || props?.user?.id,
- Channel: props => props.message?.channel_id || props.channel?.id
-} as const;
-
-type idKeys = keyof typeof idFunctions;
-
-function renderListOption(listType: ListType, IdType: idKeys, props: any) {
- const id = idFunctions[IdType](props);
- if (!id) return null;
-
- const isBlocked = settings.store[listType].includes(id);
- const oppositeListType = listType === "blacklistedIds" ? "whitelistedIds" : "blacklistedIds";
- const isOppositeBlocked = settings.store[oppositeListType].includes(id);
- const list = listType === "blacklistedIds" ? "Blacklist" : "Whitelist";
-
- const addToList = () => addToXAndRemoveFromOpposite(listType, id);
- const removeFromList = () => removeFromX(listType, id);
-
- return (
-
- );
-}
-
-function renderOpenLogs(idType: idKeys, props: any) {
- const id = idFunctions[idType](props);
- if (!id) return null;
-
- return (
- openLogModal(`${idType.toLowerCase()}:${id}`)}
- />
- );
-}
-
-export const contextMenuPath: NavContextMenuPatchCallback = (children, props) => {
- if (!props) return;
-
- if (!children.some(child => child?.props?.id === "message-logger")) {
- children.push(
- ,
-
-
- openLogModal()}
- />
-
- {Object.keys(idFunctions).map(IdType => renderOpenLogs(IdType as idKeys, props))}
-
-
-
- {Object.keys(idFunctions).map(IdType => (
-
- {renderListOption("blacklistedIds", IdType as idKeys, props)}
- {renderListOption("whitelistedIds", IdType as idKeys, props)}
-
- ))}
-
- {
- props.navId === "message"
- && (props.message?.deleted || props.message?.editHistory?.length > 0)
- && (
- <>
-
-
- deleteMessageIDB(props.message.id)
- .then(() => {
- if (props.message.deleted) {
- FluxDispatcher.dispatch({
- type: "MESSAGE_DELETE",
- channelId: props.message.channel_id,
- id: props.message.id,
- mlDeleted: true
- });
- } else {
- props.message.editHistory = [];
- }
- }).catch(() => Toasts.show({
- type: Toasts.Type.FAILURE,
- message: "Failed to remove message",
- id: Toasts.genId()
- }))
-
- }
- />
- >
- )
- }
-
- {
- settings.store.hideMessageFromMessageLoggers
- && props.navId === "message"
- && props.message?.author?.id === UserStore.getCurrentUser().id
- && props.message?.deleted === false
- && (
- <>
-
- {
- await MessageActions.deleteMessage(props.message.channel_id, props.message.id);
- MessageActions._sendMessage(props.message.channel_id, {
- "content": settings.store.hideMessageFromMessageLoggersDeletedMessage,
- "tts": false,
- "invalidEmojis": [],
- "validNonShortcutEmojis": []
- }, { nonce: props.message.id });
- }}
-
- />
- >
- )
- }
-
-
- );
- }
-};
-
-export const setupContextMenuPatches = () => {
- addContextMenuPatch("message", contextMenuPath);
- addContextMenuPatch("channel-context", contextMenuPath);
- addContextMenuPatch("user-context", contextMenuPath);
- addContextMenuPatch("guild-context", contextMenuPath);
- addContextMenuPatch("gdm-context", contextMenuPath);
-};
-
-export const removeContextMenuBindings = () => {
- removeContextMenuPatch("message", contextMenuPath);
- removeContextMenuPatch("channel-context", contextMenuPath);
- removeContextMenuPatch("user-context", contextMenuPath);
- removeContextMenuPatch("guild-context", contextMenuPath);
- removeContextMenuPatch("gdm-context", contextMenuPath);
-};
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/index.ts b/src/equicordplugins/messageLoggerEnhanced/utils/index.ts
deleted file mode 100644
index 082e2c19..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/index.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { Settings } from "@api/Settings";
-import { findStoreLazy } from "@webpack";
-import { ChannelStore, SelectedChannelStore, UserStore } from "@webpack/common";
-
-import { settings } from "../index";
-import { LoggedMessageJSON } from "../types";
-import { findLastIndex, getGuildIdByChannel } from "./misc";
-
-export * from "./cleanUp";
-export * from "./misc";
-
-
-// stolen from mlv2
-// https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/MessageLoggerV2/MessageLoggerV2.plugin.js#L2367
-interface Id { id: string, time: number; message?: LoggedMessageJSON; }
-export const DISCORD_EPOCH = 14200704e5;
-export function reAddDeletedMessages(messages: LoggedMessageJSON[], deletedMessages: LoggedMessageJSON[], channelStart: boolean, channelEnd: boolean) {
- if (!messages.length || !deletedMessages?.length) return;
- const IDs: Id[] = [];
- const savedIDs: Id[] = [];
-
- for (let i = 0, len = messages.length; i < len; i++) {
- const { id } = messages[i];
- IDs.push({ id: id, time: (parseInt(id) / 4194304) + DISCORD_EPOCH });
- }
- for (let i = 0, len = deletedMessages.length; i < len; i++) {
- const record = deletedMessages[i];
- if (!record) continue;
- savedIDs.push({ id: record.id, time: (parseInt(record.id) / 4194304) + DISCORD_EPOCH, message: record });
- }
-
- savedIDs.sort((a, b) => a.time - b.time);
- if (!savedIDs.length) return;
- const { time: lowestTime } = IDs[IDs.length - 1];
- const [{ time: highestTime }] = IDs;
- const lowestIDX = channelEnd ? 0 : savedIDs.findIndex(e => e.time > lowestTime);
- if (lowestIDX === -1) return;
- const highestIDX = channelStart ? savedIDs.length - 1 : findLastIndex(savedIDs, e => e.time < highestTime);
- if (highestIDX === -1) return;
- const reAddIDs = savedIDs.slice(lowestIDX, highestIDX + 1);
- reAddIDs.push(...IDs);
- reAddIDs.sort((a, b) => b.time - a.time);
- for (let i = 0, len = reAddIDs.length; i < len; i++) {
- const { id, message } = reAddIDs[i];
- if (messages.findIndex(e => e.id === id) !== -1) continue;
- if (!message) continue;
- messages.splice(i, 0, message);
- }
-}
-
-interface ShouldIgnoreArguments {
- channelId?: string,
- authorId?: string,
- guildId?: string;
- flags?: number,
- bot?: boolean;
- ghostPinged?: boolean;
- isCachedByUs?: boolean;
- webhookId?: string;
-}
-
-const EPHEMERAL = 64;
-
-const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
-
-/**
- * the function `shouldIgnore` evaluates whether a message should be ignored or kept, following a priority hierarchy: User > Channel > Server.
- * In this hierarchy, whitelisting takes priority; if any element (User, Channel, or Server) is whitelisted, the message is kept.
- * However, if a higher-priority element, like a User, is blacklisted, it will override the whitelisting status of a lower-priority element, such as a Server, causing the message to be ignored.
- * @param {ShouldIgnoreArguments} args - An object containing the message details.
- * @returns {boolean} - True if the message should be ignored, false if it should be kept.
-*/
-export function shouldIgnore({ channelId, authorId, guildId, flags, bot, ghostPinged, isCachedByUs, webhookId }: ShouldIgnoreArguments): boolean {
- const isEphemeral = ((flags ?? 0) & EPHEMERAL) === EPHEMERAL;
- if (isEphemeral) return true; // ignore
-
- if (channelId && guildId == null)
- guildId = getGuildIdByChannel(channelId);
-
- const myId = UserStore.getCurrentUser().id;
- const { ignoreUsers, ignoreChannels, ignoreGuilds } = Settings.plugins.MessageLogger;
- const { ignoreBots, ignoreSelf, ignoreWebhooks } = settings.store;
-
- if (ignoreSelf && authorId === myId)
- return true; // ignore
- if (settings.store.alwaysLogDirectMessages && ChannelStore.getChannel(channelId ?? "-1")?.isDM?.())
- return false; // keep
-
- const shouldLogCurrentChannel = settings.store.alwaysLogCurrentChannel && SelectedChannelStore.getChannelId() === channelId;
-
- const ids = [authorId, channelId, guildId];
-
- const whitelistedIds = settings.store.whitelistedIds.split(",");
-
- const isWhitelisted = settings.store.whitelistedIds.split(",").some(e => ids.includes(e));
- const isAuthorWhitelisted = whitelistedIds.includes(authorId!);
- const isChannelWhitelisted = whitelistedIds.includes(channelId!);
- const isGuildWhitelisted = whitelistedIds.includes(guildId!);
-
- const blacklistedIds = [
- ...settings.store.blacklistedIds.split(","),
- ...(ignoreUsers ?? []).split(","),
- ...(ignoreChannels ?? []).split(","),
- ...(ignoreGuilds ?? []).split(",")
- ];
-
- const isBlacklisted = blacklistedIds.some(e => ids.includes(e));
- const isAuthorBlacklisted = blacklistedIds.includes(authorId);
- const isChannelBlacklisted = blacklistedIds.includes(channelId);
-
-
- const shouldIgnoreMutedGuilds = settings.store.ignoreMutedGuilds;
- const shouldIgnoreMutedCategories = settings.store.ignoreMutedCategories;
- const shouldIgnoreMutedChannels = settings.store.ignoreMutedChannels;
-
- if ((ignoreBots && bot) && !isAuthorWhitelisted) return true; // ignore
-
- if ((ignoreWebhooks && webhookId) && !isAuthorWhitelisted) return true;
-
- if (ghostPinged) return false; // keep
-
- // author has highest priority
- if (isAuthorWhitelisted) return false; // keep
- if (isAuthorBlacklisted) return true; // ignore
-
- if (isChannelWhitelisted) return false; // keep
- if (isChannelBlacklisted) return true; // ignore
-
- if (shouldLogCurrentChannel) return false; // keep
-
- if (isWhitelisted) return false; // keep
-
- if (isCachedByUs && (!settings.store.cacheMessagesFromServers && guildId != null && !isGuildWhitelisted)) return true; // ignore
-
- if (isBlacklisted && (!isAuthorWhitelisted || !isChannelWhitelisted)) return true; // ignore
-
- if (guildId != null && shouldIgnoreMutedGuilds && UserGuildSettingsStore.isMuted(guildId)) return true; // ignore
- if (channelId != null && shouldIgnoreMutedCategories && UserGuildSettingsStore.isCategoryMuted(guildId, channelId)) return true; // ignore
- if (channelId != null && shouldIgnoreMutedChannels && UserGuildSettingsStore.isChannelMuted(guildId, channelId)) return true; // ignore
-
- return false; // keep;
-}
-
-export type ListType = "blacklistedIds" | "whitelistedIds";
-
-export function addToXAndRemoveFromOpposite(list: ListType, id: string) {
- const oppositeListType = list === "blacklistedIds" ? "whitelistedIds" : "blacklistedIds";
- removeFromX(oppositeListType, id);
-
- addToX(list, id);
-}
-
-export function addToX(list: ListType, id: string) {
- const items = settings.store[list] ? settings.store[list].split(",") : [];
- items.push(id);
-
- settings.store[list] = items.join(",");
-}
-
-export function removeFromX(list: ListType, id: string) {
- const items = settings.store[list] ? settings.store[list].split(",") : [];
- const index = items.indexOf(id);
- if (index !== -1) {
- items.splice(index, 1);
- }
- settings.store[list] = items.join(",");
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/memoize.ts b/src/equicordplugins/messageLoggerEnhanced/utils/memoize.ts
deleted file mode 100644
index bd7c53d3..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/memoize.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-type MemoizedFunction any> = {
- (...args: Parameters): ReturnType;
- clear(): void;
-};
-
-export function memoize any>(func: T): MemoizedFunction {
- const cache = new Map>();
-
- const memoizedFunc = (...args: Parameters): ReturnType => {
- const key = JSON.stringify(args);
- if (cache.has(key)) {
- return cache.get(key)!;
- }
-
- const result = func(...args);
- cache.set(key, result);
- return result;
- };
-
- memoizedFunc.clear = () => cache.clear();
-
- return memoizedFunc;
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/misc.ts b/src/equicordplugins/messageLoggerEnhanced/utils/misc.ts
deleted file mode 100644
index 34dfab23..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/misc.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { PluginNative } from "@utils/types";
-import { findByCodeLazy, findLazy } from "@webpack";
-import { ChannelStore, moment, UserStore } from "@webpack/common";
-
-import { DBMessageStatus } from "../db";
-import { LoggedMessageJSON } from "../types";
-import { DEFAULT_IMAGE_CACHE_DIR } from "./constants";
-import { DISCORD_EPOCH } from "./index";
-import { memoize } from "./memoize";
-
-const MessageClass: any = findLazy(m => m?.prototype?.isEdited);
-const AuthorClass = findLazy(m => m?.prototype?.getAvatarURL);
-const sanitizeEmbed = findByCodeLazy('"embed_"),');
-
-export function getGuildIdByChannel(channel_id: string) {
- return ChannelStore.getChannel(channel_id)?.guild_id;
-}
-
-export const isGhostPinged = (message?: LoggedMessageJSON) => {
- return message?.ghostPinged || message?.deleted && hasPingged(message);
-
-};
-
-export const hasPingged = (message?: LoggedMessageJSON | { mention_everyone: boolean, mentions: any[]; }) => {
- return message && !!(
- message.mention_everyone ||
- message.mentions?.find(m => (typeof m === "string" ? m : m.id) === UserStore.getCurrentUser().id)
- );
-};
-
-export const getMessageStatus = (message: LoggedMessageJSON) => {
- if (isGhostPinged(message)) return DBMessageStatus.GHOST_PINGED;
- if (message.deleted) return DBMessageStatus.DELETED;
- if (message.editHistory?.length) return DBMessageStatus.EDITED;
-
- throw new Error("Unknown message status");
-};
-
-export const discordIdToDate = (id: string) => new Date((parseInt(id) / 4194304) + DISCORD_EPOCH);
-
-export const sortMessagesByDate = (timestampA: string, timestampB: string) => {
- // very expensive
- // const timestampA = discordIdToDate(a).getTime();
- // const timestampB = discordIdToDate(b).getTime();
- // return timestampB - timestampA;
-
- // newest first
- if (timestampA < timestampB) {
- return 1;
- } else if (timestampA > timestampB) {
- return -1;
- } else {
- return 0;
- }
-};
-
-
-
-// stolen from mlv2
-export function findLastIndex(array: T[], predicate: (e: T, t: number, n: T[]) => boolean) {
- let l = array.length;
- while (l--) {
- if (predicate(array[l], l, array))
- return l;
- }
- return -1;
-}
-
-const getTimestamp = (timestamp: any): Date => {
- return new Date(timestamp);
-};
-
-export const mapTimestamp = (m: any) => {
- if (m.timestamp) m.timestamp = getTimestamp(m.timestamp);
- if (m.editedTimestamp) m.editedTimestamp = getTimestamp(m.editedTimestamp);
- if (m.embeds) m.embeds = m.embeds.map(e => sanitizeEmbed(m.channel_id, m.id, e));
- return m;
-};
-
-
-export const messageJsonToMessageClass = memoize((log: { message: LoggedMessageJSON; }) => {
- // console.time("message populate");
- if (!log?.message) return null;
-
- const message: any = new MessageClass(log.message);
- message.timestamp = getTimestamp(message.timestamp);
-
- const editHistory = message.editHistory?.map(mapTimestamp);
- if (editHistory && editHistory.length > 0) {
- message.editHistory = editHistory;
- }
- if (message.editedTimestamp)
- message.editedTimestamp = getTimestamp(message.editedTimestamp);
-
- if (message.firstEditTimestamp)
- message.firstEditTimestamp = getTimestamp(message.firstEditTimestamp);
-
- message.author = UserStore.getUser(message.author.id) ?? new AuthorClass(message.author);
- message.author.nick = message.author.globalName ?? message.author.username;
-
- message.embeds = message.embeds.map(e => sanitizeEmbed(message.channel_id, message.id, e));
-
- if (message.poll)
- message.poll.expiry = moment(message.poll.expiry);
-
- if (message.messageSnapshots)
- message.messageSnapshots.map(m => mapTimestamp(m.message));
-
- // console.timeEnd("message populate");
- return message;
-});
-
-
-export function parseJSON(json?: string | null) {
- try {
- return JSON.parse(json!);
- } finally {
- return null;
- }
-}
-
-export async function doesBlobUrlExist(url: string) {
- const res = await fetch(url);
- return res.ok;
-}
-
-export function getNative(): PluginNative {
- if (IS_WEB) {
- const Native = {
- writeLogs: async () => { },
- getDefaultNativeImageDir: async () => DEFAULT_IMAGE_CACHE_DIR,
- getDefaultNativeDataDir: async () => "",
- deleteFileNative: async () => { },
- chooseDir: async (x: string) => "",
- getSettings: async () => ({ imageCacheDir: DEFAULT_IMAGE_CACHE_DIR, logsDir: "" }),
- init: async () => { },
- initDirs: async () => { },
- getImageNative: async (x: string) => new Uint8Array(0),
- getNativeSavedImages: async () => new Map(),
- messageLoggerEnhancedUniqueIdThingyIdkMan: async () => { },
- showItemInFolder: async () => { },
- writeImageNative: async () => { },
- getCommitHash: async () => ({ ok: true, value: "" }),
- getRepoInfo: async () => ({ ok: true, value: { repo: "", gitHash: "" } }),
- getNewCommits: async () => ({ ok: true, value: [] }),
- update: async () => ({ ok: true, value: "" }),
- chooseFile: async () => "",
- downloadAttachment: async () => ({ error: "web", path: null }),
- } satisfies PluginNative;
-
- return Native;
-
- }
-
- return Object.values(VencordNative.pluginHelpers)
- .find(m => m.messageLoggerEnhancedUniqueIdThingyIdkMan) as PluginNative;
-
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/parseQuery.ts b/src/equicordplugins/messageLoggerEnhanced/utils/parseQuery.ts
deleted file mode 100644
index 0dd86b39..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/parseQuery.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { ChannelStore, GuildStore } from "@webpack/common";
-
-import { LoggedMessageJSON } from "../types";
-import { getGuildIdByChannel } from "./index";
-import { memoize } from "./memoize";
-
-
-const validIdSearchTypes = ["server", "guild", "channel", "in", "user", "from", "message", "has", "before", "after", "around", "near", "during"] as const;
-type ValidIdSearchTypesUnion = typeof validIdSearchTypes[number];
-
-interface QueryResult {
- key: ValidIdSearchTypesUnion;
- value: string;
- negate: boolean;
-}
-
-export const parseQuery = memoize((query: string = ""): QueryResult | string => {
- let trimmedQuery = query.trim();
- if (!trimmedQuery) {
- return query;
- }
-
- let negate = false;
- if (trimmedQuery.startsWith("!")) {
- negate = true;
- trimmedQuery = trimmedQuery.substring(trimmedQuery.length, 1);
- }
-
- const [filter, rest] = trimmedQuery.split(" ", 2);
- if (!filter) {
- return query;
- }
-
- const [type, id] = filter.split(":") as [ValidIdSearchTypesUnion, string];
- if (!type || !id || !validIdSearchTypes.includes(type)) {
- return query;
- }
-
- return {
- key: type,
- value: id,
- negate,
- };
-});
-
-export const tokenizeQuery = (query: string) => {
- const parts = query.split(" ").map(parseQuery);
- const queries = parts.filter(p => typeof p !== "string") as QueryResult[];
- const rest = parts.filter(p => typeof p === "string") as string[];
-
- return { queries, rest };
-};
-
-const linkRegex = /[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
-
-export const doesMatch = (type: typeof validIdSearchTypes[number], value: string, message: LoggedMessageJSON) => {
- switch (type) {
- case "in":
- case "channel":
- const channel = ChannelStore.getChannel(message.channel_id);
- if (!channel)
- return message.channel_id === value;
- const { name, id } = channel;
- return id === value
- || name.toLowerCase().includes(value.toLowerCase());
- case "message":
- return message.id === value;
- case "from":
- case "user":
- return message.author.id === value
- || message.author?.username?.toLowerCase().includes(value.toLowerCase())
- || (message.author as any)?.globalName?.toLowerCase()?.includes(value.toLowerCase());
- case "guild":
- case "server": {
- const guildId = message.guildId ?? getGuildIdByChannel(message.channel_id);
- if (!guildId) return false;
-
- const guild = GuildStore.getGuild(guildId);
- if (!guild)
- return guildId === value;
-
- return guild.id === value
- || guild.name.toLowerCase().includes(value.toLowerCase());
- }
- case "before":
- return new Date(message.timestamp) < new Date(value);
- case "after":
- return new Date(message.timestamp) > new Date(value);
- case "around":
- case "near":
- case "during":
- return Math.abs(new Date(message.timestamp)?.getTime() - new Date(value)?.getTime()) < 1000 * 60 * 60 * 24;
- case "has": {
- switch (value) {
- case "attachment":
- return message.attachments.length > 0;
- case "image":
- return message.attachments.some(a => a?.content_type?.startsWith("image")) ||
- message.embeds.some(e => e.image || e.thumbnail);
- case "video":
- return message.attachments.some(a => a?.content_type?.startsWith("video")) ||
- message.embeds.some(e => e.video);
- case "embed":
- return message.embeds.length > 0;
- case "link":
- return message.content.match(linkRegex);
- default:
- return false;
- }
- }
- default:
- return false;
- }
-};
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/saveImage/ImageManager.ts b/src/equicordplugins/messageLoggerEnhanced/utils/saveImage/ImageManager.ts
deleted file mode 100644
index a271e51e..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/saveImage/ImageManager.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import {
- createStore,
- del,
- get,
- keys,
- set,
-} from "@api/DataStore";
-import { sleep } from "@utils/misc";
-
-import { Flogger, Native } from "../..";
-import { LoggedAttachment } from "../../types";
-import { DEFAULT_IMAGE_CACHE_DIR } from "../constants";
-
-const ImageStore = createStore("MessageLoggerImageData", "MessageLoggerImageStore");
-
-interface IDBSavedImage { attachmentId: string, path: string; }
-const idbSavedImages = new Map();
-(async () => {
- try {
-
- const paths = await keys(ImageStore);
- paths.forEach(path => {
- const str = path.toString();
- if (!str.startsWith(DEFAULT_IMAGE_CACHE_DIR)) return;
-
- idbSavedImages.set(str.split("/")?.[1]?.split(".")?.[0], { attachmentId: str.split("/")?.[1]?.split(".")?.[0], path: str });
- });
- } catch (err) {
- Flogger.error("Failed to get idb images", err);
- }
-})();
-
-export async function getImage(attachmentId: string, fileExt?: string | null): Promise {
- // for people who have access to native api but some images are still in idb
- // also for people who dont have native api
- const idbPath = idbSavedImages.get(attachmentId)?.path;
- if (idbPath)
- return get(idbPath, ImageStore);
-
- if (IS_WEB) return null;
-
- return await Native.getImageNative(attachmentId);
-}
-
-export async function downloadAttachment(attachemnt: LoggedAttachment): Promise {
- if (IS_WEB) {
- return await downloadAttachmentWeb(attachemnt);
- }
-
- const { path, error } = await Native.downloadAttachment(attachemnt);
-
- if (error || !path) {
- Flogger.error("Failed to download attachment", error, path);
- return;
- }
-
- return path;
-}
-
-export async function deleteImage(attachmentId: string): Promise {
- const idbPath = idbSavedImages.get(attachmentId)?.path;
- if (idbPath)
- return await del(idbPath, ImageStore);
-
-
- if (IS_WEB) return;
-
- await Native.deleteFileNative(attachmentId);
-}
-
-
-async function downloadAttachmentWeb(attachemnt: LoggedAttachment, attempts = 0) {
- if (!attachemnt?.url || !attachemnt?.id || !attachemnt?.fileExtension) {
- Flogger.error("Invalid attachment", attachemnt);
- return;
- }
-
- const res = await fetch(attachemnt.url);
- if (res.status !== 200) {
- if (res.status === 404 || res.status === 403) return;
- attempts++;
- if (attempts > 3) {
- Flogger.warn(`Failed to get attachment ${attachemnt.id} for caching, error code ${res.status}`);
- return;
- }
-
- await sleep(1000);
- return downloadAttachmentWeb(attachemnt, attempts);
- }
- const ab = await res.arrayBuffer();
- const path = `${DEFAULT_IMAGE_CACHE_DIR}/${attachemnt.id}${attachemnt.fileExtension}`;
-
- // await writeImage(imageCacheDir, `${attachmentId}${fileExtension}`, new Uint8Array(ab));
-
- await set(path, new Uint8Array(ab), ImageStore);
- idbSavedImages.set(attachemnt.id, { attachmentId: attachemnt.id, path });
-
- return path;
-}
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/saveImage/index.ts b/src/equicordplugins/messageLoggerEnhanced/utils/saveImage/index.ts
deleted file mode 100644
index 4a1f159b..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/saveImage/index.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { MessageAttachment } from "discord-types/general";
-
-import { Flogger, settings } from "../..";
-import { LoggedAttachment, LoggedMessage, LoggedMessageJSON } from "../../types";
-import { memoize } from "../memoize";
-import { deleteImage, downloadAttachment, getImage, } from "./ImageManager";
-
-export function getFileExtension(str: string) {
- const matches = str.match(/(\.[a-zA-Z0-9]+)(?:\?.*)?$/);
- if (!matches) return null;
-
- return matches[1];
-}
-
-export function isAttachmentGoodToCache(attachment: MessageAttachment, fileExtension: string) {
- if (attachment.size > settings.store.attachmentSizeLimitInMegabytes * 1024 * 1024) {
- Flogger.log("Attachment too large to cache", attachment.filename);
- return false;
- }
- const attachmentFileExtensionsStr = settings.store.attachmentFileExtensions.trim();
-
- if (attachmentFileExtensionsStr === "")
- return true;
-
- const allowedFileExtensions = attachmentFileExtensionsStr.split(",");
-
- if (fileExtension.startsWith(".")) {
- fileExtension = fileExtension.slice(1);
- }
-
- if (!fileExtension || !allowedFileExtensions.includes(fileExtension)) {
- Flogger.log("Attachment not in allowed file extensions", attachment.filename);
- return false;
- }
-
- return true;
-}
-
-export async function cacheMessageImages(message: LoggedMessage | LoggedMessageJSON) {
- try {
- for (const attachment of message.attachments) {
- const fileExtension = getFileExtension(attachment.filename ?? attachment.url) ?? attachment?.content_type?.split("/")?.[1] ?? ".png";
-
- if (!isAttachmentGoodToCache(attachment, fileExtension)) {
- Flogger.log("skipping", attachment.filename);
- continue;
- }
-
- attachment.oldUrl = attachment.url;
- attachment.oldProxyUrl = attachment.proxy_url;
-
- // only normal urls work if theres a charset in the content type /shrug
- if (attachment?.content_type?.includes(";")) {
- attachment.proxy_url = attachment.url;
- } else {
- // apparently proxy urls last longer
- attachment.url = attachment.proxy_url;
- attachment.proxy_url = attachment.url;
- }
-
- attachment.fileExtension = fileExtension;
-
- const path = await downloadAttachment(attachment);
-
- if (!path) {
- Flogger.error("Failed to cache attachment", attachment);
- continue;
- }
-
- attachment.path = path;
- }
-
- } catch (error) {
- Flogger.error("Error caching message images:", error);
- }
-}
-
-export async function deleteMessageImages(message: LoggedMessage | LoggedMessageJSON) {
- for (let i = 0; i < message.attachments.length; i++) {
- const attachment = message.attachments[i];
- await deleteImage(attachment.id);
- }
-}
-
-export const getAttachmentBlobUrl = memoize(async (attachment: LoggedAttachment) => {
- const imageData = await getImage(attachment.id, attachment.fileExtension);
- if (!imageData) return null;
-
- const blob = new Blob([imageData]);
- const resUrl = URL.createObjectURL(blob);
-
- return resUrl;
-});
diff --git a/src/equicordplugins/messageLoggerEnhanced/utils/settingsUtils.ts b/src/equicordplugins/messageLoggerEnhanced/utils/settingsUtils.ts
deleted file mode 100644
index 6409de5d..00000000
--- a/src/equicordplugins/messageLoggerEnhanced/utils/settingsUtils.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2023 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
-*/
-
-import { chooseFile as chooseFileWeb } from "@utils/web";
-import { Toasts } from "@webpack/common";
-
-import { Native } from "..";
-import { addMessagesBulkIDB, DBMessageRecord, getAllMessagesIDB } from "../db";
-import { LoggedMessage, LoggedMessageJSON } from "../types";
-
-async function getLogContents(): Promise {
- if (IS_WEB) {
- const file = await chooseFileWeb(".json");
- return new Promise((resolve, reject) => {
- if (!file) return reject("No file selected");
-
- const reader = new FileReader();
- reader.onload = () => resolve(reader.result as string);
- reader.onerror = reject;
- reader.readAsText(file);
- });
- }
-
- const settings = await Native.getSettings();
- return Native.chooseFile("Logs", [{ extensions: ["json"], name: "logs" }], settings.logsDir);
-}
-
-export async function importLogs() {
- try {
- const content = await getLogContents();
- const data = JSON.parse(content) as { messages: DBMessageRecord[]; };
-
- let messages: LoggedMessageJSON[] = [];
-
- if ((data as any).deletedMessages || (data as any).editedMessages) {
- messages = Object.values((data as unknown as LoggedMessage)).filter(m => m.message).map(m => m.message) as LoggedMessageJSON[];
- } else
- messages = data.messages.map(m => m.message);
-
- if (!Array.isArray(messages)) {
- throw new Error("Invalid log file format");
- }
-
- if (!messages.length) {
- throw new Error("No messages found in log file");
- }
-
- if (!messages.every(m => m.id && m.channel_id && m.timestamp)) {
- throw new Error("Invalid message format");
- }
-
- await addMessagesBulkIDB(messages);
-
- Toasts.show({
- id: Toasts.genId(),
- message: "Successfully imported logs",
- type: Toasts.Type.SUCCESS
- });
- } catch (e) {
- console.error(e);
-
- Toasts.show({
- id: Toasts.genId(),
- message: "Error importing logs. Check the console for more information",
- type: Toasts.Type.FAILURE
- });
- }
-
-}
-
-export async function exportLogs() {
- const filename = "message-logger-logs-idb.json";
-
- const messages = await getAllMessagesIDB();
- const data = JSON.stringify({ messages }, null, 2);
-
- if (IS_WEB || IS_VESKTOP || IS_EQUIBOP || !DiscordNative) {
- const file = new File([data], filename, { type: "application/json" });
- const a = document.createElement("a");
- a.href = URL.createObjectURL(file);
- a.download = filename;
-
- document.body.appendChild(a);
- a.click();
- setImmediate(() => {
- URL.revokeObjectURL(a.href);
- document.body.removeChild(a);
- });
- } else {
- DiscordNative.fileManager.saveWithDialog(data, filename);
- }
-}
-
diff --git a/src/equicordplugins/moreUserTags/consts.ts b/src/equicordplugins/moreUserTags/consts.ts
new file mode 100644
index 00000000..a10b148a
--- /dev/null
+++ b/src/equicordplugins/moreUserTags/consts.ts
@@ -0,0 +1,74 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { findByCodeLazy, findLazy } from "@webpack";
+import { GuildStore } from "@webpack/common";
+import { RC } from "@webpack/types";
+import { Channel, Guild, Message, User } from "discord-types/general";
+
+import { settings } from "./settings";
+import type { ITag } from "./types";
+
+export const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
+export const tags = [
+ {
+ name: "WEBHOOK",
+ displayName: "Webhook",
+ description: "Messages sent by webhooks",
+ condition: isWebhook
+ }, {
+ name: "OWNER",
+ displayName: "Owner",
+ description: "Owns the server",
+ condition: (_, user, channel) => GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id
+ }, {
+ name: "ADMINISTRATOR",
+ displayName: "Admin",
+ description: "Has the administrator permission",
+ permissions: ["ADMINISTRATOR"]
+ }, {
+ name: "MODERATOR_STAFF",
+ displayName: "Staff",
+ description: "Can manage the server, channels or roles",
+ permissions: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES"]
+ }, {
+ name: "MODERATOR",
+ displayName: "Mod",
+ description: "Can manage messages or kick/ban people",
+ permissions: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"]
+ }, {
+ name: "VOICE_MODERATOR",
+ displayName: "VC Mod",
+ description: "Can manage voice chats",
+ permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"]
+ }, {
+ name: "CHAT_MODERATOR",
+ displayName: "Chat Mod",
+ description: "Can timeout people",
+ permissions: ["MODERATE_MEMBERS"]
+ }, {
+ name: "SPECIAL_USER",
+ displayName: "Special",
+ description: "Special custom tag for specific users",
+ condition: (_message, user) => {
+ const { specialUsers } = settings.store;
+ const userIds = specialUsers.split(",").map(id => id.trim());
+ return userIds.includes(user.id);
+ },
+ verified: true
+ }
+] as const satisfies ITag[];
+
+export const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number | null, className?: string, useRemSizes?: boolean; }> & { Types: Record; };
+
+// PermissionStore.computePermissions will not work here since it only gets permissions for the current user
+export const computePermissions: (options: {
+ user?: { id: string; } | string | null;
+ context?: Guild | Channel | null;
+ overwrites?: Channel["permissionOverwrites"] | null;
+ checkElevated?: boolean /* = true */;
+ excludeGuildPermissions?: boolean /* = false */;
+}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");
diff --git a/src/equicordplugins/moreUserTags/index.tsx b/src/equicordplugins/moreUserTags/index.tsx
new file mode 100644
index 00000000..b45dac04
--- /dev/null
+++ b/src/equicordplugins/moreUserTags/index.tsx
@@ -0,0 +1,185 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import "./styles.css";
+
+import { classNameFactory } from "@api/Styles";
+import { Devs, EquicordDevs } from "@utils/constants";
+import { getCurrentChannel, getIntlMessage } from "@utils/discord";
+import definePlugin from "@utils/types";
+import { ChannelStore, GuildStore, PermissionsBits, SelectedChannelStore, UserStore } from "@webpack/common";
+import { Channel, Message, User } from "discord-types/general";
+
+import { computePermissions, Tag, tags } from "./consts";
+import { settings } from "./settings";
+import { TagSettings } from "./types";
+
+const cl = classNameFactory("vc-mut-");
+
+const genTagTypes = () => {
+ let i = 100;
+ const obj = {};
+
+ for (const { name } of tags) {
+ obj[name] = ++i;
+ obj[i] = name;
+ }
+
+ return obj;
+};
+
+export default definePlugin({
+ name: "MoreUserTags",
+ description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
+ authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN, EquicordDevs.Hen],
+ dependencies: ["MemberListDecoratorsAPI", "NicknameIconsAPI", "MessageDecorationsAPI"],
+ settings,
+ patches: [
+ // Make discord actually use our tags
+ {
+ find: ".STAFF_ONLY_DM:",
+ replacement: [
+ {
+ match: /(?<=type:(\i).{10,1000}.REMIX.{10,100})default:(\i)=/,
+ replace: "default:$2=$self.getTagText($self.localTags[$1]);",
+ },
+ {
+ match: /(?<=type:(\i).{10,1000}.REMIX.{10,100})\.BOT:(?=default:)/,
+ replace: "$&return null;",
+ predicate: () => settings.store.dontShowBotTag
+ },
+ ],
+ }
+ ],
+ start() {
+ const tagSettings = settings.store.tagSettings || {} as TagSettings;
+ for (const tag of Object.values(tags)) {
+ tagSettings[tag.name] ??= {
+ showInChat: true,
+ showInNotChat: true,
+ text: tag.displayName
+ };
+ }
+
+ settings.store.tagSettings = tagSettings;
+ },
+ localTags: genTagTypes(),
+ getChannelId() {
+ return SelectedChannelStore.getChannelId();
+ },
+ renderNicknameIcon(props) {
+ const tagId = this.getTag({
+ user: UserStore.getUser(props.userId),
+ channel: ChannelStore.getChannel(this.getChannelId()),
+ channelId: this.getChannelId(),
+ isChat: false
+ });
+
+ return tagId &&
+ ;
+
+ },
+ renderMessageDecoration(props) {
+ const tagId = this.getTag({
+ message: props.message,
+ user: UserStore.getUser(props.message.author.id),
+ channelId: props.message.channel_id,
+ isChat: false
+ });
+
+ return tagId &&
+ ;
+ },
+ renderMemberListDecorator(props) {
+ const tagId = this.getTag({
+ user: props.user,
+ channel: getCurrentChannel(),
+ channelId: this.getChannelId(),
+ isChat: false
+ });
+
+ return tagId &&
+ ;
+ },
+
+ getTagText(tagName: string) {
+ if (!tagName) return getIntlMessage("APP_TAG");
+ const tag = tags.find(({ name }) => tagName === name);
+ if (!tag) return tagName || getIntlMessage("APP_TAG");
+
+ return settings.store.tagSettings?.[tag.name]?.text || tag.displayName;
+ },
+
+ getTag({
+ message, user, channelId, isChat, channel
+ }: {
+ message?: Message,
+ user?: User & { isClyde(): boolean; },
+ channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
+ channelId?: string;
+ isChat?: boolean;
+ }): number | null {
+ const settings = this.settings.store;
+
+ if (!user) return null;
+ if (isChat && user.id === "1") return null;
+ if (user.isClyde()) return null;
+ if (user.bot && settings.dontShowForBots) return null;
+
+ channel ??= ChannelStore.getChannel(channelId!) as any;
+ if (!channel) return null;
+
+ const perms = this.getPermissions(user, channel);
+
+ for (const tag of tags) {
+ if (isChat && !settings.tagSettings[tag.name].showInChat)
+ continue;
+ if (!isChat && !settings.tagSettings[tag.name].showInNotChat)
+ continue;
+
+ // If the owner tag is disabled, and the user is the owner of the guild,
+ // avoid adding other tags because the owner will always match the condition for them
+ if (
+ (tag.name !== "OWNER" &&
+ GuildStore.getGuild(channel?.guild_id)?.ownerId ===
+ user.id &&
+ isChat &&
+ !settings.tagSettings.OWNER.showInChat) ||
+ (!isChat &&
+ !settings.tagSettings.OWNER.showInNotChat)
+ )
+ continue;
+
+ if ("permissions" in tag ?
+ tag.permissions.some(perm => perms.includes(perm)) :
+ tag.condition(message!, user, channel)) {
+
+ return this.localTags[tag.name];
+ }
+ }
+
+ return null;
+ },
+ getPermissions(user: User, channel: Channel): string[] {
+ const guild = GuildStore.getGuild(channel?.guild_id);
+ if (!guild) return [];
+
+ const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
+ return Object.entries(PermissionsBits)
+ .map(([perm, permInt]) =>
+ permissions & permInt ? perm : ""
+ )
+ .filter(Boolean);
+ },
+});
diff --git a/src/equicordplugins/moreUserTags/settings.ts b/src/equicordplugins/moreUserTags/settings.ts
new file mode 100644
index 00000000..cdce72c9
--- /dev/null
+++ b/src/equicordplugins/moreUserTags/settings.ts
@@ -0,0 +1,21 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { definePluginSettings } from "@api/Settings";
+import { OptionType } from "@utils/types";
+
+export const settings = definePluginSettings({
+ specialUsers: {
+ type: OptionType.STRING,
+ description: "List of special user IDs (comma separated)",
+ default: "878151241769820173",
+ },
+ specialTag: {
+ type: OptionType.STRING,
+ description: "Text to display for special users",
+ default: "Special",
+ }
+});
diff --git a/src/equicordplugins/moreUserTags/settings.tsx b/src/equicordplugins/moreUserTags/settings.tsx
new file mode 100644
index 00000000..d556a21b
--- /dev/null
+++ b/src/equicordplugins/moreUserTags/settings.tsx
@@ -0,0 +1,114 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { definePluginSettings } from "@api/Settings";
+import { Margins } from "@utils/margins";
+import { OptionType } from "@utils/types";
+import { Card, Flex, Forms, Switch, TextInput, Tooltip } from "@webpack/common";
+
+import { Tag, tags } from "./consts";
+import { TagSettings } from "./types";
+
+function SettingsComponent() {
+ const tagSettings = settings.store.tagSettings as TagSettings;
+ const { localTags } = Vencord.Plugins.plugins.MoreUserTags as any;
+
+ return (
+
+
+ {tags.map(t => (
+
+
+
+ {({ onMouseEnter, onMouseLeave }) => (
+
+ {t.displayName} Tag
+
+ )}
+
+
+
+
+
+ Example:
+
+
+
+
+ tagSettings[t.name].text = v}
+ className={Margins.bottom16}
+ />
+
+ tagSettings[t.name].showInChat = v}
+ hideBorder
+ >
+ Show in messages
+
+
+ tagSettings[t.name].showInNotChat = v}
+ hideBorder
+ >
+ Show in member list and profiles
+
+
+ ))}
+
+
+ );
+}
+
+export const settings = definePluginSettings({
+ dontShowForBots: {
+ description: "Don't show extra tags for bots (excluding webhooks)",
+ type: OptionType.BOOLEAN,
+ default: false
+ },
+ dontShowBotTag: {
+ description: "Only show extra tags for bots / Hide [APP] text",
+ type: OptionType.BOOLEAN,
+ default: false,
+ restartNeeded: true
+ },
+ tagSettings: {
+ type: OptionType.COMPONENT,
+ component: SettingsComponent,
+ description: "fill me"
+ },
+ specialUsers: {
+ type: OptionType.STRING,
+ description: "List of special user IDs (comma separated)",
+ default: "878151241769820173",
+ },
+ specialTag: {
+ type: OptionType.STRING,
+ description: "Text to display for special users",
+ default: "Special",
+ }
+});
diff --git a/src/equicordplugins/moreUserTags/styles.css b/src/equicordplugins/moreUserTags/styles.css
new file mode 100644
index 00000000..bf6b34d4
--- /dev/null
+++ b/src/equicordplugins/moreUserTags/styles.css
@@ -0,0 +1,27 @@
+.vc-message-decorations-wrapper .vc-mut-message-tag {
+ margin-bottom: 1px;
+}
+
+/* stylelint-disable-next-line no-descending-specificity */
+.vc-mut-message-tag {
+ /* Remove default margin from tags in messages */
+ margin-top: unset !important;
+
+ /* Align with Discord default tags in messages */
+ /* stylelint-disable-next-line length-zero-no-unit */
+ bottom: 0px;
+ top: -2px;
+ margin-right: 3px;
+}
+
+.vc-mut-message-verified {
+ height: 1rem !important;
+}
+
+span[class*="botTagCozy"][data-moreTags-darkFg="true"]>svg>path {
+ fill: #000;
+}
+
+span[class*="botTagCozy"][data-moreTags-darkFg="false"]>svg>path {
+ fill: #fff;
+}
diff --git a/src/equicordplugins/moreUserTags/types.ts b/src/equicordplugins/moreUserTags/types.ts
new file mode 100644
index 00000000..af072239
--- /dev/null
+++ b/src/equicordplugins/moreUserTags/types.ts
@@ -0,0 +1,33 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import type { Permissions } from "@webpack/types";
+import type { Channel, Message, User } from "discord-types/general";
+
+import { tags } from "./consts";
+
+export type ITag = {
+ // name used for identifying, must be alphanumeric + underscores
+ name: string;
+ // name shown on the tag itself, can be anything probably; automatically uppercase'd
+ displayName: string;
+ description: string;
+ verified?: boolean;
+} & ({
+ permissions: Permissions[];
+} | {
+ condition?(message: Message | null, user: User, channel: Channel): boolean;
+});
+
+export interface TagSetting {
+ text: string;
+ showInChat: boolean;
+ showInNotChat: boolean;
+}
+
+export type TagSettings = {
+ [k in typeof tags[number]["name"]]: TagSetting;
+};
diff --git a/src/equicordplugins/quoter/index.tsx b/src/equicordplugins/quoter/index.tsx
index 3c2770f6..847a2e48 100644
--- a/src/equicordplugins/quoter/index.tsx
+++ b/src/equicordplugins/quoter/index.tsx
@@ -30,7 +30,11 @@ const messagePatch: NavContextMenuPatchCallback = (children, { message }) => {
label="Quote"
icon={QuoteIcon}
action={async () => {
- openModal(props => );
+ if (settings.store.autoSendQuote) {
+ await SendInChat(() => { });
+ } else {
+ openModal(props => );
+ }
}}
/>;
@@ -65,6 +69,11 @@ const settings = definePluginSettings({
{ label: "Username", value: userIDOptions.userName },
{ label: "User ID", value: userIDOptions.userId }
]
+ },
+ autoSendQuote: {
+ type: OptionType.BOOLEAN,
+ description: "Automatically send the quote when clicking the quote button",
+ default: false
}
});
@@ -187,7 +196,7 @@ function registerStyleChange(style) {
}
function QuoteModal(props: ModalProps) {
- const [gray, setGray] = useState(true);
+ const [gray, setGray] = useState(false); // Default to false (disabled)
useEffect(() => {
grayscale = gray;
GeneratePreview();
diff --git a/src/equicordplugins/spotifyLyrics/api.tsx b/src/equicordplugins/spotifyLyrics/api.tsx
deleted file mode 100644
index 1fa5c91c..00000000
--- a/src/equicordplugins/spotifyLyrics/api.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { DataStore } from "@api/index";
-import { Track } from "plugins/spotifyControls/SpotifyStore";
-
-import { getLyricsLrclib } from "./providers/lrclibAPI";
-import { getLyricsSpotify } from "./providers/SpotifyAPI";
-import { LyricsData, Provider, SyncedLyric } from "./providers/types";
-import settings from "./settings";
-
-
-const LyricsCacheKey = "SpotifyLyricsCacheNew";
-
-interface NullLyricCacheEntry {
- [Provider.Lrclib]?: boolean;
- [Provider.Spotify]?: boolean;
-}
-
-const nullLyricCache = new Map();
-
-export const lyricFetchers = {
- [Provider.Spotify]: async (track: Track) => await getLyricsSpotify(track.id),
- [Provider.Lrclib]: getLyricsLrclib,
-};
-
-export const providers = Object.keys(lyricFetchers) as Provider[];
-
-export async function getLyrics(track: Track | null): Promise {
- if (!track) return null;
-
- const cacheKey = track.id;
- const cached = await DataStore.get(LyricsCacheKey) as Record;
-
- if (cached?.[cacheKey]) {
- return cached[cacheKey];
- }
-
- const nullCacheEntry = nullLyricCache.get(cacheKey);
-
- if (nullCacheEntry) {
- const provider = settings.store.LyricsProvider;
- if (!settings.store.FallbackProvider && nullCacheEntry[provider]) {
- return null;
- }
- if (nullCacheEntry[Provider.Spotify] && nullCacheEntry[Provider.Lrclib]) {
- return null;
- }
- }
-
- const providersToTry = [settings.store.LyricsProvider, ...providers.filter(p => p !== settings.store.LyricsProvider)];
-
- for (const provider of providersToTry) {
- const lyricsInfo = await lyricFetchers[provider](track);
-
- if (lyricsInfo) {
- await DataStore.set(LyricsCacheKey, { ...cached, [cacheKey]: lyricsInfo });
- return lyricsInfo;
- }
-
- const updatedNullCacheEntry = nullLyricCache.get(cacheKey) || {};
- nullLyricCache.set(cacheKey, { ...updatedNullCacheEntry, [provider]: true });
- }
-
- return null;
-}
-
-export async function clearLyricsCache() {
- nullLyricCache.clear();
- await DataStore.set(LyricsCacheKey, {});
-}
-
-export async function getLyricsCount(): Promise {
- const cache = await DataStore.get(LyricsCacheKey) as Record;
- return Object.keys(cache).length;
-}
-
-export async function updateLyrics(trackId: string, newLyrics: SyncedLyric[], provider: Provider) {
- const cache = await DataStore.get(LyricsCacheKey) as Record;
- const current = cache[trackId];
-
- await DataStore.set(LyricsCacheKey,
- {
- ...cache, [trackId]: {
- ...current,
- useLyric: provider,
- lyricsVersions: {
- ...current?.lyricsVersions,
- [provider]: newLyrics
- }
- }
- }
- );
-}
-
-export async function removeTranslations() {
- const cache = await DataStore.get(LyricsCacheKey) as Record;
- const newCache = {} as Record;
-
- for (const [trackId, trackData] of Object.entries(cache)) {
- const { Translated, ...lyricsVersions } = trackData?.lyricsVersions || {};
- const newUseLyric = !!lyricsVersions[Provider.Spotify] ? Provider.Spotify : Provider.Lrclib;
-
- newCache[trackId] = { lyricsVersions, useLyric: newUseLyric };
- }
-
- await DataStore.set(LyricsCacheKey, newCache);
-}
-
-export async function migrateOldLyrics() {
- const oldCache = await DataStore.get("SpotifyLyricsCache");
- if (!oldCache || !Object.entries(oldCache).length) return;
-
- const filteredCache = Object.entries(oldCache).filter(lrc => lrc[1]);
- const result = {};
-
- filteredCache.forEach(([trackId, lyrics]) => {
- result[trackId] = {
- lyricsVersions: {
- // @ts-ignore
- LRCLIB: lyrics.map(({ time, text }) => ({ time, text }))
- },
- useLyric: "LRCLIB"
- };
- });
-
- await DataStore.set(LyricsCacheKey, result);
- await DataStore.set("SpotifyLyricsCache", {});
-}
diff --git a/src/equicordplugins/spotifyLyrics/components/ctxMenu.tsx b/src/equicordplugins/spotifyLyrics/components/ctxMenu.tsx
deleted file mode 100644
index 27ba27f5..00000000
--- a/src/equicordplugins/spotifyLyrics/components/ctxMenu.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { copyWithToast } from "@utils/misc";
-import { findComponentByCodeLazy } from "@webpack";
-import { FluxDispatcher, Menu } from "@webpack/common";
-
-import { Provider } from "../providers/types";
-import { useLyrics } from "./util";
-
-const CopyIcon = findComponentByCodeLazy(" 1-.5.5H10a6");
-
-const lyricsActualProviders = [Provider.Lrclib, Provider.Spotify];
-const lyricsAlternative = [Provider.Translated, Provider.Romanized];
-
-function ProviderMenuItem(toProvider: Provider, currentProvider?: Provider) {
- return (
- (!currentProvider || currentProvider !== toProvider) && (
- {
- FluxDispatcher.dispatch({
- // @ts-ignore
- type: "SPOTIFY_LYRICS_PROVIDER_CHANGE",
- provider: toProvider,
- });
- }}
- />
- )
- );
-}
-
-export function LyricsContextMenu() {
- const { lyricsInfo, currLrcIndex } = useLyrics();
-
- const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric];
- const hasAShowingLyric = currLrcIndex !== null && currLrcIndex >= 0;
- const hasLyrics = !!(lyricsInfo?.lyricsVersions[Provider.Lrclib] || lyricsInfo?.lyricsVersions[Provider.Spotify]);
-
- return (
- FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
- aria-label="Spotify Lyrics Menu"
- >
- {hasAShowingLyric && (
- {
- copyWithToast(currentLyrics![currLrcIndex].text!, "Lyric copied!");
- }}
- icon={CopyIcon}
- />
- )}
-
- {lyricsActualProviders.map(provider =>
- ProviderMenuItem(provider, lyricsInfo?.useLyric)
- )}
- {hasLyrics && lyricsAlternative.map(provider =>
- ProviderMenuItem(provider, lyricsInfo?.useLyric)
- )}
-
-
- );
-}
diff --git a/src/equicordplugins/spotifyLyrics/components/lyrics.tsx b/src/equicordplugins/spotifyLyrics/components/lyrics.tsx
deleted file mode 100644
index 9686b009..00000000
--- a/src/equicordplugins/spotifyLyrics/components/lyrics.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { openModal } from "@utils/modal";
-import { ContextMenuApi, React, Text, TooltipContainer, useEffect, useState, useStateFromStores } from "@webpack/common";
-
-import { SpotifyLrcStore } from "../providers/store";
-import settings from "../settings";
-import { LyricsContextMenu } from "./ctxMenu";
-import { LyricsModal } from "./modal";
-import { cl, NoteSvg, useLyrics } from "./util";
-
-function LyricsDisplay() {
- const { ShowMusicNoteOnNoLyrics } = settings.use(["ShowMusicNoteOnNoLyrics"]);
- const { lyricsInfo, lyricRefs, currLrcIndex } = useLyrics();
-
- const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric] || null;
-
- const NoteElement = NoteSvg(cl("music-note"));
-
- const makeClassName = (index: number): string => {
- if (currLrcIndex === null) return "";
-
- const diff = index - currLrcIndex;
-
- if (diff === 0) return cl("current");
- return cl(diff > 0 ? "next" : "prev");
- };
-
- if (!lyricsInfo) {
- return ShowMusicNoteOnNoLyrics && (
- ContextMenuApi.openContextMenu(e, () => )}
- >
-
- {NoteElement}
-
-
- );
- }
-
- return (
- openModal(props =>
)}
- onContextMenu={e => ContextMenuApi.openContextMenu(e, () =>
)}
- >
- {currentLyrics?.map((line, i) => (
-
-
- {line.text || NoteElement}
-
-
- ))}
-
- );
-}
-
-export function Lyrics() {
- const track = useStateFromStores(
- [SpotifyLrcStore],
- () => SpotifyLrcStore.track,
- null,
- (prev, next) => (prev?.id ? prev.id === next?.id : prev?.name === next?.name)
- );
-
- const device = useStateFromStores(
- [SpotifyLrcStore],
- () => SpotifyLrcStore.device,
- null,
- (prev, next) => prev?.id === next?.id
- );
-
- const isPlaying = useStateFromStores([SpotifyLrcStore], () => SpotifyLrcStore.isPlaying);
- const [shouldHide, setShouldHide] = useState(false);
-
- useEffect(() => {
- setShouldHide(false);
- if (!isPlaying) {
- const timeout = setTimeout(() => setShouldHide(true), 1000 * 60 * 5);
- return () => clearTimeout(timeout);
- }
- }, [isPlaying]);
-
- if (!track || !device?.is_active || shouldHide) return null;
-
- return ;
-}
diff --git a/src/equicordplugins/spotifyLyrics/components/modal.tsx b/src/equicordplugins/spotifyLyrics/components/modal.tsx
deleted file mode 100644
index 69815ce0..00000000
--- a/src/equicordplugins/spotifyLyrics/components/modal.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { openImageModal } from "@utils/discord";
-import { ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
-import { React, Text } from "@webpack/common";
-import { SpotifyStore, Track } from "plugins/spotifyControls/SpotifyStore";
-
-import { cl, NoteSvg, scrollClasses, useLyrics } from "./util";
-
-const formatTime = (time: number) => {
- const minutes = Math.floor(time / 60);
- const seconds = Math.floor(time % 60);
- return `${minutes}:${seconds.toString().padStart(2, "0")}`;
-};
-
-function ModalHeaderContent({ track }: { track: Track; }) {
- return (
-
-
- {track?.album?.image?.url && (
-

openImageModal({
- url: track.album.image.url,
- width: track.album.image.width,
- height: track.album.image.height,
- })}
- />
- )}
-
- {track.name}
- by {track.artists.map(a => a.name).join(", ")}
- on {track.album.name}
-
-
-
- );
-}
-
-export function LyricsModal({ rootProps }: { rootProps: ModalProps; }) {
- const { track, lyricsInfo, currLrcIndex } = useLyrics();
- const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric] || null;
-
- return (
-
-
-
-
- {currentLyrics ? (
- currentLyrics.map((line, i) => (
-
- SpotifyStore.seek(line.time * 1000)}>
- {formatTime(line.time)}
-
- {line.text || NoteSvg(cl("modal-note"))}
-
- ))
- ) : (
-
- No lyrics available :(
-
- )}
-
-
-
- );
-}
diff --git a/src/equicordplugins/spotifyLyrics/components/util.tsx b/src/equicordplugins/spotifyLyrics/components/util.tsx
deleted file mode 100644
index 372fe951..00000000
--- a/src/equicordplugins/spotifyLyrics/components/util.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { classNameFactory } from "@api/Styles";
-import { findByPropsLazy } from "@webpack";
-import { React, useEffect, useState, useStateFromStores } from "@webpack/common";
-
-import { SpotifyLrcStore } from "../providers/store";
-import { SyncedLyric } from "../providers/types";
-import settings from "../settings";
-
-export const scrollClasses = findByPropsLazy("auto", "customTheme");
-
-export const cl = classNameFactory("vc-spotify-lyrics-");
-
-export function NoteSvg(className: string) {
- return (
-
- );
-}
-
-const calculateIndexes = (lyrics: SyncedLyric[], position: number, delay: number) => {
- const posInSec = position / 1000;
- const currentIndex = lyrics.findIndex(l => l.time - (delay / 1000) > posInSec && l.time < posInSec + 8) - 1;
- const nextLyric = lyrics.findIndex(l => l.time >= posInSec);
- return [currentIndex, nextLyric];
-};
-
-export function useLyrics() {
- const [track, storePosition, isPlaying, lyricsInfo] = useStateFromStores(
- [SpotifyLrcStore],
- () => [
- SpotifyLrcStore.track!,
- SpotifyLrcStore.mPosition,
- SpotifyLrcStore.isPlaying,
- SpotifyLrcStore.lyricsInfo
- ]
- );
-
- const { LyricDelay } = settings.use(["LyricDelay"]);
-
- const [currLrcIndex, setCurrLrcIndex] = useState(null);
- const [nextLyric, setNextLyric] = useState(null);
- const [position, setPosition] = useState(storePosition);
- const [lyricRefs, setLyricRefs] = useState[]>([]);
-
- const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric] || null;
-
- useEffect(() => {
- if (currentLyrics) {
- setLyricRefs(currentLyrics.map(() => React.createRef()));
- }
- }, [currentLyrics]);
-
-
- useEffect(() => {
- if (currentLyrics && position) {
- const [currentIndex, nextLyric] = calculateIndexes(currentLyrics, position, LyricDelay);
- setCurrLrcIndex(currentIndex);
- setNextLyric(nextLyric);
- }
- }, [currentLyrics, position]);
-
- useEffect(() => {
- if (currLrcIndex !== null) {
- if (currLrcIndex >= 0) {
- lyricRefs[currLrcIndex].current?.scrollIntoView({ behavior: "smooth", block: "center" });
- }
- if (currLrcIndex < 0 && nextLyric !== null) {
- lyricRefs[nextLyric]?.current?.scrollIntoView({ behavior: "smooth", block: "center" });
- }
- }
- }, [currLrcIndex, nextLyric]);
-
- useEffect(() => {
- if (isPlaying) {
- setPosition(SpotifyLrcStore.position);
- const interval = setInterval(() => {
- setPosition(p => p + 1000);
- }, 1000);
-
- return () => clearInterval(interval);
- }
- }, [storePosition, isPlaying]);
-
- return { track, lyricsInfo, lyricRefs, currLrcIndex, nextLyric };
-}
diff --git a/src/equicordplugins/spotifyLyrics/index.tsx b/src/equicordplugins/spotifyLyrics/index.tsx
deleted file mode 100644
index c3d6388f..00000000
--- a/src/equicordplugins/spotifyLyrics/index.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import "./styles.css";
-
-import { Settings } from "@api/Settings";
-import ErrorBoundary from "@components/ErrorBoundary";
-import { Devs } from "@utils/constants";
-import definePlugin from "@utils/types";
-import { Player } from "plugins/spotifyControls/PlayerComponent";
-
-import { migrateOldLyrics } from "./api";
-import { Lyrics } from "./components/lyrics";
-import settings from "./settings";
-
-
-export default definePlugin({
- name: "SpotifyLyrics",
- authors: [Devs.Joona],
- description: "Adds lyrics to SpotifyControls",
- dependencies: ["SpotifyControls"],
- patches: [
- {
- find: "this.isCopiedStreakGodlike",
- replacement: {
- match: /Vencord.Plugins.plugins\["SpotifyControls"\].PanelWrapper/,
- replace: "$self.FakePanelWrapper",
- },
- predicate: () => Settings.plugins.SpotifyControls.enabled,
- noWarn: true,
- },
- ],
- FakePanelWrapper({ VencordOriginal, ...props }) {
- const { LyricsPosition } = settings.use(["LyricsPosition"]);
- return (
- <>
- (
-
-
Failed to render Spotify Lyrics Modal :(
-
Check the console for errors
-
- )}
- >
- {LyricsPosition === "above" && }
-
- {LyricsPosition === "below" && }
-
-
-
- >
- );
- },
- settings,
- async start() {
- await migrateOldLyrics();
- },
-});
diff --git a/src/equicordplugins/spotifyLyrics/providers/SpotifyAPI/index.ts b/src/equicordplugins/spotifyLyrics/providers/SpotifyAPI/index.ts
deleted file mode 100644
index 39e80cf1..00000000
--- a/src/equicordplugins/spotifyLyrics/providers/SpotifyAPI/index.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { LyricsData, Provider } from "../types";
-
-interface LyricsAPIResp {
- error: boolean;
- syncType: string;
- lines: Line[];
-}
-
-interface Line {
- startTimeMs: string;
- words: string;
- syllables: any[];
- endTimeMs: string;
-}
-
-
-export async function getLyricsSpotify(trackId: string): Promise {
- const resp = await fetch("https://spotify-lyrics-api-pi.vercel.app/?trackid=" + trackId);
- if (!resp.ok) return null;
-
- let data: LyricsAPIResp;
- try {
- data = await resp.json() as LyricsAPIResp;
- } catch (e) {
- return null;
- }
-
- const lyrics = data.lines;
- if (lyrics[0].startTimeMs === "0" && lyrics[lyrics.length - 1].startTimeMs === "0") return null;
-
- return {
- useLyric: Provider.Spotify,
- lyricsVersions: {
- Spotify: lyrics.map(line => {
- const trimmedText = line.words.trim();
- return {
- time: Number(line.startTimeMs) / 1000,
- text: (trimmedText === "" || trimmedText === "♪") ? null : trimmedText
- };
- })
- }
- };
-}
diff --git a/src/equicordplugins/spotifyLyrics/providers/lrclibAPI/index.ts b/src/equicordplugins/spotifyLyrics/providers/lrclibAPI/index.ts
deleted file mode 100644
index e9981763..00000000
--- a/src/equicordplugins/spotifyLyrics/providers/lrclibAPI/index.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { Track } from "plugins/spotifyControls/SpotifyStore";
-
-import { LyricsData, Provider } from "../types";
-
-const baseUrlLrclib = "https://lrclib.net/api/get";
-
-interface LrcLibResponse {
- id: number;
- name: string;
- trackName: string;
- artistName: string;
- albumName: string;
- duration: number;
- instrumental: boolean;
- plainLyrics: string | null;
- syncedLyrics: string | null;
-}
-
-function lyricTimeToSeconds(time: string) {
- const [minutes, seconds] = time.slice(1, -1).split(":").map(Number);
- return minutes * 60 + seconds;
-}
-
-export async function getLyricsLrclib(track: Track): Promise {
- const info = {
- track_name: track.name,
- artist_name: track.artists[0].name,
- album_name: track.album.name,
- duration: track.duration / 1000
- };
-
- const params = new URLSearchParams(info as any);
- const url = `${baseUrlLrclib}?${params.toString()}`;
- const response = await fetch(url, {
- headers: {
- "User-Agent": "https://github.com/Masterjoona/vc-spotifylyrics"
- }
- });
-
- if (!response.ok) return null;
-
- const data = await response.json() as LrcLibResponse;
- if (!data.syncedLyrics) return null;
-
- const lyrics = data.syncedLyrics;
- const lines = lyrics.split("\n").filter(line => line.trim() !== "");
-
- return {
- useLyric: Provider.Lrclib,
- lyricsVersions: {
- LRCLIB: lines.map(line => {
- const [lrcTime, text] = line.split("]");
- const trimmedText = text.trim();
- return {
- time: lyricTimeToSeconds(lrcTime),
- text: (trimmedText === "" || trimmedText === "♪") ? null : trimmedText
- };
- })
- }
- };
-}
diff --git a/src/equicordplugins/spotifyLyrics/providers/store.ts b/src/equicordplugins/spotifyLyrics/providers/store.ts
deleted file mode 100644
index 580af799..00000000
--- a/src/equicordplugins/spotifyLyrics/providers/store.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { showNotification } from "@api/Notifications";
-import { proxyLazyWebpack } from "@webpack";
-import { Flux, FluxDispatcher } from "@webpack/common";
-import { Track } from "plugins/spotifyControls/SpotifyStore";
-
-import { getLyrics, lyricFetchers, updateLyrics } from "../api";
-import settings from "../settings";
-import { romanizeLyrics, translateLyrics } from "./translator";
-import { LyricsData, Provider } from "./types";
-
-interface PlayerStateMin {
- track: Track | null;
- device?: Device;
- isPlaying: boolean,
- position: number,
-}
-
-interface Device {
- id: string;
- is_active: boolean;
-}
-
-function showNotif(title: string, body: string) {
- if (settings.store.ShowFailedToasts) {
- showNotification({
- color: "#ee2902",
- title,
- body,
- noPersist: true
- });
- }
-}
-
-// steal from spotifycontrols
-export const SpotifyLrcStore = proxyLazyWebpack(() => {
- class SpotifyLrcStore extends Flux.Store {
- public mPosition = 0;
- private start = 0;
-
- public track: Track | null = null;
- public device: Device | null = null;
- public isPlaying = false;
- public lyricsInfo: LyricsData | null = null;
- public fetchingsTracks: string[] = [];
-
-
- public get position(): number {
- let pos = this.mPosition;
- if (this.isPlaying) {
- pos += Date.now() - this.start;
- }
- return pos;
- }
-
- public set position(p: number) {
- this.mPosition = p;
- this.start = Date.now();
- }
- }
-
- const store = new SpotifyLrcStore(FluxDispatcher, {
- async SPOTIFY_PLAYER_STATE(e: PlayerStateMin) {
- if (store.fetchingsTracks.includes(e.track?.id ?? "")) return;
-
- store.fetchingsTracks.push(e.track?.id ?? "");
- store.track = e.track;
- store.isPlaying = e.isPlaying ?? false;
- store.position = e.position ?? 0;
- store.device = e.device ?? null;
- store.lyricsInfo = await getLyrics(e.track);
- const { LyricsConversion } = settings.store;
- if (LyricsConversion !== Provider.None) {
- // @ts-ignore
- FluxDispatcher.dispatch({ type: "SPOTIFY_LYRICS_PROVIDER_CHANGE", provider: LyricsConversion });
- }
-
- store.fetchingsTracks = store.fetchingsTracks.filter(id => id !== e.track?.id);
- store.emitChange();
- },
-
- SPOTIFY_SET_DEVICES({ devices }: { devices: Device[]; }) {
- store.device = devices.find(d => d.is_active) ?? devices[0] ?? null;
- store.emitChange();
- },
-
- // @ts-ignore
- async SPOTIFY_LYRICS_PROVIDER_CHANGE(e: { provider: Provider; }) {
- const currentInfo = await getLyrics(store.track);
- const { provider } = e;
- if (currentInfo?.useLyric === provider) return;
-
- if (currentInfo?.lyricsVersions[provider]) {
- store.lyricsInfo = { ...currentInfo, useLyric: provider };
-
- await updateLyrics(store.track!.id, currentInfo.lyricsVersions[provider]!, provider);
- store.emitChange();
- return;
- }
-
- if (provider === Provider.Translated || provider === Provider.Romanized) {
- if (!currentInfo?.lyricsVersions[Provider.Spotify] && !currentInfo?.lyricsVersions[Provider.Lrclib]) {
- showNotif("No lyrics", `No lyrics to ${provider === Provider.Translated ? "translate" : "romanize"}`);
- return;
- }
-
- const fetcher = provider === Provider.Translated ? translateLyrics : romanizeLyrics;
-
- const fetchResult = await fetcher(currentInfo.lyricsVersions[currentInfo.useLyric]);
-
- if (!fetchResult) {
- showNotif("Lyrics fetch failed", `Failed to fetch ${provider === Provider.Translated ? "translation" : "romanization"}`);
- return;
- }
-
- store.lyricsInfo = {
- ...currentInfo,
- useLyric: provider,
- lyricsVersions: {
- ...currentInfo.lyricsVersions,
- [provider]: fetchResult
- }
- };
-
- await updateLyrics(store.track!.id, fetchResult, provider);
-
- store.emitChange();
- return;
- }
-
- const newLyricsInfo = await lyricFetchers[e.provider](store.track!);
- if (!newLyricsInfo) {
- showNotif("Lyrics fetch failed", `Failed to fetch ${e.provider} lyrics`);
- return;
- }
-
- store.lyricsInfo = newLyricsInfo;
-
- updateLyrics(store.track!.id, newLyricsInfo.lyricsVersions[e.provider], e.provider);
-
- store.emitChange();
- }
- });
- return store;
-});
-
diff --git a/src/equicordplugins/spotifyLyrics/providers/translator/index.ts b/src/equicordplugins/spotifyLyrics/providers/translator/index.ts
deleted file mode 100644
index 74303508..00000000
--- a/src/equicordplugins/spotifyLyrics/providers/translator/index.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import settings from "../../settings";
-import { LyricsData, Provider, SyncedLyric } from "../types";
-
-// stolen from src\plugins\translate\utils.ts
-
-interface GoogleData {
- src: string;
- sentences: {
- // 🏳️⚧️
- trans: string;
- orig: string;
- src_translit?: string;
- }[];
-}
-
-async function googleTranslate(text: string, targetLang: string, romanize: boolean): Promise {
- const url = "https://translate.googleapis.com/translate_a/single?" + new URLSearchParams({
- // see https://stackoverflow.com/a/29537590 for more params
- // holy shidd nvidia
- client: "gtx",
- // source language
- sl: "auto",
- // target language
- tl: targetLang,
- // what to return, t = translation probably
- dt: romanize ? "rm" : "t",
- // Send json object response instead of weird array
- dj: "1",
- source: "input",
- // query, duh
- q: text
- });
-
- const res = await fetch(url);
- if (!res.ok)
- return null;
-
- return await res.json();
-}
-
-async function processLyrics(
- lyrics: LyricsData["lyricsVersions"][Provider],
- targetLang: string,
- romanize: boolean
-): Promise {
- if (!lyrics) return null;
-
- const nonDuplicatedLyrics = lyrics.filter((lyric, index, self) =>
- self.findIndex(l => l.text === lyric.text) === index
- );
-
- const processedLyricsResp = await Promise.all(
- nonDuplicatedLyrics.map(async lyric => {
- if (!lyric.text) return [lyric.text, null];
-
- const translation = await googleTranslate(lyric.text, targetLang, romanize);
-
- if (!translation || !translation.sentences || translation.sentences.length === 0) return [lyric.text, null];
-
- return [lyric.text, romanize ? translation.sentences[0].src_translit : translation.sentences[0].trans];
- })
- );
-
- if (processedLyricsResp[0][1] === null) return null;
-
- return lyrics.map(lyric => ({
- ...lyric,
- text: processedLyricsResp.find(mapping => mapping[0] === lyric.text)?.[1] ?? lyric.text
- }));
-}
-
-export async function translateLyrics(lyrics: LyricsData["lyricsVersions"][Provider]): Promise {
- const language = settings.store.TranslateTo;
- // Why not make only one request to translate?
- // because occasionally it will add a new line
- // and i dont have a good way to handle that
- return processLyrics(lyrics, language, false);
-}
-
-export async function romanizeLyrics(lyrics: LyricsData["lyricsVersions"][Provider]): Promise {
- // Why not make only one request to romanize?
- // it will romanize it as one string, and how would i know where to split it?
- return processLyrics(lyrics, "", true);
-}
diff --git a/src/equicordplugins/spotifyLyrics/providers/translator/languages.ts b/src/equicordplugins/spotifyLyrics/providers/translator/languages.ts
deleted file mode 100644
index db87a4a8..00000000
--- a/src/equicordplugins/spotifyLyrics/providers/translator/languages.ts
+++ /dev/null
@@ -1,546 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-// hate having this twice but oh well
-export default [
- {
- value: "auto",
- label: "Detect language"
- },
- {
- value: "af",
- label: "Afrikaans"
- },
- {
- value: "sq",
- label: "Albanian"
- },
- {
- value: "am",
- label: "Amharic"
- },
- {
- value: "ar",
- label: "Arabic"
- },
- {
- value: "hy",
- label: "Armenian"
- },
- {
- value: "as",
- label: "Assamese"
- },
- {
- value: "ay",
- label: "Aymara"
- },
- {
- value: "az",
- label: "Azerbaijani"
- },
- {
- value: "bm",
- label: "Bambara"
- },
- {
- value: "eu",
- label: "Basque"
- },
- {
- value: "be",
- label: "Belarusian"
- },
- {
- value: "bn",
- label: "Bengali"
- },
- {
- value: "bho",
- label: "Bhojpuri"
- },
- {
- value: "bs",
- label: "Bosnian"
- },
- {
- value: "bg",
- label: "Bulgarian"
- },
- {
- value: "ca",
- label: "Catalan"
- },
- {
- value: "ceb",
- label: "Cebuano"
- },
- {
- value: "ny",
- label: "Chichewa"
- },
- {
- value: "zh-CN",
- label: "Chinese (Simplified)"
- },
- {
- value: "zh-TW",
- label: "Chinese (Traditional)"
- },
- {
- value: "co",
- label: "Corsican"
- },
- {
- value: "hr",
- label: "Croatian"
- },
- {
- value: "cs",
- label: "Czech"
- },
- {
- value: "da",
- label: "Danish"
- },
- {
- value: "dv",
- label: "Dhivehi"
- },
- {
- value: "doi",
- label: "Dogri"
- },
- {
- value: "nl",
- label: "Dutch"
- },
- {
- value: "en",
- label: "English",
- default: true
- },
- {
- value: "eo",
- label: "Esperanto"
- },
- {
- value: "et",
- label: "Estonian"
- },
- {
- value: "ee",
- label: "Ewe"
- },
- {
- value: "tl",
- label: "Filipino"
- },
- {
- value: "fi",
- label: "Finnish"
- },
- {
- value: "fr",
- label: "French"
- },
- {
- value: "fy",
- label: "Frisian"
- },
- {
- value: "gl",
- label: "Galician"
- },
- {
- value: "ka",
- label: "Georgian"
- },
- {
- value: "de",
- label: "German"
- },
- {
- value: "el",
- label: "Greek"
- },
- {
- value: "gn",
- label: "Guarani"
- },
- {
- value: "gu",
- label: "Gujarati"
- },
- {
- value: "ht",
- label: "Haitian Creole"
- },
- {
- value: "ha",
- label: "Hausa"
- },
- {
- value: "haw",
- label: "Hawaiian"
- },
- {
- value: "iw",
- label: "Hebrew"
- },
- {
- value: "hi",
- label: "Hindi"
- },
- {
- value: "hmn",
- label: "Hmong"
- },
- {
- value: "hu",
- label: "Hungarian"
- },
- {
- value: "is",
- label: "Icelandic"
- },
- {
- value: "ig",
- label: "Igbo"
- },
- {
- value: "ilo",
- label: "Ilocano"
- },
- {
- value: "id",
- label: "Indonesian"
- },
- {
- value: "ga",
- label: "Irish"
- },
- {
- value: "it",
- label: "Italian"
- },
- {
- value: "ja",
- label: "Japanese"
- },
- {
- value: "jw",
- label: "Javanese"
- },
- {
- value: "kn",
- label: "Kannada"
- },
- {
- value: "kk",
- label: "Kazakh"
- },
- {
- value: "km",
- label: "Khmer"
- },
- {
- value: "rw",
- label: "Kinyarwanda"
- },
- {
- value: "gom",
- label: "Konkani"
- },
- {
- value: "ko",
- label: "Korean"
- },
- {
- value: "kri",
- label: "Krio"
- },
- {
- value: "ku",
- label: "Kurdish (Kurmanji)"
- },
- {
- value: "ckb",
- label: "Kurdish (Sorani)"
- },
- {
- value: "ky",
- label: "Kyrgyz"
- },
- {
- value: "lo",
- label: "Lao"
- },
- {
- value: "la",
- label: "Latin"
- },
- {
- value: "lv",
- label: "Latvian"
- },
- {
- value: "ln",
- label: "Lingala"
- },
- {
- value: "lt",
- label: "Lithuanian"
- },
- {
- value: "lg",
- label: "Luganda"
- },
- {
- value: "lb",
- label: "Luxembourgish"
- },
- {
- value: "mk",
- label: "Macedonian"
- },
- {
- value: "mai",
- label: "Maithili"
- },
- {
- value: "mg",
- label: "Malagasy"
- },
- {
- value: "ms",
- label: "Malay"
- },
- {
- value: "ml",
- label: "Malayalam"
- },
- {
- value: "mt",
- label: "Maltese"
- },
- {
- value: "mi",
- label: "Maori"
- },
- {
- value: "mr",
- label: "Marathi"
- },
- {
- value: "mni-Mtei",
- label: "Meiteilon (Manipuri)"
- },
- {
- value: "lus",
- label: "Mizo"
- },
- {
- value: "mn",
- label: "Mongolian"
- },
- {
- value: "my",
- label: "Myanmar (Burmese)"
- },
- {
- value: "ne",
- label: "Nepali"
- },
- {
- value: "no",
- label: "Norwegian"
- },
- {
- value: "or",
- label: "Odia (Oriya)"
- },
- {
- value: "om",
- label: "Oromo"
- },
- {
- value: "ps",
- label: "Pashto"
- },
- {
- value: "fa",
- label: "Persian"
- },
- {
- value: "pl",
- label: "Polish"
- },
- {
- value: "pt",
- label: "Portuguese"
- },
- {
- value: "pa",
- label: "Punjabi"
- },
- {
- value: "qu",
- label: "Quechua"
- },
- {
- value: "ro",
- label: "Romanian"
- },
- {
- value: "ru",
- label: "Russian"
- },
- {
- value: "sm",
- label: "Samoan"
- },
- {
- value: "sa",
- label: "Sanskrit"
- },
- {
- value: "gd",
- label: "Scots Gaelic"
- },
- {
- value: "nso",
- label: "Sepedi"
- },
- {
- value: "sr",
- label: "Serbian"
- },
- {
- value: "st",
- label: "Sesotho"
- },
- {
- value: "sn",
- label: "Shona"
- },
- {
- value: "sd",
- label: "Sindhi"
- },
- {
- value: "si",
- label: "Sinhala"
- },
- {
- value: "sk",
- label: "Slovak"
- },
- {
- value: "sl",
- label: "Slovenian"
- },
- {
- value: "so",
- label: "Somali"
- },
- {
- value: "es",
- label: "Spanish"
- },
- {
- value: "su",
- label: "Sundanese"
- },
- {
- value: "sw",
- label: "Swahili"
- },
- {
- value: "sv",
- label: "Swedish"
- },
- {
- value: "tg",
- label: "Tajik"
- },
- {
- value: "ta",
- label: "Tamil"
- },
- {
- value: "tt",
- label: "Tatar"
- },
- {
- value: "te",
- label: "Telugu"
- },
- {
- value: "th",
- label: "Thai"
- },
- {
- value: "ti",
- label: "Tigrinya"
- },
- {
- value: "ts",
- label: "Tsonga"
- },
- {
- value: "tr",
- label: "Turkish"
- },
- {
- value: "tk",
- label: "Turkmen"
- },
- {
- value: "ak",
- label: "Twi"
- },
- {
- value: "uk",
- label: "Ukrainian"
- },
- {
- value: "ur",
- label: "Urdu"
- },
- {
- value: "ug",
- label: "Uyghur"
- },
- {
- value: "uz",
- label: "Uzbek"
- },
- {
- value: "vi",
- label: "Vietnamese"
- },
- {
- value: "cy",
- label: "Welsh"
- },
- {
- value: "xh",
- label: "Xhosa"
- },
- {
- value: "yi",
- label: "Yiddish"
- },
- {
- value: "yo",
- label: "Yoruba"
- },
- {
- value: "zu",
- label: "Zulu"
- }
-];
diff --git a/src/equicordplugins/spotifyLyrics/providers/types.ts b/src/equicordplugins/spotifyLyrics/providers/types.ts
deleted file mode 100644
index 867f8e55..00000000
--- a/src/equicordplugins/spotifyLyrics/providers/types.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-export interface SyncedLyric {
- time: number;
- text: string | null;
-}
-
-export enum Provider {
- Lrclib = "LRCLIB",
- Spotify = "Spotify",
- Translated = "Translated",
- Romanized = "Romanized",
- None = "None",
-}
-
-export interface LyricsData {
- lyricsVersions: Partial>;
- useLyric: Provider;
-}
diff --git a/src/equicordplugins/spotifyLyrics/settings.tsx b/src/equicordplugins/spotifyLyrics/settings.tsx
deleted file mode 100644
index 032b5a8e..00000000
--- a/src/equicordplugins/spotifyLyrics/settings.tsx
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { definePluginSettings } from "@api/Settings";
-import { makeRange, SettingSliderComponent } from "@components/PluginSettings/components";
-import { useAwaiter } from "@utils/react";
-import { OptionType } from "@utils/types";
-import { Button, showToast, Text, Toasts, useMemo } from "@webpack/common";
-
-import { clearLyricsCache, getLyricsCount, removeTranslations } from "./api";
-import { Lyrics } from "./components/lyrics";
-import { useLyrics } from "./components/util";
-import languages from "./providers/translator/languages";
-import { Provider } from "./providers/types";
-
-const sliderOptions = {
- markers: makeRange(-2500, 2500, 250),
- stickToMarkers: true,
-};
-
-function Details() {
- const { lyricsInfo } = useLyrics();
-
- const [count, error, loading] = useAwaiter(
- useMemo(() => getLyricsCount, []),
- {
- onError: () => console.error("Failed to get lyrics count"),
- fallbackValue: null,
- }
- );
-
- return (
- <>
- Current lyrics provider: {lyricsInfo?.useLyric || "None"}
- {loading ? Loading lyrics count... : error ? Failed to get lyrics count : Lyrics count: {count}}
- >
- );
-}
-
-const settings = definePluginSettings({
- ShowMusicNoteOnNoLyrics: {
- description: "Show a music note icon when no lyrics are found",
- type: OptionType.BOOLEAN,
- default: true,
- },
- LyricsPosition: {
- description: "Position of the lyrics",
- type: OptionType.SELECT,
- options: [
- { value: "above", label: "Above SpotifyControls" },
- { value: "below", label: "Below SpotifyControls", default: true },
- ],
- },
- LyricsProvider: {
- description: "Where lyrics are fetched from",
- type: OptionType.SELECT,
- options: [
- { value: Provider.Spotify, label: "Spotify (Musixmatch)", default: true },
- { value: Provider.Lrclib, label: "LRCLIB" },
- ],
- },
- FallbackProvider: {
- description: "When a lyrics provider fails, try other providers",
- type: OptionType.BOOLEAN,
- default: true,
- },
- TranslateTo: {
- description: "Translate lyrics to - Changing this will clear existing translations",
- type: OptionType.SELECT,
- options: languages,
- onChange: async () => {
- await removeTranslations();
- showToast("Translations cleared", Toasts.Type.SUCCESS);
- }
- },
- LyricsConversion: {
- description: "Automatically translate or romanize lyrics",
- type: OptionType.SELECT,
- options: [
- { value: Provider.None, label: "None", default: true },
- { value: Provider.Translated, label: "Translate" },
- { value: Provider.Romanized, label: "Romanize" },
- ]
- },
- ShowFailedToasts: {
- description: "Hide toasts when lyrics fail to fetch",
- type: OptionType.BOOLEAN,
- default: true,
- },
- LyricDelay: {
- description: "",
- type: OptionType.SLIDER,
- default: 0,
- hidden: true,
- ...sliderOptions
- },
- Display: {
- description: "",
- type: OptionType.COMPONENT,
- component: () => (
- <>
- {
- settings.store.LyricDelay = v;
- }}
- pluginSettings={Vencord.Settings.plugins.SpotifyLyrics}
- id={"LyricDelay"}
- onError={() => { }}
- />
-
- >
- )
- },
- Details: {
- description: "",
- type: OptionType.COMPONENT,
- component: () => ,
- },
- PurgeLyricsCache: {
- description: "Purge the lyrics cache",
- type: OptionType.COMPONENT,
- component: () => (
-
- ),
- },
- TestingCache: {
- description: "Save songs to a testing cache instead",
- type: OptionType.BOOLEAN,
- default: false,
- hidden: true,
- }
-});
-
-export default settings;
diff --git a/src/equicordplugins/spotifyLyrics/styles.css b/src/equicordplugins/spotifyLyrics/styles.css
deleted file mode 100644
index e148cfbd..00000000
--- a/src/equicordplugins/spotifyLyrics/styles.css
+++ /dev/null
@@ -1,78 +0,0 @@
-.vc-spotify-lyrics {
- padding: 0.375rem 0.5rem;
- border-bottom: 1px solid var(--background-modifier-accent);
- text-align: center;
- overflow: hidden;
- max-height: 60px;
- cursor: pointer;
-}
-
-.vc-spotify-lyrics-music-note {
- font-size: 16px;
- text-align: center;
- animation: side-to-side 2s ease-in-out infinite;
- color: var(--text-muted);
- height: 18px;
- margin: 3px;
-}
-
-.vc-spotify-lyrics-next,
-.vc-spotify-lyrics-prev {
- opacity: 0.6;
- margin-bottom: 1%;
-}
-
-@keyframes side-to-side {
- 0%,
- 100% {
- transform: translateX(0);
- }
-
- 50% {
- transform: translateX(-10px);
- }
-}
-
-.vc-spotify-lyrics-header-content {
- display: flex;
- align-items: center;
- width: 100%;
-}
-
-.vc-spotify-lyrics-album-image {
- width: 100px;
- height: 100px;
- margin-right: 5%;
- border-radius: 5px;
- cursor: pointer;
-}
-
-.vc-spotify-lyrics-modal-line-current,
-.vc-spotify-lyrics-modal-line {
- margin: 4px 0;
- padding: 4px 8px;
- position: relative;
-}
-
-.vc-spotify-lyrics-modal-line-current {
- font-weight: bold;
-}
-
-.vc-spotify-lyrics-modal-note {
- height: 1lh;
-}
-
-.vc-spotify-lyrics-modal-timestamp {
- color: var(--text-muted);
- margin-right: 0.5em;
- cursor: pointer;
-}
-
-.vc-spotify-lyrics-modal-timestamp:hover {
- text-decoration: underline;
-}
-
-.vc-spotify-lyrics-modal-no-lyrics {
- text-align: center;
- padding: 1rem;
-}
diff --git a/src/equicordplugins/woof/index.tsx b/src/equicordplugins/woof/index.tsx
deleted file mode 100644
index 7a06a715..00000000
--- a/src/equicordplugins/woof/index.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Vencord, a Discord client mod
- * Copyright (c) 2024 Vendicated and contributors
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-import { addChatBarButton, ChatBarButton, ChatBarButtonFactory, removeChatBarButton } from "@api/ChatButtons";
-import { Devs } from "@utils/constants";
-import { getCurrentChannel, sendMessage } from "@utils/discord";
-import definePlugin from "@utils/types";
-
-async function handleButtonClick() {
- // @ts-expect-error typing issue
- sendMessage(getCurrentChannel().id, { content: "woof" });
-}
-
-const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
- if (!isMainChat) return null;
- return (
-
-
-
- );
-};
-
-export default definePlugin({
- name: "Woof",
- description: "Adds a chatbar button to woof in chat",
- authors: [Devs.Samwich],
- start: () => addChatBarButton("Woof", ChatBarIcon),
- stop: () => removeChatBarButton("Woof")
-});
diff --git a/src/main/utils/extensions.ts b/src/main/utils/extensions.ts
index 4af7129f..3f36831b 100644
--- a/src/main/utils/extensions.ts
+++ b/src/main/utils/extensions.ts
@@ -71,7 +71,7 @@ export async function installExt(id: string) {
const buf = await get(url, {
headers: {
- "User-Agent": `Electron ${process.versions.electron} ~ Equicord (https://github.com/Equicord/Equicord)`
+ "User-Agent": `Electron ${process.versions.electron} ~ Equicord (https://github.com/Rayanzay/ryncord)`
}
});
diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx
index fa09561b..2234c612 100644
--- a/src/plugins/_core/supportHelper.tsx
+++ b/src/plugins/_core/supportHelper.tsx
@@ -83,7 +83,7 @@ async function generateDebugInfoMessage() {
const info = {
Equicord:
- `v${VERSION} • [${gitHash}]()` +
+ `v${VERSION} • [${gitHash}]()` +
`${SettingsPlugin.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
Client: `${RELEASE_CHANNEL} ~ ${client}`,
Platform: typeof DiscordNative !== "undefined" ?
@@ -265,8 +265,8 @@ export default definePlugin({
You are using a custom build of Equicord, which we do not provide support for!
- We only provide support for official builds.
- Either switch to an official build or figure your issue out yourself.
+ We only provide support for official builds.
+ Either switch to an official build or figure your issue out yourself.
You will be banned from receiving support if you ignore this rule.