feat: get all internal links from a Wikipedia article

This commit is contained in:
Théo LUDWIG 2024-07-26 14:05:05 +02:00
parent 6c717e5768
commit 0ee7b35530
Signed by: theoludwig
GPG Key ID: ADFE5A563D718F3B
8 changed files with 154 additions and 46 deletions

View File

@ -2,9 +2,10 @@
- [x] chore: initial commit (+ mirror on GitHub) - [x] chore: initial commit (+ mirror on GitHub)
- [x] Deploy first staging version (v1.0.0-staging.1) - [x] Deploy first staging version (v1.0.0-staging.1)
- [ ] Add docs to add locale/edit translations, create component, install a dependency in a package, create a new package, technology used, architecture, links where it's deployed, how to use/install for end users, how to update dependencies with `npx taze -l` etc.
- [ ] Implement Wikipedia Game Solver (`website`) with inputs, button to submit, and list all articles to go from one to another, or none if it is not possible - [ ] Implement Wikipedia Game Solver (`website`) with inputs, button to submit, and list all articles to go from one to another, or none if it is not possible
- [ ] Implement toast notifications for errors, warnings, and success messages
- [ ] v1.0.0-staging.2 - [ ] v1.0.0-staging.2
- [ ] Add docs to add locale/edit translations, create component, install a dependency in a package, create a new package, technology used, architecture, links where it's deployed, how to use/install for end users, how to update dependencies with `npx taze -l` etc.
- [ ] Implement CLI (`cli`) - [ ] Implement CLI (`cli`)
- [ ] v1.0.0-staging.3 - [ ] v1.0.0-staging.3
- [ ] Implement REST API (`api`) with JSON responses ([AdonisJS](https://adonisjs.com/)) - [ ] Implement REST API (`api`) with JSON responses ([AdonisJS](https://adonisjs.com/))

View File

@ -23,6 +23,7 @@ const config = {
yellow: "#fef08a", yellow: "#fef08a",
transparent: "transparent", transparent: "transparent",
inherit: "inherit", inherit: "inherit",
current: "currentColor",
}, },
fontFamily: { fontFamily: {
sans: ["'Montserrat'", ...fontFamily.sans], sans: ["'Montserrat'", ...fontFamily.sans],

View File

@ -5,5 +5,5 @@ export const VERSION =
? "0.0.0-development" ? "0.0.0-development"
: packageJSON.version : packageJSON.version
export const GIT_REPO_URL = export const GIT_REPO_LINK =
"https://git.theoludwig.fr/theoludwig/wikipedia-game-solver" "https://git.theoludwig.fr/theoludwig/wikipedia-game-solver"

View File

@ -18,6 +18,6 @@
}, },
"home": { "home": {
"title": "Wikipedia Game Solver", "title": "Wikipedia Game Solver",
"description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page Wikipédia à une autre en utilisant uniquement des liens internes." "description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page <wikipedia>Wikipedia</wikipedia> à une autre en utilisant uniquement des liens internes."
} }
} }

View File

@ -1,6 +1,6 @@
import { useTranslations } from "next-intl" import { useTranslations } from "next-intl"
import { GIT_REPO_URL } from "@repo/constants" import { GIT_REPO_LINK } from "@repo/constants"
import { Link } from "../design/Link/Link" import { Link } from "../design/Link/Link"
export interface FooterProps { export interface FooterProps {
@ -24,7 +24,7 @@ export const Footer: React.FC<FooterProps> = (props) => {
<p> <p>
Version{" "} Version{" "}
<Link <Link
href={`${GIT_REPO_URL}/releases/tag/v${version}`} href={`${GIT_REPO_LINK}/releases/tag/v${version}`}
target="_blank" target="_blank"
isExternal={false} isExternal={false}
> >

View File

@ -1,6 +1,7 @@
"use client" "use client"
import { classNames } from "@repo/config-tailwind/classNames" import { classNames } from "@repo/config-tailwind/classNames"
import type { Locale } from "@repo/i18n/config"
import { LOCALES } from "@repo/i18n/config" import { LOCALES } from "@repo/i18n/config"
import { usePathname, useRouter } from "@repo/i18n/navigation" import { usePathname, useRouter } from "@repo/i18n/navigation"
import { useLocale, useTranslations } from "next-intl" import { useLocale, useTranslations } from "next-intl"
@ -8,7 +9,7 @@ import { useLocale, useTranslations } from "next-intl"
export const Locales: React.FC = () => { export const Locales: React.FC = () => {
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()
const currentLocale = useLocale() const localeCurrent = useLocale() as Locale
const t = useTranslations() const t = useTranslations()
@ -21,7 +22,7 @@ export const Locales: React.FC = () => {
key={locale} key={locale}
className={classNames("rounded-md p-2", { className={classNames("rounded-md p-2", {
"border-primary dark:border-primary-dark border": "border-primary dark:border-primary-dark border":
locale === currentLocale, locale === localeCurrent,
})} })}
> >
<button <button

View File

@ -1,21 +1,46 @@
"use client" "use client"
import type { Locale } from "@repo/i18n/config"
import { Button } from "@repo/ui/design/Button" import { Button } from "@repo/ui/design/Button"
import { Link } from "@repo/ui/design/Link"
import { Typography } from "@repo/ui/design/Typography"
import { useLocale } from "next-intl"
import { useState } from "react" import { useState } from "react"
import {
const wait = async (ms: number): Promise<void> => { fromLocaleToWikipediaLocale,
return await new Promise((resolve) => { getWikipediaLink,
setTimeout(resolve, ms) getWikipediaPageInternalLinks,
}) } from "./wikipedia-api"
}
export const WikipediaClient: React.FC = () => { export const WikipediaClient: React.FC = () => {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const localeCurrent = useLocale() as Locale
const localeWikipedia = fromLocaleToWikipediaLocale(localeCurrent)
const handleClick: React.MouseEventHandler<HTMLButtonElement> = async () => { const handleClick: React.MouseEventHandler<HTMLButtonElement> = async () => {
console.log("clicked")
setIsLoading(true) setIsLoading(true)
await wait(2_000) const fromArticleInput = "Linux"
const toArticleInput = "Node.js"
console.log({
fromArticleInput,
toArticleInput,
})
const [fromArticleWikipediaLinks, toArticleWikipediaLinks] =
await Promise.all([
getWikipediaPageInternalLinks({
title: fromArticleInput,
locale: localeWikipedia,
}),
getWikipediaPageInternalLinks({
title: toArticleInput,
locale: localeWikipedia,
}),
])
console.log({
fromArticleWikipediaLinks,
toArticleWikipediaLinks,
})
setIsLoading(false) setIsLoading(false)
} }
@ -24,15 +49,12 @@ export const WikipediaClient: React.FC = () => {
<Button onClick={handleClick} isLoading={isLoading} className="w-36"> <Button onClick={handleClick} isLoading={isLoading} className="w-36">
Wikipedia Wikipedia
</Button> </Button>
<Typography variant="text1" as="p">
<Button <span>Using: </span>
onClick={handleClick} <Link href={getWikipediaLink(localeWikipedia)} target="_blank">
variant="outline" {getWikipediaLink(localeWikipedia).replace("https://", "")}
isLoading={isLoading} </Link>
className="w-36" </Typography>
>
Wikipedia
</Button>
</section> </section>
) )
} }

View File

@ -1,28 +1,111 @@
import type { Locale } from "@repo/i18n/config"
export const sum = (a: number, b: number): number => { export const sum = (a: number, b: number): number => {
return a + b return a + b
} }
export const wikipediaAPIBaseURL = new URL("https://en.wikipedia.org/w/api.php") export const WIKIPEDIA_LOCALES = ["en", "fr"] as const
export type WikipediaLocale = (typeof WIKIPEDIA_LOCALES)[number]
// const getWikipediaPageLinks = async (title: string): Promise<string[]> => { export const fromLocaleToWikipediaLocale = (
// const url = new URL(wikipediaAPIBaseURL) locale: Locale,
// url.searchParams.append("action", "query") ): WikipediaLocale => {
// url.searchParams.append("titles", title) return locale === "en-US" ? "en" : "fr"
// url.searchParams.append("prop", "links") }
// url.searchParams.append("pllimit", "max")
// url.searchParams.append("format", "json")
// url.searchParams.append("origin", "*")
// const response = await fetch(url, {
// method: "GET",
// })
// if (!response.ok) {
// throw new Error(response.statusText)
// }
// const json = await response.json()
// return json
// }
// const links = await getWikipediaPageLinks("France") export const getWikipediaLink = (locale: WikipediaLocale): string => {
// const links2 = await getWikipediaPageLinks("Frddgdgdgance") return `https://${locale}.wikipedia.org`
// console.log("links.length", links) }
// console.log("links.length", links2)
interface WikipediaQueryLinksResponse {
continue?: {
plcontinue: string
continue: string
}
query: {
pages: {
[key: string]: {
pageid: number
ns: number
title: string
links: [
{
ns: number
title: string
},
]
}
}
}
limits: {
links: number
}
}
interface GetWikipediaPageInternalLinksInput {
title: string
locale: WikipediaLocale
}
interface GetWikipediaPageInternalLinksOutput {
title: string
links: string[]
}
export const getWikipediaPageInternalLinks = async (
input: GetWikipediaPageInternalLinksInput,
): Promise<GetWikipediaPageInternalLinksOutput> => {
const links: string[] = []
let title = input.title
let plcontinue: string | null = null
const fetchLinks = async (): Promise<WikipediaQueryLinksResponse> => {
const url = new URL("/w/api.php", getWikipediaLink(input.locale))
url.searchParams.append("action", "query")
url.searchParams.append("titles", title)
url.searchParams.append("prop", "links")
url.searchParams.append("pllimit", "max")
url.searchParams.append("format", "json")
url.searchParams.append("origin", "*")
if (plcontinue != null) {
url.searchParams.set("plcontinue", plcontinue)
}
const response = await fetch(url, {
method: "GET",
})
if (!response.ok) {
throw new Error(response.statusText)
}
const json = (await response.json()) as WikipediaQueryLinksResponse
return json
}
do {
try {
const response = await fetchLinks()
plcontinue = response?.continue?.plcontinue ?? null
const pages = Object.keys(response.query.pages)
const page = pages[0] ?? ""
if (page === "-1" || page === "") {
break
}
const pageData = response.query.pages[page]
if (pageData == null) {
break
}
title = pageData.title
links.push(
...pageData.links.map((link) => {
return link.title
}),
)
} catch {
break
}
} while (plcontinue != null)
return {
title,
links,
}
}