107.13.0
another absolutely massive diff * Disabled keyboard handler for focus management, the club got better at this * Input history redone entirely, the code contains comments * Completion now takes cursor position into account * Dropped autoremoval of service messages in favour of future manual deletion * Tons of typechecking cleanup and style improvements * The code now has zero errors on the strictest TS settings I know
This commit is contained in:
		
							
								
								
									
										467
									
								
								ambient.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										467
									
								
								ambient.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -2,61 +2,458 @@ interface ServerChatRoomMessage { | |||||||
| 	MBCHC_ID?: number | 	MBCHC_ID?: number | ||||||
| } | } | ||||||
|  |  | ||||||
| declare namespace MBCHC { | namespace BCE { | ||||||
| 	type WGT = Window & typeof globalThis | 	interface Matcher { | ||||||
| 	interface Root extends WGT { | 		Tester: RegExp | ||||||
| 		MBCHC?: any | 		Criteria?: { | ||||||
| 		bcModSdk?: import('bondage-club-mod-sdk').ModSDKGlobalAPI | 			TargetIsPlayer?: boolean | ||||||
| 		bce_ActivityTriggers?: any | 			SenderIsPlayer?: boolean | ||||||
| 		bce_EventExpressions?: any | 			DictionaryMatchers?: Array<OBJ<string>> | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	namespace Settings { |  | ||||||
| 		type V0 = PlayerOnlineSettings & {MBCHC: {timezones?: Record<number, number>}} // V0 is the whole onlinesettings |  | ||||||
| 		interface V1 { // V1 is specifically MBCHC inside extensionsettings |  | ||||||
| 			TZ: Record<number, number> |  | ||||||
| 	} | 	} | ||||||
| 		interface Methods { | 	interface Trigger { | ||||||
| 			migrate_0_1(v0: V0): true | 		Event: string | ||||||
| 			save(cb?: (v1: V1) => unknown): true | 		Type: 'Emote' | 'Activity' | 'Action' | ||||||
| 			replace(new_v1: V1): true | 		Matchers: Matcher[] | ||||||
| 			'purge!'(): true | 	} | ||||||
| 			get v1(): V1 | 	interface Patcher { | ||||||
|  | 		timer: number | undefined | ||||||
|  | 		patches: Array<[RegExp, string]> | ||||||
|  | 		cfs: {[k in 'anim' | 'pose']: () => Iterable<string>} | ||||||
|  | 		gen: (comp_func: () => Iterable<string>) => (this: Optional<ICommand, 'Tag'>) => undefined | ||||||
|  | 		copy(t: Trigger): Trigger | ||||||
|  | 		patch(): true | undefined | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type SUBCOMMANDS = { | ||||||
|  | 	[k: string]: { | ||||||
|  | 		desc: string | ||||||
|  | 		args?: {[n: string]: OBJ} | ||||||
|  | 		cb: (mbchc: Window['MBCHC'], args: string[], __: unknown, ___?: unknown) => void | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | interface Window { | ||||||
|  | 	MBCHC: { | ||||||
|  | 		loader: () => void | ||||||
|  | 		DO_DATA: { | ||||||
|  | 			zones: OBJ<string> | ||||||
|  | 			verbs: { | ||||||
|  | 				[verb: string]: { | ||||||
|  | 					[zone: string]: { | ||||||
|  | 						self: string[] | ||||||
|  | 						others: string[] | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		MAP_ACTIONS: { | ||||||
|  | 			[verb: string]: { | ||||||
|  | 				[zones: string]: { | ||||||
|  | 					all?: string | ||||||
|  | 					self?: string | ||||||
|  | 					others?: string | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		MAP_ZONES: {[zone: string]: string[]} | ||||||
|  | 		LOADED: boolean | ||||||
|  | 		NEXT_MESSAGE: number | ||||||
|  | 		LOG_MESSAGES: boolean | ||||||
|  | 		LAST_HACKED: number | undefined | ||||||
|  | 		version: string | ||||||
|  | 		VERSION: string | ||||||
|  | 		Settings: Settings.Methods | ||||||
|  | 		TZ: TZ_Cache | ||||||
|  | 		SUBCOMMANDS_MBCHC: SUBCOMMANDS | ||||||
|  | 		H: InputHistory | ||||||
|  | 		U: Utils | ||||||
|  | 		AUTOHACK_ENABLED: boolean | ||||||
|  | 		RE_PREF_ACTIVITY_ME: RegExp | ||||||
|  | 		RE_PREF_ACTIVITY: RegExp | ||||||
|  | 		RE_ACT_CIDS: RegExp | ||||||
|  | 		RE_LAST_WORD: RegExp | ||||||
|  | 		RE_LAST_LETTER: RegExp | ||||||
|  | 		RE_ACTIVITY: RegExp | ||||||
|  | 		UTC_OFFSET: number | ||||||
|  | 		calculate_maps(): void | ||||||
|  | 		normalise_message(text: string, options?: OBJ<boolean>): string | ||||||
|  | 		donate_data(target: string): void | ||||||
|  | 		run_activity(char: Character, ag: AssetGroupItemName, action: ActivityName): void | ||||||
|  | 		send_activity(message: string): void | ||||||
|  | 		set_timezone(args: string[]): number | undefined | ||||||
|  | 		command_mbchc(this: Optional<ICommand, 'Tag'>, args: string, msg: string, parsed: string[]): undefined | ||||||
|  | 		command_activity(this: Optional<ICommand, 'Tag'>, args: string, msg: string, parsed: string[]): undefined | ||||||
|  | 		command_do(this: Optional<ICommand, 'Tag'>, args: string, msg: string, parsed: string[]): undefined | ||||||
|  | 	} | ||||||
|  | 	bcModSdk?: import('./node_modules/bondage-club-mod-sdk/dist/bcmodsdk.d.ts').ModSDKGlobalAPI; | ||||||
|  | 	FBC_VERSION?: string | ||||||
|  | 	bce_ActivityTriggers?: BCE.Trigger[] | ||||||
|  | 	bce_EventExpressions?: OBJ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type OBJ<T = unknown> = Record<string, T>; | ||||||
|  | type FUN = (...args: never[]) => unknown; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Stands for "Value or Function". Not remotely ideal, but the best I can come up with. | ||||||
|  |  */ | ||||||
|  | type VOF<F extends FUN> = undefined | boolean | number | bigint | string | symbol | OBJ | Set<unknown> | Map<unknown, unknown> | FP.Interval | F; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * I can write Haskell in every language. | ||||||
|  |  * I was unbelievably tempted to go with emojis here, but I'm not a monster of this caliber. | ||||||
|  |  * @see https://8fw.me/hell/a8aaaefcb6e30ec4b2e398c70d49b72a6b53232f.png | ||||||
|  |  */ | ||||||
|  | namespace FP { | ||||||
| 	interface Interval { | 	interface Interval { | ||||||
| 		proxy: object // eslint-disable-line @typescript-eslint/ban-types | 		proxy: object // eslint-disable-line @typescript-eslint/ban-types | ||||||
| 		min: number | 		min: number | ||||||
| 		max: number | 		max: number | ||||||
| 		mini: boolean | 		mini: boolean | ||||||
| 		maxi: boolean | 		maxi: boolean | ||||||
| 		upd(min: number, max: number, min_inclusive?: boolean, max_inclusive?: boolean): undefined |  | ||||||
| 		has(_: unknown, x: string): boolean | 		has(_: unknown, x: string): boolean | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	interface Utils { | 	interface Pipeline<T> { | ||||||
| 		interval: Interval | 		proxy: PipelineProxy<T> | ||||||
| 		cid(character: Character): number | undefined | 		me<N>(iterable: Iterable<N>): PipelineProxy<N> | ||||||
| 		dn(character: Character): string | 		[Symbol.iterator](): Iterator<T> | ||||||
| 		current(): string | 		rdc<R>(initial: R, func: (accumulator: R, value: T) => R): R | ||||||
| 		with<V, R>(value: V, cb: (value: V) => R): R // A silly helper to kinda curry values | 		any(func: (v: T) => boolean): boolean | ||||||
| 		true<F extends (...args: unknown[]) => unknown>(cb: F): true // Useful for type-safe chaining | 		all(func: (v: T) => boolean): boolean | ||||||
| 		mutate<T>(value: T, cb: (v: T) => unknown): T // A silly helper for chaining | 		map<R>(func: (value: T) => R): PipelineProxy<R> | ||||||
| 		rm<T>(object: T, property: keyof T): T | 		sel(func: (value: T) => boolean): PipelineProxy<T> | ||||||
| 		mrg<T extends Record<string, unknown>>(target: T, ...source: T[]): T // Shorter, also less confusing with types | 	} | ||||||
| 		style<T>(query: string, cb: (s: CSSStyleDeclaration) => T): T | undefined | 	interface PipelineProxy<T> extends Pipeline<T> { | ||||||
| 		range(min: number, max: number, min_inclusive?: boolean, max_inclusive?: boolean): Proxy | 		[i: number]: T | undefined | ||||||
| 		assert<T>(error: string, value: T, cb?: (value: T) => boolean): T | never |  | ||||||
| 		each<T extends Record<string, unknown>>(object: T, cb: (key: string, value: unknown) => unknown): true |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Creates an iteration pipeline. | ||||||
|  | 	 */ | ||||||
|  | 	type P = <T>(iterable: Iterable<T>) => PipelineProxy<T>; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * A silly helper to kinda c[au]rry values. Basically equivalent to: | ||||||
|  | 	 * `const a = value; return func(a)` | ||||||
|  | 	 * Stands for "Carry, Use, Return". | ||||||
|  | 	 */ | ||||||
|  | 	type cur = <T, R>(value: T, func: (value: T) => R) => R; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Convert `null` to `undefined`. | ||||||
|  | 	 */ // eslint-disable-next-line @typescript-eslint/ban-types | ||||||
|  | 	type n2u = <T>(value: T | undefined | null) => T | undefined; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Enumerate keys of an object. Type-safe, also shorter. | ||||||
|  | 	 */ | ||||||
|  | 	type enu = <O extends OBJ>(object: O) => Array<keyof O>; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Like `carry()` but does nothing if `value` is `null` or `undefined`. | ||||||
|  | 	 * Something like an `Option<>` when you want to just pass `undefined` along, but | ||||||
|  | 	 * run a function on actual values. Also coverts `null` to `undefined`. | ||||||
|  | 	 */ // eslint-disable-next-line @typescript-eslint/ban-types | ||||||
|  | 	type val = <T, R>(value: T | null | undefined, func: (value: T) => R) => R | undefined; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * `+` as a function | ||||||
|  | 	 */ | ||||||
|  | 	type add = (x: number, y: number) => number; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * `-` as a function | ||||||
|  | 	 */ | ||||||
|  | 	type sub = (x: number, y: number) => number; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * `>` as a function | ||||||
|  | 	 */ | ||||||
|  | 	type cgt = (x: number, y: number) => boolean; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * `>=` as a function | ||||||
|  | 	 */ | ||||||
|  | 	type cge = (x: number, y: number) => boolean; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * `<` as a function | ||||||
|  | 	 */ | ||||||
|  | 	type clt = (x: number, y: number) => boolean; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * `<=` as a function | ||||||
|  | 	 */ | ||||||
|  | 	type cle = (x: number, y: number) => boolean; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Converts a string into a base 10 integer. Not only is it shorter than `Number.parseInt`, | ||||||
|  | 	 * it also converts `NaN` to `undefined`, because I find it much easier to only have | ||||||
|  | 	 * one nil value for everything. | ||||||
|  | 	 */ | ||||||
|  | 	type int = (text: string) => number | undefined; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * This is a type assertion helper for Typescript. Probably not very useful in general. | ||||||
|  | 	 */ | ||||||
|  | 	type fun = <F extends FUN>(func: unknown) => func is F; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Takes anything, ignores non-functions, calls functions with supplied parameters. | ||||||
|  | 	 */ | ||||||
|  | 	type run = <F extends FUN>(functions_or_values: Array<VOF<F>>, ...args: Parameters<F>) => true; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Takes anything, ignores non-functions, calls functions with `undefined` as a single parameter. | ||||||
|  | 	 */ | ||||||
|  | 	type yes = (...args: Array<VOF<(_: undefined) => unknown>>) => true; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Takes anything, ignores non-functions, calls functions with a single provided parameter. | ||||||
|  | 	 * Always returns the parameter itself. | ||||||
|  | 	 */ | ||||||
|  | 	type mut = <T>(value: T, ...args: Array<VOF<(value: T) => unknown>>) => T; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * `delete` as a function | ||||||
|  | 	 */ // eslint-disable-next-line @typescript-eslint/ban-types | ||||||
|  | 	type del = <T extends object>(object: T, property: keyof T) => T; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Short for `assert`. Throws, if value is `undefined`, or if `condition(value)` is false. | ||||||
|  | 	 */ | ||||||
|  | 	type ass = <T>(error: string, value: T | undefined, condition?: (value: T) => boolean) => T | never; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Casts value as a given prototype or returns undefined. | ||||||
|  | 	 */ | ||||||
|  | 	type asa = <T>(prototype: new () => T, value: unknown) => T | undefined; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * `catch` as a function. Short for `rescue`. | ||||||
|  | 	 */ | ||||||
|  | 	type rsc = (operation: (_: undefined) => unknown, exception_handler: (error: unknown) => unknown) => true; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Returns a proxy for `in` operator. | ||||||
|  | 	 * Use it like `2 in rng(0, 4)`. | ||||||
|  | 	 */ // eslint-disable-next-line @typescript-eslint/ban-types | ||||||
|  | 	type rng = (min: number, max: number, min_inclusive?: boolean, max_inclusive?: boolean) => object; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Something slightly better than `Boolean()`. | ||||||
|  | 	 * True is `null`, `undefined`, `false`, empty strings, sets, maps, arrays and objects. | ||||||
|  | 	 * Short for "empty". | ||||||
|  | 	 */ | ||||||
|  | 	type m_t = (value: unknown) => boolean; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * `while()` as a function. Executes the second callback while the first one is true. | ||||||
|  | 	 * Always bound by max number of iterations. | ||||||
|  | 	 * Returns the last value from the action or undefined if the action never ran. | ||||||
|  | 	 */ | ||||||
|  | 	type loo = <T>(max: number, condition: (_: undefined) => boolean, action: (_: undefined) => T) => T | undefined; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace SDK { | ||||||
|  | 	type GDPT<F> = import('./node_modules/bondage-club-mod-sdk/dist/bcmodsdk.d.ts').GetDotedPathType<typeof globalThis, F>; | ||||||
|  | 	type Void<F extends FUN> = (...args: Parameters<F>) => unknown; | ||||||
|  | 	type Hook = <F extends string>(name: F, hook: Void<GDPT<F>>) => () => unknown; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace Cons { | ||||||
|  | 	type MS = {w: 'warn', i: 'info', d: 'debug', l: 'log'}; | ||||||
|  | 	type E = (error: unknown) => true; | ||||||
|  | 	type F = (message: string) => true; | ||||||
|  | 	interface Wrap { | ||||||
|  | 		readonly ms: MS | ||||||
|  | 		e: E | ||||||
|  | 		w: F | ||||||
|  | 		i: F | ||||||
|  | 		d: F | ||||||
|  | 		l: F | ||||||
|  | 		gen: (m: keyof MS) => F | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace Settings { | ||||||
|  | 	/** | ||||||
|  | 	 * The whole `Player.OnlineSettings`. | ||||||
|  | 	 */ | ||||||
|  | 	type V0 = PlayerOnlineSettings & {MBCHC?: {timezones?: Record<number, number>}}; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Specifically `MBCHC` inside `Player.ExtensionSettings`. | ||||||
|  | 	 */ | ||||||
|  | 	interface V1 { | ||||||
|  | 		TZ: Record<number, number> | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	interface Methods { | ||||||
|  | 		migrate_0_1(v0: V0): true | ||||||
|  | 		save(func?: (v1: V1) => unknown): true | ||||||
|  | 		replace(new_v1: V1): true | ||||||
|  | 		'purge!'(): true | ||||||
|  | 		get v0(): V0 | undefined | ||||||
|  | 		get v1(): V1 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * We need a place to cache the timezones instead of the `Character` object itself. | ||||||
|  |  */ | ||||||
| interface TZ_Cache { | interface TZ_Cache { | ||||||
| 	map: Map<number, number> | 	map: Map<number, number> | ||||||
| 	RE: RegExp | 	RE: RegExp | ||||||
| 		for(character: Character): number | undefined |  | ||||||
| 		memo(member_number: number, description?: string | undefined): number | undefined |  | ||||||
| 	parse(description: string | undefined): number | undefined | 	parse(description: string | undefined): number | undefined | ||||||
|  | 	memo(member_number: number, description?: string | undefined): number | undefined | ||||||
|  | 	lookup(character: Character): number | undefined | ||||||
| } | } | ||||||
|  |  | ||||||
|  | interface Utils { | ||||||
|  | 	remove_loader_hook: (() => unknown) | undefined | ||||||
|  | 	RE: {SPACES: RegExp, REL: {L: RegExp, R: RegExp}, '@': [RegExp, RegExp]} | ||||||
|  | 	RGB: {Polly: string, Mute: string} | ||||||
|  | 	ACT: string, | ||||||
|  | 	get crc(): Character[] | ||||||
|  | 	get ic(): HTMLTextAreaElement | undefined | ||||||
|  | 	cid(character: Character): number | undefined | ||||||
|  | 	dn(character: Character): string | ||||||
|  | 	current(): string | ||||||
|  | 	style<T>(query: string, func: (s: CSSStyleDeclaration) => T): T | undefined | ||||||
|  | 	inform(html: string): true | ||||||
|  | 	report(error: unknown): true | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Splits a string into words by continuous whitespace sequences. Some examples: | ||||||
|  | 	 * ``` | ||||||
|  | 	 * ""      => [""] | ||||||
|  | 	 * " "     => ["", ""] | ||||||
|  | 	 * "f   g" => ["f", "g"] | ||||||
|  | 	 * " f g " => ["", "f", "g", ""] | ||||||
|  | 	 * ``` | ||||||
|  | 	 */ | ||||||
|  | 	split(text: string): string[] | ||||||
|  | 	abs2char(index: number): Character | ||||||
|  | 	rel2char(target: string): Character | ||||||
|  | 	cid2char(cid: number): Character | ||||||
|  | 	target2char(target: string): Character | ||||||
|  | 	mkdiv(html?: string): HTMLDivElement | ||||||
|  | 	bell(): true | ||||||
|  | 	targets(me2?: boolean, check_perms?: boolean): Set<string> | ||||||
|  | 	complete_mbchc(this: Optional<ICommand, 'Tag'>): undefined | ||||||
|  | 	complete_do_target(actions: {self: unknown, others: unknown}): Set<string> | ||||||
|  | 	complete_do(this: Optional<ICommand, 'Tag'>): undefined | ||||||
|  | 	replace_me(_match: string, _offset: number, whole: string): string | ||||||
|  | 	pad_chat(chat: HTMLDivElement): undefined | ||||||
| } | } | ||||||
|  |  | ||||||
|  | interface Complete { | ||||||
|  | 	S_OPTS: {behavior: 'instant'} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The suggestions panel. | ||||||
|  | 	 * Its structure is (outer div) -> (container div) -> (multiple suggestion divs) | ||||||
|  | 	 */ | ||||||
|  | 	e: HTMLDivElement | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The container div. | ||||||
|  | 	 */ | ||||||
|  | 	get div(): Element | undefined | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Longest common prefix, or an empty string for an empty set. | ||||||
|  | 	 */ | ||||||
|  | 	lcp(candidates: Set<string>): string | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * !WARNING! Mutate the given set in accordance with the input. | ||||||
|  | 	 * Returns the input with completion done and removes all failed candidates. | ||||||
|  | 	 * If the set is empty, the completion failed and the result is the unmodified input. | ||||||
|  | 	 * If the set has more than one value, these are all new candidates, and the input was completed with the lcp. | ||||||
|  | 	 * Otherwise, the only successful candidate will remain in the set and the input was completed to it. | ||||||
|  | 	 */ | ||||||
|  | 	complete_word(input: string, candidates: Set<string>, ignore_case?: boolean | undefined): string | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Show the suggestions to the user. | ||||||
|  | 	 */ | ||||||
|  | 	hint(candidates: Set<string>): true | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Returns false if the suggestion panel is visible. | ||||||
|  | 	 */ | ||||||
|  | 	get hidden(): boolean | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Makes the suggestions panel disappear. | ||||||
|  | 	 */ | ||||||
|  | 	hide(): true | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The whole deal. Will read and modify the chat input window. | ||||||
|  | 	 * Takes a callback that will receive current input split into words | ||||||
|  | 	 * and should return all possible candidates for the word being completed. | ||||||
|  | 	 * Note: if the input ends with whitespace, the last word will be empty. | ||||||
|  | 	 */ | ||||||
|  | 	complete(func: (words: string[]) => Set<string>, ignore_case?: boolean | undefined): true | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Case-insensitive complete. | ||||||
|  | 	 */ | ||||||
|  | 	icomplete(func: (words: string[]) => Set<string>): true | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * So, this is what happens. We have two modes: input mode and history mode. In the history mode the element is read-only. | ||||||
|  |  * In the input mode: | ||||||
|  |  *   If the input is empty, we just scroll the history as usual | ||||||
|  |  *   Otherwise, we build a history set using the input as prefix | ||||||
|  |  *     The history set filters through the history, keeping only unique lines that start with the prefix along with indices | ||||||
|  |  *     But we exclude lines that match the input exactly | ||||||
|  |  *     It keeps the original input in a separate place too | ||||||
|  |  *   If the set is empty, we bell out | ||||||
|  |  *   Otherwise, we enter the history mode and invoke the first search | ||||||
|  |  * In the history mode: | ||||||
|  |  *   We search up or down, using the index as a starting point for the next match, treating the set as a ring | ||||||
|  |  *   If the found line is the same as the current line, we bell out | ||||||
|  |  *   Upon finding a next match, we replace the input with its text and set the index appropriately | ||||||
|  |  * We exit the history mode using the inputchat keydown handler, on Tab, Escape or Enter | ||||||
|  |  * Escape restores the saved input, discarding the history line | ||||||
|  |  * Tab keeps the current text and unlocks the element, allowing it to be edited | ||||||
|  |  * Enter keeps the current text and sends it as the message as usual | ||||||
|  |  */ | ||||||
|  | interface InputHistory { | ||||||
|  | 	/** | ||||||
|  | 	 * Whether the chat log is scrolled to the end when we enter history mode. | ||||||
|  | 	 */ | ||||||
|  | 	bottom: boolean | undefined | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The initial user input when we enter the history mode. | ||||||
|  | 	 */ | ||||||
|  | 	input: string | undefined | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The indices of the lines that match the prefix. | ||||||
|  | 	 */ | ||||||
|  | 	ids: Set<number> | undefined | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Enter the history mode. | ||||||
|  | 	 */ | ||||||
|  | 	enter(textarea: HTMLTextAreaElement, input: string, bottom: boolean, ids: Set<number>): true | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Exit the history mode and optionally restore original input. | ||||||
|  | 	 */ | ||||||
|  | 	exit(textarea: HTMLTextAreaElement, restore_input: boolean): true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // FIXME spread around readonlys where appropriate | ||||||
|   | |||||||
| @@ -7,13 +7,19 @@ | |||||||
| 		"server.js" | 		"server.js" | ||||||
| 	], | 	], | ||||||
| 	"compilerOptions": { | 	"compilerOptions": { | ||||||
| 		"lib": [ | 		"target": "es2023", | ||||||
| 			"es2022", | 		"allowJs": true, | ||||||
| 			"DOM" |  | ||||||
| 		], |  | ||||||
| 		"checkJs": true, | 		"checkJs": true, | ||||||
| 		"strict": false, | 		"allowUnreachableCode": false, | ||||||
| 		"strictNullChecks": true, | 		"allowUnusedLabels": false, | ||||||
| 		"noImplicitOverride": true | 		"exactOptionalPropertyTypes": true, | ||||||
|  | 		"noImplicitOverride": true, | ||||||
|  | 		"noImplicitReturns": true, | ||||||
|  | 		"noPropertyAccessFromIndexSignature": true, | ||||||
|  | 		"noUncheckedIndexedAccess": true, | ||||||
|  | 		"noUnusedLocals": true, | ||||||
|  | 		"noUnusedParameters": true, | ||||||
|  | 		"strict": true, | ||||||
|  | 		"noEmit": true | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,16 +1,17 @@ | |||||||
| { | { | ||||||
| 	"name": "mbchc", | 	"name": "mbchc", | ||||||
| 	"version": "105.13.0", | 	"version": "107.13.0", | ||||||
| 	"lockfileVersion": 3, | 	"lockfileVersion": 3, | ||||||
| 	"requires": true, | 	"requires": true, | ||||||
| 	"packages": { | 	"packages": { | ||||||
| 		"": { | 		"": { | ||||||
| 			"name": "mbchc", | 			"name": "mbchc", | ||||||
| 			"version": "105.13.0", | 			"version": "107.13.0", | ||||||
| 			"license": "SEE LICENSE IN LICENSE.", | 			"license": "SEE LICENSE IN LICENSE", | ||||||
| 			"devDependencies": { | 			"devDependencies": { | ||||||
| 				"bc-stubs": "^105.0.0", | 				"bc-stubs": "^106.0.0", | ||||||
| 				"bondage-club-mod-sdk": "^1.2.0", | 				"bondage-club-mod-sdk": "^1.2.0", | ||||||
|  | 				"typescript": "^5.5.2", | ||||||
| 				"xo": "^0.56.0" | 				"xo": "^0.56.0" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| @@ -1073,9 +1074,9 @@ | |||||||
| 			"dev": true | 			"dev": true | ||||||
| 		}, | 		}, | ||||||
| 		"node_modules/bc-stubs": { | 		"node_modules/bc-stubs": { | ||||||
| 			"version": "105.0.3", | 			"version": "106.0.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/bc-stubs/-/bc-stubs-105.0.3.tgz", | 			"resolved": "https://registry.npmjs.org/bc-stubs/-/bc-stubs-106.0.0.tgz", | ||||||
| 			"integrity": "sha512-haKRphxOdPQT/9W6s5L0x5DFDttOrk0xBddFfoLnMQlz7AiXnU3TGdlJ+biWpmQtyHc7WrJARbYd4Wf7+a4j8g==", | 			"integrity": "sha512-qUbBXFdTZNq+JUGJLpWCiqfMwSDu/i36fcoTtwfbHMV32lSkXsaLgqK7itOpDZIUTWcZhneN4DO2m6Jd7juk7w==", | ||||||
| 			"dev": true, | 			"dev": true, | ||||||
| 			"dependencies": { | 			"dependencies": { | ||||||
| 				"socket.io-client": "4.6.1" | 				"socket.io-client": "4.6.1" | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,35 +1,38 @@ | |||||||
| { | { | ||||||
| 	"name": "mbchc", | 	"name": "mbchc", | ||||||
| 	"version": "105.13.0", | 	"version": "107.13.0", | ||||||
| 	"description": "Mute's Bondage Club Hacks Collection", | 	"description": "Mute's Bondage Club Hacks Collection", | ||||||
|  | 	"author": "Mute", | ||||||
| 	"type": "module", | 	"type": "module", | ||||||
| 	"devDependencies": { | 	"devDependencies": { | ||||||
| 		"xo": "^0.56.0", | 		"bc-stubs": "^106.0.0", | ||||||
| 		"bc-stubs": "^105.0.0", | 		"bondage-club-mod-sdk": "^1.2.0", | ||||||
| 		"bondage-club-mod-sdk": "^1.2.0" | 		"typescript": "^5.5.2", | ||||||
|  | 		"xo": "^0.56.0" | ||||||
|   }, |   }, | ||||||
| 	"license": "SEE LICENSE IN LICENSE.", | 	"license": "SEE LICENSE IN LICENSE", | ||||||
| 	"eslintConfig": { | 	"eslintConfig": { | ||||||
| 		"root": true, | 		"root": true, | ||||||
| 		"extends": [ | 		"extends": ["xo", "xo-typescript", "plugin:unicorn/recommended"], | ||||||
| 			"xo", |  | ||||||
| 			"xo-typescript" |  | ||||||
| 		], |  | ||||||
| 		"parser": "@typescript-eslint/parser", | 		"parser": "@typescript-eslint/parser", | ||||||
| 		"parserOptions": { "project": ["./jsconfig.json"] }, | 		"parserOptions": { "project": ["./jsconfig.json"] }, | ||||||
| 		"plugins": ["@typescript-eslint"], | 		"plugins": ["@typescript-eslint", "unicorn"], | ||||||
| 		"globals": {"GM": true, "GM_info": true}, |  | ||||||
| 		"rules": { | 		"rules": { | ||||||
| 			"@typescript-eslint/brace-style": "off", | 			"@typescript-eslint/brace-style": "off", | ||||||
| 			"@typescript-eslint/comma-dangle": ["error", "only-multiline"], | 			"@typescript-eslint/comma-dangle": ["error", "only-multiline"], | ||||||
|  | 			"@typescript-eslint/consistent-indexed-object-style": "off", | ||||||
| 			"@typescript-eslint/consistent-type-definitions": "off", | 			"@typescript-eslint/consistent-type-definitions": "off", | ||||||
| 			"@typescript-eslint/consistent-type-imports": "off", | 			"@typescript-eslint/consistent-type-imports": "off", | ||||||
|  | 			"@typescript-eslint/dot-notation": "off", | ||||||
|  | 			"@typescript-eslint/lines-between-class-members": "off", | ||||||
| 			"@typescript-eslint/member-delimiter-style": "off", | 			"@typescript-eslint/member-delimiter-style": "off", | ||||||
| 			"@typescript-eslint/naming-convention": "off", | 			"@typescript-eslint/naming-convention": "off", | ||||||
| 			"@typescript-eslint/no-confusing-void-expression": ["error", { | 			"@typescript-eslint/no-confusing-void-expression": ["error", { | ||||||
| 				"ignoreVoidOperator": true | 				"ignoreVoidOperator": true | ||||||
| 			}], | 			}], | ||||||
|  | 			"@typescript-eslint/no-explicit-any": "error", | ||||||
| 			"@typescript-eslint/no-meaningless-void-operator": "off", | 			"@typescript-eslint/no-meaningless-void-operator": "off", | ||||||
|  | 			"@typescript-eslint/no-unused-expressions": "off", | ||||||
| 			"@typescript-eslint/object-curly-spacing": "off", | 			"@typescript-eslint/object-curly-spacing": "off", | ||||||
| 			"@typescript-eslint/padding-line-between-statements": "off", | 			"@typescript-eslint/padding-line-between-statements": "off", | ||||||
| 			"@typescript-eslint/semi": "off", | 			"@typescript-eslint/semi": "off", | ||||||
| @@ -44,9 +47,12 @@ | |||||||
| 			"camelcase": "off", | 			"camelcase": "off", | ||||||
| 			"capitalized-comments": "off", | 			"capitalized-comments": "off", | ||||||
| 			"curly": "off", | 			"curly": "off", | ||||||
|  | 			"generator-star-spacing": "off", | ||||||
|  | 			"max-nested-callbacks": "off", | ||||||
| 			"max-params": "off", | 			"max-params": "off", | ||||||
| 			"max-statements-per-line": "off", | 			"max-statements-per-line": "off", | ||||||
| 			"new-cap": "off", | 			"new-cap": "off", | ||||||
|  | 			"no-mixed-spaces-and-tabs": ["error", "smart-tabs"], | ||||||
| 			"no-return-assign": "off", | 			"no-return-assign": "off", | ||||||
| 			"no-unused-expressions": "off", | 			"no-unused-expressions": "off", | ||||||
| 			"no-unused-vars": ["error", { | 			"no-unused-vars": ["error", { | ||||||
| @@ -61,16 +67,23 @@ | |||||||
| 			"semi": "off", | 			"semi": "off", | ||||||
| 			"space-before-function-paren": "off", | 			"space-before-function-paren": "off", | ||||||
| 			"spaced-comment": "off", | 			"spaced-comment": "off", | ||||||
|  | 			"unicorn/catch-error-name": ["error", {"name": "x"}], | ||||||
|  | 			"unicorn/consistent-function-scoping": "off", | ||||||
|  | 			"unicorn/no-array-callback-reference": "off", | ||||||
| 			"unicorn/no-array-for-each": "off", | 			"unicorn/no-array-for-each": "off", | ||||||
| 			"unicorn/no-array-reduce": "off", | 			"unicorn/no-array-reduce": "off", | ||||||
| 			"unicorn/prefer-module": "off", | 			"unicorn/no-nested-ternary": "off", | ||||||
| 			"unicorn/prefer-top-level-await": "off", | 			"unicorn/prevent-abbreviations": ["error", { | ||||||
|  | 				"allowList": {"cur": true, "args": true, "func": true, "val": true, "mod": true, "msg": true, "i": true, "e": true} | ||||||
|  | 			}], | ||||||
|  | 			"unicorn/switch-case-braces": ["error", "avoid"], | ||||||
| 		"fake/fuck-commas": "off" | 		"fake/fuck-commas": "off" | ||||||
| 		}, | 		}, | ||||||
| 		"overrides": [ | 		"overrides": [ | ||||||
| 			{ | 			{ | ||||||
| 				"files": ["*.d.ts"], | 				"files": ["*.d.ts"], | ||||||
| 				"rules": { | 				"rules": { | ||||||
|  | 					"@typescript-eslint/semi": "error", | ||||||
| 					"no-unused-vars": "off" | 					"no-unused-vars": "off" | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								server.js
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								server.js
									
									
									
									
									
								
							| @@ -1,7 +1,8 @@ | |||||||
| import {readFileSync} from 'node:fs' | import {readFileSync} from 'node:fs' | ||||||
| import {createServer} from 'node:http' | import {createServer} from 'node:http' | ||||||
|  | import {argv} from 'node:process' | ||||||
|  |  | ||||||
| const config = {host: '127.0.0.1', port: 9696} | const config = {host: '127.0.0.1', port: 9696, filename: argv[2] ?? 'mbchc.mjs'} | ||||||
|  |  | ||||||
| const h_cors = { | const h_cors = { | ||||||
| 	'Access-Control-Max-Age': '86400', | 	'Access-Control-Max-Age': '86400', | ||||||
| @@ -11,23 +12,19 @@ const h_cors = { | |||||||
| 	'Access-Control-Allow-Headers': '*', | 	'Access-Control-Allow-Headers': '*', | ||||||
| 	// 'Access-Control-Allow-Credentials': 'false', // omit this header to disallow | 	// 'Access-Control-Allow-Credentials': 'false', // omit this header to disallow | ||||||
| } | } | ||||||
| const h_all = Object.assign({ | const h_all = { | ||||||
| 	'Content-Type': 'text/javascript', | 	'Content-Type': 'text/javascript', | ||||||
| 	'Cache-Control': 'no-cache', | 	'Cache-Control': 'no-cache', | ||||||
| }, h_cors) | 	...h_cors | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** @type {Record<string,((request: import('node:http').ServerResponse) => void)>} */ const resp = { | ||||||
|  * @typedef {import('node:http').ServerResponse} ServerResponse | 	GET(rx) { rx.writeHead(200, h_all); rx.write(readFileSync(config.filename)) }, | ||||||
|  * @type {Record<string,function(ServerResponse):void>} |  | ||||||
|  */ |  | ||||||
| const resp = { |  | ||||||
| 	GET(rx) { rx.writeHead(200, h_all); rx.write(readFileSync('./mbchc.mjs')) }, |  | ||||||
| 	OPTIONS(rx) { rx.writeHead(204, h_cors) }, | 	OPTIONS(rx) { rx.writeHead(204, h_cors) }, | ||||||
| } | } | ||||||
|  |  | ||||||
| const server = createServer((rq, rx) => { | const server = createServer((rq, rx) => { | ||||||
| 	resp[rq.method] && (new URL(`http://${config.host}:${config.port}${rq.url}`)).pathname === '/' ? resp[rq.method](rx) : rx.writeHead(400) | 	rq.method !== undefined && resp[rq.method] !== undefined && (new URL(`http://${config.host}:${config.port}${rq.url}`)).pathname === '/' ? resp[rq.method](rx) : rx.writeHead(400) | ||||||
| 	rx.end() | 	rx.end(() => void console.log('%s %d %s %s', (new Date()).toISOString(), rx.statusCode, rq.method, rq.url)) | ||||||
| 	console.log('%s %d %s %s', (new Date()).toISOString(), rx.statusCode, rq.method, rq.url) |  | ||||||
| }) | }) | ||||||
| server.listen(config.port, config.host, () => console.log(`Server started at http://${config.host}:${config.port}`)) | server.listen(config.port, config.host, () => void console.log(`Server started at http://${config.host}:${config.port} for ${config.filename}`)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user