diff --git a/types/tmi.js/index.d.ts b/types/tmi.js/index.d.ts new file mode 100644 index 0000000000..86076ce202 --- /dev/null +++ b/types/tmi.js/index.d.ts @@ -0,0 +1,299 @@ +// Type definitions for tmi.js 1.3 +// Project: https://github.com/tmijs/tmi.js +// Definitions by: William Papsco +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 3.3 + +// Twitch IRC docs: https://dev.twitch.tv/docs/irc/ +// Last updated: 2019/2/27 + +import { StrictEventEmitter } from "./strict-event-emitter-types"; + +export interface Actions { + action(channel: string, message: string): Promise<[string]>; + ban(channel: string, username: string, reason?: string): Promise<[string, string, string]>; + clear(channel: string): Promise<[string]>; + color(color: string): Promise<[string]>; + commercial(channel: string, seconds: number): Promise<[string, number]>; + connect(): Promise<[string, number]>; + disconnect(): Promise<[string, number]>; + emoteonly(channel: string): Promise<[string]>; + emoteonlyoff(channel: string): Promise<[string]>; + followersonly(channel: string, length?: number): Promise<[string, number]>; + followersonlyoff(channel: string): Promise<[string]>; + host(channel: string, target: string): Promise<[string, string]>; + join(channel: string): Promise<[string]>; + mod(channel: string, username: string): Promise<[string, string]>; + mods(channel: string): Promise; + part(channel: string): Promise<[string]>; + ping(): Promise<[number]>; + r9kbeta(channel: string): Promise<[string]>; + r9kbetaoff(channel: string): Promise<[string]>; + raw(message: string): Promise<[string]>; + say(channel: string, message: string): Promise<[string]>; + slow(channel: string, length?: number): Promise<[string]>; + slowoff(channel: string): Promise<[string]>; + subscribers(channel: string): Promise<[string]>; + subscribersoff(channel: string): Promise<[string]>; + timeout(channel: string, username: string, length?: number, reason?: string): Promise<[string, string, number, string]>; + unban(channel: string, username: string): Promise<[string, string]>; + unhost(channel: string): Promise<[string]>; + unmod(channel: string, username: string): Promise<[string, string]>; + whisper(username: string, message: string): Promise<[string, string]>; +} + +export interface Events { + action(channel: string, userstate: ChatUserstate, message: string, self: boolean): void; + ban(channel: string, username: string, reason: string): void; + chat(channel: string, userstate: ChatUserstate, message: string, self: boolean): void; + cheer(channel: string, userstate: ChatUserstate, message: string): void; + clearchat(channel: string): void; + connected(address: string, port: number): void; + connecting(address: string, port: number): void; + disconnected(reason: string): void; + emoteonly(channel: string, enabled: boolean): void; + emotesets(sets: string, obj: EmoteObj): void; + followersonly(channel: string, enabled: boolean, length: number): void; + hosted(channel: string, username: string, viewers: number, autohost: boolean): void; + hosting(channel: string, target: string, viewers: number): void; + join(channel: string, username: string, self: boolean): void; + logon(): void; + message(channel: string, userstate: ChatUserstate, message: string, self: boolean): void; + mod(channel: string, username: string): void; + mods(channel: string, mods: string[]): void; + notice(channel: string, msgid: MsgID, message: string): void; + part(channel: string, username: string, self: boolean): void; + ping(): void; + pong(latency: number): void; + r9kbeta(channel: string, enabled: boolean): void; + reconnect(): void; + resub(channel: string, username: string, months: number, message: string, userstate: SubUserstate, methods: ResubMethod): void; + roomstate(channel: string, state: RoomState): void; + serverchange(channel: string): void; + slowmode(channel: string, enabled: boolean, length: number): void; + subscribers(channel: string, enabled: boolean): void; + subscription(channel: string, username: string, method: ResubMethod, message: string, userstate: SubUserstate): void; + timeout(channel: string, username: string, reason: string, duration: number): void; + unhost(channel: string, viewers: number): void; + unmod(channel: string, username: string): void; + whisper(from: string, userstate: ChatUserstate, message: string, self: boolean): void; +} + +export interface ClientBase { + getChannels(): string[]; + getOptions(): Options; + getUsername(): string; + isMod(channel: string, username: string): boolean; + readyState(): "CONNECTING" | "OPEN" | "CLOSING" | "CLOSED"; + on(event: any, listener: any): Client; + addListener(event: any, listener: any): Client; + removeListener(event: any, listener: any): Client; + removeAllListeners(event?: keyof Events): Client; + setMaxListeners(n: number): Client; + emits(events: Array, values: any[][]): void; // wish this could work better but either I'm just not smart enough or it's not possible + emit: (event: any) => boolean; + once(event: any, listener: any): Client; + listenerCount(event: keyof Events): number; +} + +export interface Badges { + admin?: string; + bits?: string; + broadcaster?: string; + global_mod?: string; + moderator?: string; + subscriber?: string; + staff?: string; + turbo?: string; + premium?: string; +} + +export interface CommonUserstate { + badges?: Badges; + color?: string; + "display-name"?: string; + emotes?: { [emoteid: string]: string[] }; + id?: string; + mod?: boolean; + turbo?: boolean; + 'emotes-raw'?: string; + 'badges-raw'?: string; + "room-id"?: string; + subscriber?: boolean; + 'user-type'?: "" | "mod" | "global_mod" | "admin" | "staff"; + "user-id"?: string; + "tmi-sent-ts"?: string; + flags?: string; +} + +export interface UserNoticeState extends CommonUserstate { + login?: string; + message?: string; + "system-msg"?: string; +} + +export interface CommonSubUserstate extends UserNoticeState { + "msg-param-sub-plan"?: ResubMethod; + "msg-param-sub-plan-name"?: string; +} + +export interface ChatUserstate extends CommonUserstate { + 'message-type'?: "chat" | "action" | "whisper"; + username?: string; + bits?: string; +} + +export interface SubUserstate extends CommonSubUserstate { + 'message-type'?: "sub" | "resub"; + "msg-param-cumulative-months"?: string | boolean; + "msg-param-should-share-streak"?: boolean; + "msg-param-streak-months"?: string | boolean; +} + +export interface SubGiftUserstate extends CommonSubUserstate { + 'message-type'?: "subgift" | "anonsubgift"; + "msg-param-recipient-display-name"?: string; + "msg-param-recipient-id"?: string; + "msg-param-recipient-user-name"?: string; +} + +export interface RaidUserstate extends UserNoticeState { + "message-type"?: "raid"; + "msg-param-displayName"?: string; + "msg-param-login"?: string; + "msg-param-viewerCount"?: string; +} + +export interface RitualUserstate extends UserNoticeState { + "message-type"?: "ritual"; + "msg-param-ritual-name"?: "new_chatter"; +} + +export type Userstate = ChatUserstate | SubGiftUserstate | SubUserstate | RaidUserstate | RitualUserstate; + +export interface EmoteObj { + [id: string]: [{ + code: string; + id: number; + }]; +} + +export type MsgID = "already_banned" | + "already_emote_only_on" | + "already_emote_only_off" | + "already_subs_on" | + "already_subs_off" | + "bad_ban_admin" | + "bad_ban_broadcaster" | + "bad_ban_global_mod" | + "bad_ban_self" | + "bad_ban_staff" | + "bad_commercial_error" | + "bad_host_hosting" | + "bad_host_rate_exceeded" | + "bad_mod_mod" | + "bad_mod_banned" | + "bad_timeout_admin" | + "bad_timeout_global_mod" | + "bad_timeout_self" | + "bad_timeout_staff" | + "bad_unban_no_ban" | + "bad_unmod_mod" | + "ban_success" | + "cmds_available" | + "color_changed" | + "commercial_success" | + "emote_only_on" | + "emote_only_off" | + "hosts_remaining" | + "host_target_went_offline" | + "mod_success" | + "msg_banned" | + "msg_censored_broadcaster" | + "msg_channel_suspended" | + "msg_duplicate" | + "msg_emoteonly" | + "msg_ratelimit" | + "msg_subsonly" | + "msg_timedout" | + "msg_verified_email" | + "no_help" | + "no_permission" | + "not_hosting" | + "timeout_success" | + "unban_success" | + "unmod_success" | + "unrecognized_cmd" | + "usage_ban" | + "usage_clear" | + "usage_color" | + "usage_commercial" | + "usage_disconnect" | + "usage_emote_only_on" | + "usage_emote_only_off" | + "usage_help" | + "usage_host" | + "usage_me" | + "usage_mod" | + "usage_mods" | + "usage_r9k_on" | + "usage_r9k_off" | + "usage_slow_on" | + "usage_slow_off" | + "usage_subs_on" | + "usage_subs_off" | + "usage_timeout" | + "usage_unban" | + "usage_unhost" | + "usage_unmod" | + "whisper_invalid_self" | + "whisper_limit_per_min" | + "whisper_limit_per_sec" | + "whisper_restricted_recipient"; + +export type ResubMethod = "Prime" | "1000" | "2000" | "3000"; + +export interface RoomState { + "broadcaster-lang"?: string; + "emote-only"?: boolean; + "followers-only"?: string | boolean; + "r9k"?: boolean; + "rituals"?: boolean; + "room-id"?: string; + "slow"?: string | boolean; + "subs-only"?: boolean; + "channel"?: string; +} + +export type Client = StrictEventEmitter & Actions; + +export interface Options { + options?: { + clientId?: string; + debug?: boolean; + }; + connection?: { + server?: string; + port?: number; + reconnect?: boolean; + maxReconnectAttempts?: number; + maxReconnectInverval?: number; + reconnectDecay?: number; + reconnectInterval?: number; + secure?: boolean; + timeout?: number; + }; + identity?: { + username?: string; + password?: string; + }; + channels?: string[]; + logger?: { + info?: (message: string) => any; + warn?: (message: string) => any; + error?: (message: string) => any; + }; +} + +export function client(opts: Options): Client; +export function Client(opts: Options): Client; diff --git a/types/tmi.js/strict-event-emitter-types/index.d.ts b/types/tmi.js/strict-event-emitter-types/index.d.ts new file mode 100644 index 0000000000..d5d09a5d3a --- /dev/null +++ b/types/tmi.js/strict-event-emitter-types/index.d.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2018 bterlson + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +declare const assignmentCompatibilityHack: unique symbol; +export type MatchingKeys = K extends (TRecord[K] extends TMatch ? K : never) ? K : never; +export type VoidKeys = MatchingKeys; +export interface TypeRecord { + ' _emitterType'?: T; + ' _eventsType'?: U; + ' _emitType'?: V; +} +export type ReturnTypeOfMethod = T extends (...args: any[]) => any ? ReturnType : never; +export type ReturnTypeOfMethodIfExists = S extends keyof T ? ReturnTypeOfMethod : never; +export type InnerEEMethodReturnType = T extends (...args: any[]) => any ? ReturnType extends never | undefined ? FValue : TValue : FValue; +export type EEMethodReturnType = S extends keyof T ? InnerEEMethodReturnType : FValue; +type ListenerType = [T] extends [(...args: infer U) => any] ? U : [T] extends [never] ? [] : [T]; +export interface OverriddenMethods { + on

