feat: implement adding movie details from tmdb
This commit is contained in:
		| @@ -4,7 +4,6 @@ import IconStarFilled from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/star- | |||||||
| export const Star = ( | export const Star = ( | ||||||
|   { max = 5, rating = 3 }: { max?: number; rating: number }, |   { max = 5, rating = 3 }: { max?: number; rating: number }, | ||||||
| ) => { | ) => { | ||||||
|   console.log({ max, rating }); |  | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       class="flex gap-2 px-4 py-2 rounded-2xl bg-gray-200 z-10" |       class="flex gap-2 px-4 py-2 rounded-2xl bg-gray-200 z-10" | ||||||
|   | |||||||
| @@ -46,9 +46,9 @@ export const menus: Record<string, Menu> = { | |||||||
|                   body: JSON.stringify({ tmdbId: m.id }), |                   body: JSON.stringify({ tmdbId: m.id }), | ||||||
|                 }); |                 }); | ||||||
|                 const j = await res.json(); |                 const j = await res.json(); | ||||||
|                 console.log("Selected", { movie, m, j }); |  | ||||||
|                 state.visible.value = false; |                 state.visible.value = false; | ||||||
|                 state.activeState.value = "normal"; |                 state.activeState.value = "normal"; | ||||||
|  |                 window.location.reload(); | ||||||
|               }, |               }, | ||||||
|             })), |             })), | ||||||
|           }; |           }; | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								lib/cache/cache.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								lib/cache/cache.ts
									
									
									
									
										vendored
									
									
								
							| @@ -30,13 +30,20 @@ const cache = await createCache(); | |||||||
|  |  | ||||||
| export async function get<T>(id: string, binary = false) { | export async function get<T>(id: string, binary = false) { | ||||||
|   if (binary && !(cache instanceof Map)) { |   if (binary && !(cache instanceof Map)) { | ||||||
|     return await cache.sendCommand("GET", [id], { |     const cacheHit = await cache.sendCommand("GET", [id], { | ||||||
|       returnUint8Arrays: true, |       returnUint8Arrays: true, | ||||||
|     }) as T; |     }) as T; | ||||||
|  |     if (cacheHit) console.log("[cache] HIT ", { id }); | ||||||
|  |     else console.log("[cache] MISS", { id }); | ||||||
|  |     return cacheHit; | ||||||
|   } |   } | ||||||
|   return await cache.get(id) as T; |   const cacheHit = await cache.get(id) as T; | ||||||
|  |   if (cacheHit) console.log("[cache] HIT ", { id }); | ||||||
|  |   else console.log("[cache] MISS", { id }); | ||||||
|  |   return cacheHit; | ||||||
| } | } | ||||||
|  |  | ||||||
| export async function set<T extends RedisValue>(id: string, content: T) { | export async function set<T extends RedisValue>(id: string, content: T) { | ||||||
|  |   console.log("[cache] storing ", { id }); | ||||||
|   return await cache.set(id, content); |   return await cache.set(id, content); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
| import { unified } from "npm:unified"; | import { unified } from "npm:unified"; | ||||||
| import remarkParse from "npm:remark-parse"; | import remarkParse from "npm:remark-parse"; | ||||||
| import remarkFrontmatter from "https://esm.sh/remark-frontmatter@4"; | import remarkStringify from "https://esm.sh/remark-stringify@10.0.3"; | ||||||
| import remarkRehype from "https://esm.sh/remark-rehype"; | import remarkFrontmatter, { | ||||||
| import rehypeSanitize from "https://esm.sh/rehype-sanitize"; |   Root, | ||||||
| import rehypeStringify from "https://esm.sh/rehype-stringify"; | } from "https://esm.sh/remark-frontmatter@4.0.1"; | ||||||
|  | import remarkRehype from "https://esm.sh/remark-rehype@10.1.0"; | ||||||
|  | import rehypeSanitize from "https://esm.sh/rehype-sanitize@5.0.1"; | ||||||
|  | import rehypeStringify from "https://esm.sh/rehype-stringify@9.0.3"; | ||||||
| import { parse } from "https://deno.land/std@0.194.0/yaml/mod.ts"; | import { parse } from "https://deno.land/std@0.194.0/yaml/mod.ts"; | ||||||
| import * as cache from "@lib/cache/documents.ts"; | import * as cache from "@lib/cache/documents.ts"; | ||||||
|  |  | ||||||
| @@ -38,7 +41,7 @@ export async function getDocuments(): Promise<Document[]> { | |||||||
|   return documents; |   return documents; | ||||||
| } | } | ||||||
|  |  | ||||||
| export async function createDocument( | export function createDocument( | ||||||
|   name: string, |   name: string, | ||||||
|   content: string | ArrayBuffer, |   content: string | ArrayBuffer, | ||||||
|   mediaType?: string, |   mediaType?: string, | ||||||
| @@ -49,13 +52,11 @@ export async function createDocument( | |||||||
|     headers.append("Content-Type", mediaType); |     headers.append("Content-Type", mediaType); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   const response = await fetch(SILVERBULLET_SERVER + "/" + name, { |   return fetch(SILVERBULLET_SERVER + "/" + name, { | ||||||
|     body: content, |     body: content, | ||||||
|     method: "PUT", |     method: "PUT", | ||||||
|     headers, |     headers, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   return response; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export async function getDocument(name: string): Promise<string> { | export async function getDocument(name: string): Promise<string> { | ||||||
| @@ -70,6 +71,33 @@ export async function getDocument(name: string): Promise<string> { | |||||||
|   return text; |   return text; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function transformDocument(input: string, cb: (r: Root) => Root) { | ||||||
|  |   const out = unified() | ||||||
|  |     .use(remarkParse) | ||||||
|  |     .use(remarkFrontmatter, ["yaml"]) | ||||||
|  |     .use(() => (tree) => { | ||||||
|  |       return cb(tree); | ||||||
|  |     }) | ||||||
|  |     .use(remarkStringify) | ||||||
|  |     .processSync(input); | ||||||
|  |  | ||||||
|  |   return String(out) | ||||||
|  |     .replace("***\n", "---") | ||||||
|  |     .replace("----------------", "---") | ||||||
|  |     .replace("\n---", "---") | ||||||
|  |     .replace(/^(date:[^'\n]*)'|'/gm, (match, p1, p2) => { | ||||||
|  |       if (p1) { | ||||||
|  |         // This is a line starting with date: followed by single quotes | ||||||
|  |         return p1.replace(/'/gm, ""); | ||||||
|  |       } else if (p2) { | ||||||
|  |         return ""; | ||||||
|  |       } else { | ||||||
|  |         // This is a line with single quotes, but not starting with date: | ||||||
|  |         return match; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
| export function parseDocument(doc: string) { | export function parseDocument(doc: string) { | ||||||
|   return unified() |   return unified() | ||||||
|     .use(remarkParse).use(remarkFrontmatter, ["yaml", "toml"]) |     .use(remarkParse).use(remarkFrontmatter, ["yaml", "toml"]) | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								lib/string.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								lib/string.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | export function formatDate(date: Date): string { | ||||||
|  |   const options = { year: "numeric", month: "long", day: "numeric" } as const; | ||||||
|  |   return new Intl.DateTimeFormat("en-US", options).format(date); | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								main.ts
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								main.ts
									
									
									
									
									
								
							| @@ -11,6 +11,7 @@ import manifest from "./fresh.gen.ts"; | |||||||
|  |  | ||||||
| import twindPlugin from "$fresh/plugins/twind.ts"; | import twindPlugin from "$fresh/plugins/twind.ts"; | ||||||
| import twindConfig from "./twind.config.ts"; | import twindConfig from "./twind.config.ts"; | ||||||
|  |  | ||||||
| await start(manifest, { | await start(manifest, { | ||||||
|   plugins: [twindPlugin(twindConfig)], |   plugins: [twindPlugin(twindConfig)], | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,8 +1,14 @@ | |||||||
| import { HandlerContext } from "$fresh/server.ts"; | import { HandlerContext } from "$fresh/server.ts"; | ||||||
| import { createDocument, getDocument } from "@lib/documents.ts"; | import { | ||||||
| import { fileExtension } from "https://deno.land/x/file_extension/mod.ts"; |   createDocument, | ||||||
| import { parseMovie } from "@lib/movies.ts"; |   getDocument, | ||||||
|  |   transformDocument, | ||||||
|  | } from "@lib/documents.ts"; | ||||||
|  | import { fileExtension } from "https://deno.land/x/file_extension@v2.1.0/mod.ts"; | ||||||
|  | import { type Movie, parseMovie } from "@lib/movies.ts"; | ||||||
| import * as tmdb from "@lib/tmdb.ts"; | import * as tmdb from "@lib/tmdb.ts"; | ||||||
|  | import { parse, stringify } from "https://deno.land/std@0.194.0/yaml/mod.ts"; | ||||||
|  | import { formatDate } from "@lib/string.ts"; | ||||||
|  |  | ||||||
| function safeFileName(inputString: string): string { | function safeFileName(inputString: string): string { | ||||||
|   // Convert the string to lowercase |   // Convert the string to lowercase | ||||||
| @@ -25,6 +31,45 @@ export async function getMovie(name: string) { | |||||||
|   return movie; |   return movie; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function updateMovieMetadata( | ||||||
|  |   name: string, | ||||||
|  |   metadata: Partial<Movie["meta"]>, | ||||||
|  | ) { | ||||||
|  |   const docId = `Media/movies/${name}.md`; | ||||||
|  |  | ||||||
|  |   const currentDoc = await getDocument(docId); | ||||||
|  |   if (!currentDoc) return; | ||||||
|  |  | ||||||
|  |   const newDoc = transformDocument(currentDoc, (root) => { | ||||||
|  |     const frontmatterNode = root.children.find((c) => c.type === "yaml"); | ||||||
|  |  | ||||||
|  |     const frontmatter = frontmatterNode?.value as string; | ||||||
|  |  | ||||||
|  |     if (frontmatter) { | ||||||
|  |       const value = parse(frontmatter) as Movie["meta"]; | ||||||
|  |  | ||||||
|  |       if (metadata.author && !value.author) { | ||||||
|  |         value.author = metadata.author; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (metadata.image && !value.image) { | ||||||
|  |         value.image = metadata.image; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (metadata.date && !value.date) { | ||||||
|  |         value.date = formatDate(metadata.date); | ||||||
|  |       } | ||||||
|  |       frontmatterNode.value = stringify(value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return root; | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const response = await createDocument(docId, newDoc); | ||||||
|  |  | ||||||
|  |   return response; | ||||||
|  | } | ||||||
|  |  | ||||||
| export const handler = async ( | export const handler = async ( | ||||||
|   _req: Request, |   _req: Request, | ||||||
|   _ctx: HandlerContext, |   _ctx: HandlerContext, | ||||||
| @@ -32,8 +77,9 @@ export const handler = async ( | |||||||
|   const headers = new Headers(); |   const headers = new Headers(); | ||||||
|   headers.append("Content-Type", "application/json"); |   headers.append("Content-Type", "application/json"); | ||||||
|  |  | ||||||
|  |   const movie = await getMovie(_ctx.params.name); | ||||||
|  |  | ||||||
|   if (_req.method === "GET") { |   if (_req.method === "GET") { | ||||||
|     const movie = await getMovie(_ctx.params.name); |  | ||||||
|     return new Response(JSON.stringify(movie)); |     return new Response(JSON.stringify(movie)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -46,25 +92,41 @@ export const handler = async ( | |||||||
|         status: 400, |         status: 400, | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const movieDetails = await tmdb.getMovie(tmdbId); |     const movieDetails = await tmdb.getMovie(tmdbId); | ||||||
|     const movieCredits = await tmdb.getMovieCredits(tmdbId); |     const movieCredits = !movie.meta.author && | ||||||
|  |       await tmdb.getMovieCredits(tmdbId); | ||||||
|  |  | ||||||
|     const releaseDate = movieDetails.release_date; |     const releaseDate = movieDetails.release_date; | ||||||
|     const posterPath = movieDetails.poster_path; |     const posterPath = movieDetails.poster_path; | ||||||
|     const director = movieCredits?.crew?.filter?.((person) => |     const director = movieCredits?.crew?.filter?.((person) => | ||||||
|       person.job === "Director" |       person.job === "Director" | ||||||
|     ); |     )[0]; | ||||||
|  |  | ||||||
|     if (posterPath) { |     let finalPath = ""; | ||||||
|  |     if (posterPath && !movie.meta.image) { | ||||||
|       const poster = await tmdb.getMoviePoster(posterPath); |       const poster = await tmdb.getMoviePoster(posterPath); | ||||||
|       const extension = fileExtension(posterPath); |       const extension = fileExtension(posterPath); | ||||||
|       const finalPath = `Media/movies/images/${ |  | ||||||
|  |       finalPath = `Media/movies/images/${ | ||||||
|         safeFileName(name) |         safeFileName(name) | ||||||
|       }_cover.${extension}`; |       }_cover.${extension}`; | ||||||
|       await createDocument(finalPath, poster); |       await createDocument(finalPath, poster); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     console.log({ releaseDate, director, posterPath }); |     const metadata = {} as Movie["meta"]; | ||||||
|  |     if (releaseDate) { | ||||||
|  |       metadata.date = new Date(releaseDate); | ||||||
|  |     } | ||||||
|  |     if (finalPath) { | ||||||
|  |       metadata.image = finalPath; | ||||||
|  |     } | ||||||
|  |     if (director) { | ||||||
|  |       metadata.author = director.name; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     await updateMovieMetadata(name, metadata); | ||||||
|  |  | ||||||
|     return new Response(JSON.stringify(movieCredits), { |     return new Response(JSON.stringify(movieCredits), { | ||||||
|       headers, |       headers, | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -24,6 +24,8 @@ export const handler = async ( | |||||||
|   const headers = new Headers(); |   const headers = new Headers(); | ||||||
|   headers.append("Content-Type", "application/json"); |   headers.append("Content-Type", "application/json"); | ||||||
|  |  | ||||||
|  |   console.log("[api] getting movie credits"); | ||||||
|  |  | ||||||
|   const cacheId = `/movie/credits/${id}`; |   const cacheId = `/movie/credits/${id}`; | ||||||
|  |  | ||||||
|   const cachedResponse = await cache.get<CachedMovieCredits>(cacheId); |   const cachedResponse = await cache.get<CachedMovieCredits>(cacheId); | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| [ZoneTransfer] |  | ||||||
| ZoneId=3 |  | ||||||
| HostUrl=about:internet |  | ||||||
		Reference in New Issue
	
	Block a user