Skip to content

Instantly share code, notes, and snippets.

@brecert
Created April 10, 2022 21:09
Show Gist options
  • Save brecert/962e1206ff18f0eff7bd532f188465ab to your computer and use it in GitHub Desktop.
Save brecert/962e1206ff18f0eff7bd532f188465ab to your computer and use it in GitHub Desktop.
// Utils
export interface TypedResponse<T extends Record<string, unknown> | unknown = Record<string, unknown>> extends Response {
json<P = T extends Record<string, unknown> ? T['application/json'] : unknown>(): Promise<P>
}
export const encodeURLQueryString = (params: Record<string, string | number | boolean>) =>
Object.keys(params)
.map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
.join("&");
// Main Types
export type Question = {
type: string,
category: string,
question: string
difficulty: string
correct_answer: string
incorrect_answers: string[]
}
export const enum ResponseCode {
Success = 0,
NoResults = 1,
InvalidParameter = 2,
TokenNotFound = 3,
TokenEmpty = 4
}
export enum Category {
Any = "",
"General Knowledge" = 9,
"Entertainment: Books" = 10,
"Entertainment: Film" = 11,
"Entertainment: Music" = 12,
"Entertainment: Musicals & Theatres" = 13,
"Entertainment: Television" = 14,
"Entertainment: Video Games" = 15,
"Entertainment: Board Games" = 16,
"Science & Nature" = 17,
"Science: Computers" = 18,
"Science: Mathematics" = 19,
"Mythology" = 20,
"Sports" = 21,
"Geography" = 22,
"History" = 23,
"Politics" = 24,
"Art" = 25,
"Celebrities" = 26,
"Animals" = 27,
"Vehicles" = 28,
"Entertainment: Comics" = 29,
"Science: Gadgets" = 30,
"Entertainment: Japanese Anime & Manga" = 31,
"Entertainment: Cartoon & Animations" = 32,
}
export const enum Difficulty {
Any = "",
Easy = "easy",
Medium = "medium",
Hard = "hard"
}
export const enum QuestionType {
Any = "",
/** multiple choice questions */
Multiple = "multiple",
/** true or false questions */
Boolean = "Boolean"
}
export const enum Encoding {
/** The default encoding */
HTMLCodes = "",
URL = "url3986",
Base64 = "base64"
}
export type TriviaParams = {
/** The amount of questions to fetch, `1..50`. */
amount: number
/** The category to pick questions from */
category?: Category
/** The relative difficulty of the questions picked */
difficulty?: Difficulty
/** The type of questions given */
type?: QuestionType
/** The encoding question values should in */
encode?: Encoding
/** Token to use */
token?: string
}
export type TriviaResponse =
| { response_code: ResponseCode, results: Question[] }
export type TokenParams =
| { command: "request" }
| { command: "reset", token: string }
export type TokenResponse =
| { response_code: ResponseCode, response_message?: string, token: string }
// Main Code
export const tokenFetch = (params: TokenParams): Promise<TypedResponse<{ 'application/json': TokenResponse }>> =>
fetch("https://opentdb.com/api_token.php?" + encodeURLQueryString(params))
export const triviaFetch = (params: TriviaParams): Promise<TypedResponse<{ 'application/json': TriviaResponse }>> =>
fetch("https://opentdb.com/api.php?" + encodeURLQueryString(params))
/** Utility function to help with decoding questions */
export const decodeQuestion = (question: Question, fn: (text: string) => string): Question => ({
type: fn(question.type),
category: fn(question.category),
question: fn(question.question),
difficulty: fn(question.difficulty),
correct_answer: fn(question.correct_answer),
incorrect_answers: question.incorrect_answers.map(fn),
})
// Example Usage
type AnswerList = { answer: string, correct?: boolean }[]
const getAnswers = (question: Question): AnswerList =>
([{ answer: question.correct_answer, correct: true }] as AnswerList)
.concat(question.incorrect_answers.map((answer) => ({ answer })))
.sort((a, b) => b.answer.charCodeAt(0) - a.answer.charCodeAt(0));
triviaFetch({ amount: 5, encode: Encoding.Base64 })
.then(res => res.json())
.then(res => res.results.map(res => decodeQuestion(res, atob)))
.then(questions => {
let correct = 0
for (const question of questions) {
const answers = getAnswers(question)
let chosenIndex: string | null = null
while (true) {
chosenIndex = prompt(question.question + '\n' + answers.map((ans, i) => `[${i+1}] ${ans.answer}`).join('\n'))
if (!chosenIndex || !(chosenIndex in answers)) {
alert(`'${chosenIndex}' is not a valid selection between 1 and ${answers.length}, try selecting again.`)
continue
}
break
}
const chosenAnswer = answers[parseInt(chosenIndex)-1]
const correctIndex = answers.findIndex(a => a.correct)+1
if (chosenAnswer.correct) {
alert(`[${chosenIndex}] ${chosenAnswer.answer} was the correct answer!`)
correct += 1
} else {
alert(`[${chosenIndex}] ${chosenAnswer.answer} was the incorrect answer!\nThe correct answer was: [${correctIndex}] ${question.correct_answer}`)
}
}
alert(`Total Score: ${correct} / ${questions.length}`)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment