diff --git a/types/kahoot.js-updated/index.d.ts b/types/kahoot.js-updated/index.d.ts new file mode 100644 index 0000000000..5f66ccd977 --- /dev/null +++ b/types/kahoot.js-updated/index.d.ts @@ -0,0 +1,374 @@ +// Type definitions for kahoot.js-updated 2.4 +// Project: https://github.com/theusaf/kahoot.js-updated +// Definitions by: Adam Thompson-Sharpe +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +/// + +import EventEmitter = require('events'); +import { RequestOptions } from 'http'; +import WebSocket = require('ws'); + +import LiveTwoStepAnswer = require('./src/assets/LiveTwoStepAnswer'); +import LiveClientHandshake = require('./src/assets/LiveClientHandshake'); +import LiveJoinPacket = require('./src/assets/LiveJoinPacket'); +import LiveJoinTeamPacket = require('./src/assets/LiveJoinTeamPacket'); +import LiveLeavePacket = require('./src/assets/LiveLeavePacket'); +import LiveBaseMessage = require('./src/assets/LiveBaseMessage'); + +declare namespace Kahoot { + interface KahootOptions { + /** + * Options used in Challenge quizzes + */ + options?: + | { + ChallengeAutoContinue?: boolean | undefined; + ChallengeGetFullScore?: boolean | undefined; + ChallengeAlwaysCorrect?: boolean | undefined; + ChallengeUseStreakBonus?: boolean | undefined; + ChallengeWaitForInput?: boolean | undefined; + ChallengeScore?: number | null | undefined; + } + | undefined; + /** + * Modules to load or not to load. All are enabled by default + */ + modules?: + | { + extraData?: boolean | undefined; + feedback?: boolean | undefined; + gameReset?: boolean | undefined; + quizStart?: boolean | undefined; + quizEnd?: boolean | undefined; + podium?: boolean | undefined; + timeOver?: boolean | undefined; + reconnect?: boolean | undefined; + questionReady?: boolean | undefined; + questionStart?: boolean | undefined; + questionEnd?: boolean | undefined; + nameAccept?: boolean | undefined; + teamAccept?: boolean | undefined; + teamTalk?: boolean | undefined; + backup?: boolean | undefined; + answer?: boolean | undefined; + } + | undefined; + + proxy?: ((options: RequestOptions) => void | RequestOptions) | undefined; + wsproxy?: ((url: string) => WsProxyReturn) | undefined; + } + + /** Returned by wsproxy on KahootOptions */ + interface WsProxyReturn { + address: string; + protocols?: string[] | undefined; + options?: WebSocket.ClientOptions | undefined; + } + + interface LiveEventTimetrack { + id: string; + channel: string; + ext: { + /** Unix timestamp when the server received the message */ + timetrack: number; + }; + /** Whether the request was successful */ + successful: boolean; + } + + interface QuizVideo { + startTime: number; + endTime: number; + service: string; + fullUrl: string; + } + + interface QuizInfo extends QuizStart { + currentQuestion: QuestionReady; + } + + interface JoinResponse { + twoFactorAuth: boolean; + namerator: boolean; + participantId: boolean; + smartPractice: boolean; + collaborations: boolean; + liveGameId: string; + /** JavaScript code to evaluate and reply back to Kahoot with */ + challenge: string; + } + + type GameBlockType = string; + type GameBlockLayout = string; + + interface NameAccept { + playerName: string; + activeTheme: { isRTL: boolean }; + } + + interface TeamAccept { + memberNames: string[]; + } + + interface TeamTalk { + questionIndex: number; + gameBlockType: GameBlockType; + gameBlockLayout: GameBlockLayout; + duration: number; + teamTalkDuration: number; + } + + interface QuizStart { + quizQuestionAnswers: number[]; + gameBlockCount: number; + shouldRemoveSeasonalTheme: boolean; + kahootLangIsRTL: boolean; + questionCount: number; + } + + interface QuizEnd { + rank: number; + cid: number; + correctCount: number; + incorrectCount: number; + isKicked: boolean; + isGhost: boolean; + unansweredCount: number; + playerCount: number; + startTime: number; + quizId: string; + name: string; + totalScore: number; + hostId: string; + challengeId: null; + isOnlyNonPointGameBlockKahoot: boolean; + } + + interface Podium { + podiumMedalType?: 'gold' | 'silver' | 'bronze' | null | undefined; + } + + interface QuestionReady { + video: QuizVideo; + gameBlockIndex: number; + layout: string; + type: string; + timeRemaining: number; + timeAvailable: number; + numberOfAnswersAllowed: number; + currentQuestionAnswerCount: number; + numberOfChoices: number; + questionRestricted: boolean; + getReadyTimeAvailable: number; + getReadyTimeRemaining: number; + questionIndex: number; + timeLeft: number; + gameBlockLayout: GameBlockLayout; + gameBlockType: GameBlockType; + index: number; + } + + interface QuestionStart extends QuestionReady { + /** @inheritdoc */ + answer: Kahoot['answer']; + } + + interface QuestionEnd { + choice: number; + /** @todo add types */ + type: unknown; + isCorrect: boolean; + text: string; + receivedTime: number; + /** @todo add types */ + pointsQuestion: unknown; + points: number; + correctChoices: number[]; + totalScore: number; + rank: number; + nemesis: { + name: string; + isGhost: boolean; + rank: number; + totalScore: number; + }; + pointsdata: { + questionPoints: number; + totalPointsWithBonuses: number; + totalPointsWithoutBonuses: number; + answerStreakPoints: { + streakLevel: number; + streakBonus: number; + totalStreakPoints: number; + previousStreakLevel: number; + previousStreakBonus: number; + }; + }; + lastGameBlockIndex: number; + } + + interface TimeOver { + questionNumber: number; + } + + /** + * @todo Add better types. + * This event doesn't seem to get fired any more so hard to tell exact types + */ + interface RecoveryData { + data: { + defaultQuizData: { + quizQuestionAnswers: number[]; + } & Record; + getReady?: unknown; + revealAnswer?: QuestionEnd | undefined; + } & Record; + state: number; + } + + /** Map of events to their argument */ + interface EventsMap { + Joined: JoinResponse; + Disconnect: string; + GameReset: undefined; + NameAccept: NameAccept; + TeamAccept: TeamAccept; + TeamTalk: TeamTalk; + QuizStart: QuizStart; + QuizEnd: QuizEnd; + Podium: Podium; + QuestionStart: QuestionStart; + QuestionReady: QuestionReady; + QuestionEnd: QuestionEnd; + TimeOver: TimeOver; + Feedback: {}; + RecoveryData: RecoveryData; + TwoFactorCorrect: undefined; + TwoFactorReset: undefined; + TwoFactorWrong: undefined; + } + + /** All event names */ + type Events = keyof EventsMap; +} + +/** The main Kahoot class */ +declare class Kahoot extends EventEmitter { + /** + * @param options Default options to configure the client + */ + constructor(options?: Kahoot.KahootOptions); + + /** + * Create a new constructor for Kahoot with the provided options as the defaults + * @param options Default options to configure the client + * @returns A new Kahoot constructor defaulting to the provided options + */ + static defaults(options?: Kahoot.KahootOptions): typeof Kahoot; + + /** + * Creates a new client and joins the game with it + * @param pin The game pin + * @param name The player name to join with + * @param team The team member names. If set to false, team members will not be automatically added + * @fires Kahoot#Joined + * @returns The client and the return of the join call + */ + static join( + pin: string | number, + name: string, + team?: string[] | false, + ): { client: Kahoot; event: ReturnType }; + + /** + * Join a game + * @param pin The game pin + * @param name The player name to join with + * @param team The team member names. If set to false, team members will not be automatically added + * @fires Kahoot#Joined + * @fires Kahoot#TwoFactorReset + */ + join(pin: string | number, name: string, team?: string[] | false): Promise; + + /** + * + * @param team The team member names. If set to false, team members will not be automatically added + * @param emit Whether to emit the Join and TwoFactorReset events + * @fires Kahoot#Joined + * @fires Kahoot#TwoFactorReset + */ + joinTeam(team: string[] | false, emit?: boolean): Promise; + + /** + * Answer the 2FA question + * @param steps A list of four numbers. + * Each number represents one of the four colors in the 2FA code (red, blue, yellow, green) + */ + answerTwoFactorAuth(steps: [number, number, number, number]): Promise; + + /** + * Answer a question + * @param choice The index of the choice + */ + answer(choice: number): Promise; + + cid: number; + + classes: { + LiveTwoStepAnswer: typeof LiveTwoStepAnswer; + LiveClientHandshake: typeof LiveClientHandshake; + LiveJoinPacket: typeof LiveJoinPacket; + LiveJoinTeamPacket: typeof LiveJoinTeamPacket; + LiveLeavePacket: typeof LiveLeavePacket; + }; + + connected?: boolean | undefined; + + data?: { totalScore: number; streak: number; rank: number } | undefined; + + defaults: Required; + + disconnectReason?: string | undefined; + + /** The game's pin */ + gameid?: number | undefined; + + handlers: Record< + | 'feedback' + | 'gameReset' + | 'quizStart' + | 'quizEnd' + | 'podium' + | 'timeOver' + | 'QuestionReady' + | 'questionStart' + | 'questionEnd' + | 'nameAccept' + | 'teamAccept' + | 'teamTalk' + | 'PingChecker' + | 'timetrack' + | 'Disconnect' + | 'JoinFinish', + (message: LiveBaseMessage) => void + >; + + lastEvent: undefined; + messageId: number; + /** Player name */ + name?: string | undefined; + quiz?: Kahoot.QuizInfo | undefined; + settings?: Kahoot.JoinResponse | undefined; + socket: WebSocket; + userAgent: string; + waiting: {}; + + // Events + on(eventName: E, listener: (ev: Kahoot.EventsMap[E]) => void): this; + addListener(eventName: E, listener: (ev: Kahoot.EventsMap[E]) => void): this; + prependListener(eventName: E, listener: (ev: Kahoot.EventsMap[E]) => void): this; + prependOnceListener(eventName: E, listener: (ev: Kahoot.EventsMap[E]) => void): this; + removeListener(eventName: E, listener: (ev: Kahoot.EventsMap[E]) => void): this; +} + +export = Kahoot; diff --git a/types/kahoot.js-updated/kahoot.js-updated-tests.ts b/types/kahoot.js-updated/kahoot.js-updated-tests.ts new file mode 100644 index 0000000000..10966b5802 --- /dev/null +++ b/types/kahoot.js-updated/kahoot.js-updated-tests.ts @@ -0,0 +1,76 @@ +import Kahoot = require('kahoot.js-updated'); + +// No options +new Kahoot(); + +// Some options +new Kahoot({ + options: { ChallengeScore: 123 }, + modules: { backup: false }, + proxy() {}, + wsproxy: url => ({ address: url }), +}); + +// Async API +(async () => { + const kahootAsync = new Kahoot(); + await kahootAsync.join(1234567, 'foo'); // $ExpectType JoinResponse + await kahootAsync.answerTwoFactorAuth([1, 2, 3, 4]); // $ExpectType LiveEventTimetrack + await kahootAsync.joinTeam(['foo', 'bar', 'baz']); // $ExpectType LiveEventTimetrack + await kahootAsync.answer(0); // $ExpectType LiveEventTimetrack +})(); + +// With Kahoot.join +const { client: joinClient, event: joinEvent } = Kahoot.join(1234567, 'foo'); +joinClient; // $ExpectType Kahoot +joinEvent; // $ExpectType Promise + +// Defaults method +const DefaultKahoot = Kahoot.defaults({}); // $ExpectType typeof Kahoot +new DefaultKahoot(); // $ExpectType Kahoot + +// Test properties on instances +(async () => { + const kahoot = new Kahoot(); + await kahoot.join(1234567, 'foo'); + console.log('Name:', kahoot.name); + console.log('CID:', kahoot.cid); + console.log('Classes:', kahoot.classes); + console.log('Connected:', kahoot.connected); + console.log('Defaults:', kahoot.defaults); + console.log('Disconnect Reason:', kahoot.disconnectReason); + console.log('Handlers:', kahoot.handlers); + console.log('Data:', kahoot.data); + console.log('Last Event:', kahoot.lastEvent); + console.log('Message ID:', kahoot.messageId); + console.log('PIN:', kahoot.gameid); + console.log('Quiz Info:', kahoot.quiz); + console.log('Settings:', kahoot.settings); + console.log('Socket:', kahoot.socket); + console.log('User Agent:', kahoot.userAgent); + console.log('Waiting:', kahoot.waiting); +})(); + +// All events +const kahootEvents = new Kahoot(); +kahootEvents.on('Disconnect', ev => console.log('Event: Disconnect', ev)); +kahootEvents.on('Feedback', ev => console.log('Event: Feedback', ev)); +kahootEvents.on('GameReset', ev => console.log('Event: GameReset', ev)); +kahootEvents.on('Joined', ev => console.log('Event: Joined', ev)); +kahootEvents.on('NameAccept', ev => console.log('Event: NameAccept', ev)); +kahootEvents.on('TeamAccept', ev => console.log('Event: TeamAccept', ev)); +kahootEvents.on('TeamTalk', ev => console.log('Event: TeamTalk', ev)); +kahootEvents.on('QuestionReady', ev => console.log('Event: QuestionReady', ev)); +kahootEvents.on('QuestionStart', ev => { + console.log('Event: QuestionStart', ev); + ev.answer(0); // $ExpectType Promise +}); +kahootEvents.on('QuestionEnd', ev => console.log('Event: QuestionEnd', ev)); +kahootEvents.on('QuizStart', ev => console.log('Event: QuizStart', ev)); +kahootEvents.on('QuizEnd', ev => console.log('Event: QuizEnd', ev)); +kahootEvents.on('Podium', ev => console.log('Event: Podium', ev)); +kahootEvents.on('RecoveryData', ev => console.log('Event: RecoveryData', ev)); +kahootEvents.on('TimeOver', ev => console.log('Event: TimeOver', ev)); +kahootEvents.on('TwoFactorCorrect', () => console.log('Event: TwoFactorCorrect')); +kahootEvents.on('TwoFactorReset', () => console.log('Event: TwoFactorReset')); +kahootEvents.on('TwoFactorWrong', () => console.log('Event: TwoFactorWrong')); diff --git a/types/kahoot.js-updated/src/assets/LiveBaseMessage.d.ts b/types/kahoot.js-updated/src/assets/LiveBaseMessage.d.ts new file mode 100644 index 0000000000..510c0e9b84 --- /dev/null +++ b/types/kahoot.js-updated/src/assets/LiveBaseMessage.d.ts @@ -0,0 +1,12 @@ +import Kahoot = require('../..'); + +declare class LiveBaseMessage { + constructor(client: Kahoot, channel: string, data?: any); + + channel: string; + clientId: undefined; + data: {}; + ext: {}; +} + +export = LiveBaseMessage; diff --git a/types/kahoot.js-updated/src/assets/LiveClientHandshake.d.ts b/types/kahoot.js-updated/src/assets/LiveClientHandshake.d.ts new file mode 100644 index 0000000000..0d7e7299d7 --- /dev/null +++ b/types/kahoot.js-updated/src/assets/LiveClientHandshake.d.ts @@ -0,0 +1,16 @@ +import Kahoot = require('../..'); +import LiveBaseMessage = require('./LiveBaseMessage'); + +declare class LiveClientHandshake extends LiveBaseMessage { + constructor( + number: string | number, + message: { + l: number; + o: number; + readonly tc: number; + }, + client: Kahoot, + ); +} + +export = LiveClientHandshake; diff --git a/types/kahoot.js-updated/src/assets/LiveJoinPacket.d.ts b/types/kahoot.js-updated/src/assets/LiveJoinPacket.d.ts new file mode 100644 index 0000000000..9b566ac4b9 --- /dev/null +++ b/types/kahoot.js-updated/src/assets/LiveJoinPacket.d.ts @@ -0,0 +1,8 @@ +import Kahoot = require('../..'); +import LiveBaseMessage = require('./LiveBaseMessage'); + +declare class LiveJoinPacket extends LiveBaseMessage { + constructor(client: Kahoot, name?: string); +} + +export = LiveJoinPacket; diff --git a/types/kahoot.js-updated/src/assets/LiveJoinTeamPacket.d.ts b/types/kahoot.js-updated/src/assets/LiveJoinTeamPacket.d.ts new file mode 100644 index 0000000000..dc5a49541f --- /dev/null +++ b/types/kahoot.js-updated/src/assets/LiveJoinTeamPacket.d.ts @@ -0,0 +1,8 @@ +import Kahoot = require('../..'); +import LiveBaseMessage = require('./LiveBaseMessage'); + +declare class LiveJoinTeamPacket extends LiveBaseMessage { + constructor(client: Kahoot, team: string[]); +} + +export = LiveJoinTeamPacket; diff --git a/types/kahoot.js-updated/src/assets/LiveLeavePacket.d.ts b/types/kahoot.js-updated/src/assets/LiveLeavePacket.d.ts new file mode 100644 index 0000000000..28b24abba3 --- /dev/null +++ b/types/kahoot.js-updated/src/assets/LiveLeavePacket.d.ts @@ -0,0 +1,8 @@ +import Kahoot = require('../..'); +import LiveBaseMessage = require('./LiveBaseMessage'); + +declare class LiveLeavePacket extends LiveBaseMessage { + constructor(client: Kahoot); +} + +export = LiveLeavePacket; diff --git a/types/kahoot.js-updated/src/assets/LiveTwoStepAnswer.d.ts b/types/kahoot.js-updated/src/assets/LiveTwoStepAnswer.d.ts new file mode 100644 index 0000000000..ecf3281a1f --- /dev/null +++ b/types/kahoot.js-updated/src/assets/LiveTwoStepAnswer.d.ts @@ -0,0 +1,8 @@ +import Kahoot = require('../..'); +import LiveBaseMessage = require('./LiveBaseMessage'); + +declare class LiveTwoStepAnswer extends LiveBaseMessage { + constructor(client: Kahoot, sequence: number[]); +} + +export = LiveTwoStepAnswer; diff --git a/types/kahoot.js-updated/tsconfig.json b/types/kahoot.js-updated/tsconfig.json new file mode 100644 index 0000000000..ec3566d927 --- /dev/null +++ b/types/kahoot.js-updated/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": ["es6"], + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": ["../"], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": ["index.d.ts", "kahoot.js-updated-tests.ts"] +} diff --git a/types/kahoot.js-updated/tslint.json b/types/kahoot.js-updated/tslint.json new file mode 100644 index 0000000000..794cb4bf3e --- /dev/null +++ b/types/kahoot.js-updated/tslint.json @@ -0,0 +1 @@ +{ "extends": "@definitelytyped/dtslint/dt.json" }