+76
packages/core-extensions/src/commands/index.ts
+76
packages/core-extensions/src/commands/index.ts
···
1
+
import { Patch, ExtensionWebpackModule } from "@moonlight-mod/types";
2
+
import { APPLICATION_ID } from "@moonlight-mod/types/coreExtensions/commands";
3
+
4
+
export const patches: Patch[] = [
5
+
{
6
+
find: ".fI5MTU)", // COMMAND_SECTION_BUILT_IN_NAME
7
+
replace: [
8
+
// inject commands
9
+
{
10
+
match: /return (\i)=\i/,
11
+
replacement: (orig, commands) =>
12
+
`${commands}=[...${commands},...require("commands_commands").default._getCommands()];${orig}`
13
+
},
14
+
15
+
// section
16
+
{
17
+
match: /(?<=\i={)(?=\[\i\.\i\.BUILT_IN]:{id:\i\.\i\.BUILT_IN,type:(\i.\i\.BUILT_IN))/,
18
+
replacement: (_, type) =>
19
+
`"${APPLICATION_ID}":{id:"${APPLICATION_ID}",type:${type},get name(){return "moonlight"}},`
20
+
}
21
+
]
22
+
},
23
+
24
+
// index our section
25
+
{
26
+
find: '"ApplicationCommandIndexStore"',
27
+
replace: {
28
+
match: /(?<=let \i=(\i)\((\i\.\i)\[\i\.\i\.BUILT_IN\],(\i),!0,!0,(\i)\);)null!=(\i)&&(\i)\.push\(\i\)/,
29
+
replacement: (_, createSection, sections, deny, props, section, commands) =>
30
+
`null!=${section}&&(${section}.data=${section}.data.filter(c=>c.applicationId=="-1"));
31
+
null!=${section}&&${commands}.push(${section});
32
+
const moonlightCommands=${createSection}(${sections}["${APPLICATION_ID}"],${deny},!0,!0,${props});
33
+
null!=moonlightCommands&&(moonlightCommands.data=moonlightCommands.data.filter(c=>c.applicationId=="${APPLICATION_ID}"));
34
+
null!=moonlightCommands&&${commands}.push(moonlightCommands)`
35
+
}
36
+
},
37
+
38
+
// grab legacy commands (needed for adding actions that act like sed/plus reacting)
39
+
{
40
+
find: "={tts:{action:",
41
+
replace: {
42
+
match: /Object\.setPrototypeOf\((\i),null\)/,
43
+
replacement: (_, legacyCommands) => `require("commands_commands")._getLegacyCommands(${legacyCommands})`
44
+
}
45
+
},
46
+
47
+
// add icon
48
+
{
49
+
find: ",hasSpaceTerminator:",
50
+
replace: {
51
+
match: /(\i)\.type===/,
52
+
replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}`
53
+
}
54
+
},
55
+
{
56
+
find: ".icon,bot:null===",
57
+
replace: {
58
+
match: /(\.useMemo\(\(\)=>{)(if\((\i)\.type)/,
59
+
replacement: (_, before, after, section) => `${before}
60
+
if (${section}.id==="${APPLICATION_ID}") return "https://moonlight-mod.github.io/favicon.png";
61
+
${after}`
62
+
}
63
+
},
64
+
// fix icon sizing because they expect built in to be 24 and others to be 32
65
+
{
66
+
find: ".builtInSeparator}):null]",
67
+
replace: {
68
+
match: /(\i)\.type===\i\.\i\.BUILT_IN/,
69
+
replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}`
70
+
}
71
+
}
72
+
];
73
+
74
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
75
+
commands: {}
76
+
};
+11
packages/core-extensions/src/commands/manifest.json
+11
packages/core-extensions/src/commands/manifest.json
+55
packages/core-extensions/src/commands/webpackModules/commands.ts
+55
packages/core-extensions/src/commands/webpackModules/commands.ts
···
1
+
import {
2
+
APPLICATION_ID,
3
+
Commands,
4
+
LegacyCommand,
5
+
RegisteredCommand
6
+
} from "@moonlight-mod/types/coreExtensions/commands";
7
+
8
+
type LegacyCommands = Record<string, LegacyCommand>;
9
+
let legacyCommands: LegacyCommands | undefined;
10
+
let queuedLegacyCommands: Record<string, LegacyCommand> | null = {};
11
+
12
+
const registeredCommands: RegisteredCommand[] = [];
13
+
14
+
export function _getLegacyCommands(commands: LegacyCommands) {
15
+
legacyCommands = commands;
16
+
if (queuedLegacyCommands != null) {
17
+
for (const [key, value] of Object.entries(queuedLegacyCommands)) {
18
+
legacyCommands[key] = value;
19
+
}
20
+
queuedLegacyCommands = null;
21
+
}
22
+
}
23
+
24
+
export const commands: Commands = {
25
+
registerCommand(command) {
26
+
const registered: RegisteredCommand = {
27
+
...command,
28
+
untranslatedName: command.id,
29
+
displayName: command.id,
30
+
applicationId: APPLICATION_ID,
31
+
untranslatedDescription: command.description,
32
+
displayDescription: command.description,
33
+
options: command.options.map((o) => ({
34
+
...o,
35
+
displayName: o.name,
36
+
displayDescription: o.description
37
+
}))
38
+
};
39
+
registeredCommands.push(registered);
40
+
},
41
+
42
+
registerLegacyCommand(id, command) {
43
+
if (!legacyCommands) {
44
+
queuedLegacyCommands![id] = command;
45
+
} else {
46
+
legacyCommands[id] = command;
47
+
}
48
+
},
49
+
50
+
_getCommands() {
51
+
return [...registeredCommands];
52
+
}
53
+
};
54
+
55
+
export default commands;
+118
packages/types/src/coreExtensions/commands.ts
+118
packages/types/src/coreExtensions/commands.ts
···
1
+
export const APPLICATION_ID = "-3";
2
+
3
+
export enum CommandType {
4
+
CHAT = 1,
5
+
MESSAGE = 3,
6
+
PRIMARY_ENTRY_POINT = 4,
7
+
USER = 2
8
+
}
9
+
10
+
export enum InputType {
11
+
BOT = 3,
12
+
BUILT_IN = 0,
13
+
BUILT_IN_INTEGRATION = 2,
14
+
BUILT_IN_TEXT = 1,
15
+
PLACEHOLDER = 4
16
+
}
17
+
18
+
export enum OptionType {
19
+
ATTACHMENT = 11,
20
+
BOOLEAN = 5,
21
+
CHANNEL = 7,
22
+
INTEGER = 4,
23
+
MENTIONABLE = 9,
24
+
NUMBER = 10,
25
+
ROLE = 8,
26
+
STRING = 3,
27
+
SUB_COMMAND = 1,
28
+
SUB_COMMAND_GROUP = 2,
29
+
USER = 6
30
+
}
31
+
32
+
export type RegisteredCommandOption = {
33
+
name: string;
34
+
displayName: string;
35
+
type: OptionType;
36
+
description: string;
37
+
displayDescription: string;
38
+
};
39
+
40
+
export type MoonlightCommandOption = {
41
+
name: string;
42
+
type: OptionType;
43
+
description: string;
44
+
};
45
+
46
+
// TODO: types
47
+
export type CommandPredicateState = {
48
+
channel: any;
49
+
guild: any;
50
+
};
51
+
52
+
export type RegisteredCommand = {
53
+
id: string;
54
+
untranslatedName: string;
55
+
displayName: string;
56
+
type: CommandType;
57
+
inputType: InputType;
58
+
applicationId: string; // set to -3!
59
+
untranslatedDescription: string;
60
+
displayDescription: string;
61
+
options: RegisteredCommandOption[];
62
+
predicate?: (state: CommandPredicateState) => boolean;
63
+
execute: (options: CommandOption[]) => void;
64
+
};
65
+
66
+
export type MoonlightCommand = {
67
+
id: string;
68
+
description: string;
69
+
type: CommandType;
70
+
inputType: InputType;
71
+
options: MoonlightCommandOption[];
72
+
predicate?: (state: CommandPredicateState) => boolean;
73
+
execute: (options: CommandOption[]) => void;
74
+
};
75
+
76
+
export type CommandOption = {
77
+
name: string;
78
+
} & ( // TODO: more of these
79
+
| {
80
+
type: Exclude<OptionType, OptionType.STRING>;
81
+
value: any;
82
+
}
83
+
| {
84
+
type: OptionType.STRING;
85
+
value: string;
86
+
}
87
+
);
88
+
89
+
export type Commands = {
90
+
/**
91
+
* Register a command in the internal slash command system
92
+
*/
93
+
registerCommand: (command: MoonlightCommand) => void;
94
+
95
+
/**
96
+
* Register a legacy command that works via regex
97
+
*/
98
+
registerLegacyCommand: (id: string, command: LegacyCommand) => void;
99
+
100
+
/**
101
+
* @private
102
+
*/
103
+
_getCommands: () => RegisteredCommand[];
104
+
};
105
+
106
+
export type LegacyContext = {
107
+
channel: any;
108
+
isEdit: boolean;
109
+
};
110
+
111
+
export type LegacyReturn = {
112
+
content: string;
113
+
};
114
+
115
+
export type LegacyCommand = {
116
+
match?: RegExp;
117
+
action: (content: string, context: LegacyContext) => LegacyReturn;
118
+
};