(this: T, event: P, listener: (...args: ListenerType) => void): EEMethodReturnType; + on(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; + addListener

(this: T, event: P, listener: (...args: ListenerType) => void): EEMethodReturnType; + addListener(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; + addEventListener

(this: T, event: P, listener: (...args: ListenerType) => void): EEMethodReturnType; + addEventListener(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; + removeListener(this: T, event: keyof TEventRecord, listener: (...args: any[]) => any): EEMethodReturnType; + removeListener(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; + removeEventListener(this: T, event: keyof TEventRecord, listener: (...args: any[]) => any): EEMethodReturnType; + removeEventListener(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; + once

(this: T, event: P, listener: (...args: ListenerType) => void): EEMethodReturnType; + once(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; + emit

(this: T, event: P, ...args: ListenerType): EEMethodReturnType; + emit(event: typeof assignmentCompatibilityHack, ...args: any[]): void; +} +export type OverriddenKeys = keyof OverriddenMethods; +export type StrictEventEmitter = + Exclude, NeededMethods extends Exclude = Exclude> = + TypeRecord & Pick> & + Pick, NeededMethods>; +export default StrictEventEmitter; +export type NoUndefined = T extends undefined ? never : T; +export interface StrictBroadcast, + TEmitRecord extends NoUndefined = NoUndefined, + VK extends VoidKeys = VoidKeys, NVK extends Exclude = Exclude> { + (event: E, request: TEmitRecord[E]): any; + (event: VK): any; +} +export type EventNames, + TEventRecord extends NoUndefined = NoUndefined, + TEmitRecord extends NoUndefined = NoUndefined> = keyof TEmitRecord | keyof TEventRecord; +export type OnEventNames, + TEventRecord extends NoUndefined = NoUndefined, + TEmitRecord extends NoUndefined = NoUndefined> = keyof TEventRecord; +export type EmitEventNames, + TEventRecord extends NoUndefined = NoUndefined, + TEmitRecord extends NoUndefined = NoUndefined> = keyof TEmitRecord; diff --git a/types/tmi.js/tmi.js-tests.ts b/types/tmi.js/tmi.js-tests.ts new file mode 100644 index 0000000000..6cd2c62629 --- /dev/null +++ b/types/tmi.js/tmi.js-tests.ts @@ -0,0 +1,108 @@ +import * as tmi from "tmi.js"; + +const options: tmi.Options = { + channels: ['#channel1', '#channel2'], + connection: { + maxReconnectAttempts: 2, + maxReconnectInverval: 10, + port: 100, + reconnect: true, + reconnectDecay: 20, + reconnectInterval: 10, + secure: true, + timeout: 20 + }, + identity: { + password: "oauth:xxxOAuthIDHerexxx", + username: "yourusernamehere" + }, + logger: { + warn: (message) => { }, + error: (message) => { }, + info: (message) => { }, + }, + options: { + clientId: "xxxapiidherexxx", + debug: true + } +}; + +const client: tmi.Client = tmi.Client(options); + +client.connect().then(() => { + client.on("subscription", (channel: string, username: string, method: tmi.ResubMethod, msg: string, userstate: tmi.SubUserstate) => { + client.say(channel, `Thank you to ${userstate["display-name"]} for subscribing!`); + client.ping(); + client.r9kbeta(channel); + client.r9kbetaoff(channel); + client.raw("xxxRawIRCHere"); + switch (client.readyState()) { + case "CLOSED": + case "CLOSING": + case "CONNECTING": + case "OPEN": + break; + } + client.slow(channel, 5); + client.slowoff(channel); + client.subscribers(channel); + client.subscribersoff(channel); + client.timeout(channel, username, 600, "timeoutreason"); + client.ban(channel, username, "reason"); + client.unban(channel, username); + client.host(channel, "tohost"); + client.unhost(channel); + client.mod(channel, username); + client.unmod(channel, username); + client.whisper(username, "whisper"); + client.part(channel); + switch (method) { + case "1000": + case "2000": + case "3000": + case "Prime": + break; + } + const { badges, color, emotes, flags, id, login, message, mod, subscriber, turbo } = userstate; + if (emotes) { + emotes.test.forEach(element => { }); + } + if (badges) { + const { admin, turbo, subscriber, bits, broadcaster, global_mod, moderator, premium, staff } = badges; + } + userstate["display-name"]; + userstate["emotes-raw"]; + userstate["badges-raw"]; + userstate["message-type"]; + userstate["msg-param-cumulative-months"]; + userstate["msg-param-should-share-streak"]; + userstate["msg-param-streak-months"]; + userstate["msg-param-sub-plan"]; + userstate["msg-param-sub-plan-name"]; + userstate["room-id"]; + userstate["system-msg"]; + userstate["tmi-sent-ts"]; + userstate["user-id"]; + userstate["user-type"]; + }).on("roomstate", (chnl: string, roomstate: tmi.RoomState) => { + const { channel, r9k, rituals, slow } = roomstate; + roomstate["broadcaster-lang"]; + roomstate["emote-only"]; + roomstate["followers-only"]; + roomstate["room-id"]; + roomstate["subs-only"]; + }).once("chat", (channel: string, userstate: tmi.ChatUserstate, message: string, self: boolean) => { + const { badges, bits, color, emotes, flags, id, mod, subscriber, turbo, username } = userstate; + userstate["badges-raw"]; + userstate["display-name"]; + userstate["emotes-raw"]; + userstate["message-type"]; + userstate["room-id"]; + userstate["tmi-sent-ts"]; + userstate["user-id"]; + userstate["user-type"]; + }).once("emotesets", (sets: string, emotes: tmi.EmoteObj) => { + emotes.test[0].code; + emotes.test[0].id; + }); +}); diff --git a/types/tmi.js/tsconfig.json b/types/tmi.js/tsconfig.json new file mode 100644 index 0000000000..70fffac476 --- /dev/null +++ b/types/tmi.js/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "tmi.js-tests.ts" + ] +} diff --git a/types/tmi.js/tslint.json b/types/tmi.js/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/tmi.js/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }