openjutsu
One-click character swap for videos: an open-source, local Higgsfield Genjutsu alternative built on ComfyUI and MiniMax H3, with FLUX.3 Klein relighting.
AI Generated TV and Film recommendations
Translation pending
MediaTypeScript · PLpgSQLGitHub @andybriggsOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A Next.js 15 web app for discovering, rating, and getting AI-powered recommendations for TV shows and movies.
Users can search for titles, maintain a watchlist, rate shows/movies 1-5 stars, and receive personalised AI recommendations. The homepage also features daily AI-curated picks sourced from online community buzz.
openai) - single-call structured output for recommendations; Responses API with web_search_preview for cronvercel.json)Install dependencies:
yarn install
Copy .env.local.example to .env.local and fill in the required values (see Environment Variables below).
Run the development server:
yarn dev
Open http://localhost:3000.
| Variable | Description |
|---|---|
TMDB_API_KEY | TMDB v3 API key |
TMDB_ACCESS_TOKEN | TMDB v4 Bearer token (used for all requests) |
OPENAI_API_KEY | OpenAI API key - used for recommendations and cron web search |
DATABASE_URL | Neon PostgreSQL connection string (pooled) |
DATABASE_URL_UNPOOLED | Neon PostgreSQL connection string (direct, for migrations) |
NEXTAUTH_SECRET | NextAuth secret |
NEXTAUTH_URL | App base URL (e.g. http://localhost:3000) |
GOOGLE_CLIENT_ID | Google OAuth client ID |
GOOGLE_CLIENT_SECRET | Google OAuth client secret |
CRON_SECRET | Bearer token for Vercel cron authentication |
NEXT_PUBLIC_CACHE_VERSION | Cache-busting version for client-side SWR requests |
STRIPE_SECRET_KEY | Stripe secret key (sk_test_... locally, sk_live_... on Vercel) |
STRIPE_WEBHOOK_SECRET | Stripe webhook signing secret (see note below) |
STRIPE_PRICE_ID | Stripe Price ID for the £1.99/month Pro plan (price_...) |
OMDB_API_KEY | OMDb API key for fetching IMDb and Rotten Tomatoes ratings on title pages - free tier (1,000 req/day) at omdbapi.com. If unset, both rating pills are hidden. |
The project uses Drizzle ORM with a Neon PostgreSQL database.
| Table | Purpose |
|---|---|
users | Google OAuth users + Stripe subscription state |
titles | TMDB title cache (keyed by tmdb_id + media_type) |
user_titles | Watchlist entries and ratings (status: WATCHLIST | RATED, rating: 1-5) |
recommendation_sets | Cached AI recommendation batches (7-day expiry) |
recommendation_items | Individual titles within a recommendation set |
ai_popular_titles | Daily AI-curated popular titles populated by the cron job |
yarn db:generate # Generate Drizzle migration files from schema changes
yarn db:push # Push schema changes directly to the database
yarn db:studio # Open Drizzle Studio (database browser)
Note:
db:pushcan get stuck prompting about existing constraints. When that happens, run the SQL statements directly via@neondatabase/serverlessusingDATABASE_URL_UNPOOLED.
| Method | Route | Description |
|---|---|---|
GET | /api/discover?type=movie|tv&timeframe=recent|all | TMDB popular or top-rated titles |
GET | /api/discover?type=movie|tv&source=ai | Daily AI picks from the ai_popular_titles table |
GET | /api/autocomplete?q={query} | TMDB title search for the search bar |
GET | /api/resolve-title?title={title}&type=movie|tv | Advanced TMDB title resolution with scoring |
| Method | Route | Description |
|---|---|---|
GET | /api/watchlist | Get the current user's watchlist |
POST | /api/watchlist | Add a title to the watchlist |
DELETE | /api/watchlist | Remove a title from the watchlist |
GET | /api/rated | Get all titles the current user has rated |
POST | /api/rated | Rate a title (1-5 stars) |
GET | /api/rating?tmdbId={id}&type=movie|tv | Get the current user's rating for a specific title |
GET | /api/recommendations | Fetch cached AI recommendations for the current user |
POST | /api/recommend | Generate AI recommendations via OpenAI - subscription-gated (3 free lifetime calls, then £1.99/month) |
GET | /api/subscription-status | Returns current user's subscription status and free call count |
POST | /api/stripe/checkout | Create a Stripe Checkout session for TellySauce Pro |
POST | /api/stripe/portal | Create a Stripe Billing Portal session (manage/cancel subscription) |
| Method | Route | Description |
|---|---|---|
GET | /api/cron/ai-popular | Daily job: fetch AI picks via OpenAI web search, resolve via TMDB, store in DB |
The cron route requires Authorization: Bearer $CRON_SECRET. Vercel adds this header automatically when the env var is set. To trigger manually:
curl -H "Authorization: Bearer <CRON_SECRET>" https://<your-domain>/api/cron/ai-popular
All AI calls use the shared openai client from src/lib/ai.ts.
Recommendations (src/app/api/recommend/route.ts): a single gpt-4o-mini call using response_format: json_schema (strict structured output). Returns { recommendations: [...] } directly - no intermediate text parsing or retry loops.
Cron (src/app/api/cron/ai-popular/route.ts): a 4-stage pipeline:
openai.responses.create with gpt-4o-mini + web_search_preview tool. Finds trending titles from Reddit.gpt-4o-mini chat completions with json_schema. Parses the Stage 1 text into structured title objects.gpt-4o-mini chat completions with json_schema. Generates Reddit-style viewer quotes per title.Scheduled daily at 06:00 UTC. The job:
gpt-4o-mini + web_search_preview tool) to find the 10 most positively discussed movies and TV shows from the past 7 days across Reddit and similar communitiesai_popular_titles with a fetched_date batch keyRegional focus: prioritises English-speaking markets (US, UK, AU, CA, IE) and Western Europe. East Asian content is included only for major Western crossovers (e.g. Squid Game, Parasite).
Guard: if 0 titles resolve successfully, the DB write is skipped and the previous day's data is preserved.
New-first ordering: before writing today's batch, the previous day's titles are queried. Titles not seen yesterday are assigned lower rank values and appear at the start of the carousel; duplicate/returning titles follow in their AI-returned order.
The /api/recommend route uses the user's rating history to generate a personalised recommendation list via OpenAI. Results are cached in recommendation_sets / recommendation_items for 7 days.
All TMDB requests use the v4 Bearer token (TMDB_ACCESS_TOKEN).
fetchTMDBTitle(tmdbId, mediaType) - src/server/tmdb.ts - canonical function for fetching full title detailsyear, TV shows use first_air_date_yearfetchTitleSources(kind, id, revalidate) - returns streaming providers for all regions as Record<string, TitleSource[]>. The title page passes this to WhereToWatch, which lets users pick their country from a dropdown. The selected region persists in localStorage (watch_region key) and auto-detects from navigator.language on first visit.AI recommendations (profile and seed modes) are gated behind a £1.99/month subscription (TellySauce Pro). Users get 3 free lifetime recommendation generations before the paywall appears. Daily AI picks remain free.
price_... ID into STRIPE_PRICE_ID./api/stripe/webhook listening to: customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.payment_failed. Copy the signing secret into STRIPE_WEBHOOK_SECRET.The Stripe CLI forwards events to your local server:
stripe listen --forward-to localhost:3000/api/stripe/webhook
Important: The
whsec_...secret printed bystripe listenis different from the one in the Stripe dashboard. Use the CLI secret in.env.localand the dashboard secret in Vercel environment variables.
Test with card number 4242 4242 4242 4242, any future expiry, any CVC.
The project uses Vitest + React Testing Library + MSW for mocking.
yarn test # Run all tests (watch mode)
yarn test:coverage # Run with coverage report
Test files live next to the code they test (*.test.ts / *.test.tsx). Global test setup is in src/test/setup.ts and shared MSW handlers in src/test/mocks/.
Deployed on Vercel. The vercel.json at the project root configures the daily cron job. Make sure CRON_SECRET is set in your Vercel project environment variables - Vercel uses it to authenticate cron requests automatically.