diff --git a/.env.local.example b/.env.local.example index de50d8e..261f5a3 100644 --- a/.env.local.example +++ b/.env.local.example @@ -7,3 +7,9 @@ NEXT_PUBLIC_URL= # AXIOS_USERAGENT= # default accept header is 'application/json, text/plain, */*' # AXIOS_ACCEPT= + +# for docker, just set the domain to the container name, default is 'libremdb_redis' +REDIS_URL=localhost:6379 + +# if you want to use redis to speed up the media proxy, set this to true +USE_REDIS = true \ No newline at end of file diff --git a/.gitignore b/.gitignore index c8bb8d0..7888140 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,7 @@ yarn-error.log* #just dev stuff dev/* -yarn.lock \ No newline at end of file +yarn.lock + +# docker +docker-compose.yml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bbd5f28 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# Thanks @yordis on Github! https://github.com/vercel/next.js/discussions/16995#discussioncomment-132339 + +# Install dependencies only when needed +FROM node:lts-alpine AS deps + +WORKDIR /opt/app +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# Rebuild the source code only when needed +# This is where because may be the case that you would try +# to build the app based on some `X_TAG` in my case (Git commit hash) +# but the code hasn't changed. +FROM node:lts-alpine AS builder + +ENV NODE_ENV=production +WORKDIR /opt/app +COPY . . +COPY --from=deps /opt/app/node_modules ./node_modules +RUN yarn build + +# Production image, copy all the files and run next +FROM node:lts-alpine AS runner + +ARG X_TAG +WORKDIR /opt/app +ENV NODE_ENV=production +COPY --from=builder /opt/app/next.config.mjs ./ +COPY --from=builder /opt/app/public ./public +COPY --from=builder /opt/app/.next ./.next +COPY --from=builder /opt/app/node_modules ./node_modules +ENV HOST=0.0.0.0 +ENV PORT=3000 +CMD ["node_modules/.bin/next", "start"] \ No newline at end of file diff --git a/README.md b/README.md index 5f38cd2..2afe6ce 100644 --- a/README.md +++ b/README.md @@ -64,12 +64,11 @@ Inspired by projects like [teddit](https://codeberg.org/teddit/teddit), [nitter] - It doesn't have all routes. I'll implement more with time :) -- I see connection being made to some Amazon domains. - For now, images and videos are directly served from Amazon. If I have enough time in the future, I'll implement a way to serve the images from libremdb instead. +- Is content served from third-parties, like Amazon? + Nope, libremdb proxies all image and video requests through the instance to avoid exposing your IP address, browser information and other personally identifiable metadata ([Contributor](https://github.com/httpjamesm)). - Will Amazon track me then? - They may log your IP address, useragent, and other such - identifiers. I'd recommend using a VPN, or accessing the website through TOR for mitigating this risk. + Also nope. All Amazon will see is the libremdb instance making the request, not you. IP address, browser information and other personally identifiable metadata is hidden from Amazon. - Why not just use IMDb? Refer to the [features section](#some-features) above. @@ -87,7 +86,9 @@ Inspired by projects like [teddit](https://codeberg.org/teddit/teddit), [nitter] A key named 'theme' is stored in Local Storage provided by your browser, if you ever override the default theme. To remove it, go to site data settings, and clear the data for this website. To permamently disable libremdb from storing your theme prefrences, either turn off JavaScript or disable access to Local Storage for libremdb. - Information collected by other services: - libremdb connects to 'media-amazon.com' and 'media-imdb.com' for fetching images and videos. So, Amazon might log your IP address, and other information(such as http headers) sent by your browser. + ~~libremdb connects to 'media-amazon.com' and 'media-imdb.com' for fetching images and videos. So, Amazon might log your IP address, and other information(such as http headers) sent by your browser.~~ + + None. libremdb proxies images anonymously through the instance for maximum privacy ([Contributor](https://github.com/httpjamesm)). --- @@ -114,7 +115,7 @@ Inspired by projects like [teddit](https://codeberg.org/teddit/teddit), [nitter] - [ ] use redis, or any other caching strategy - [x] implement a better installation method -- [ ] serve images and videos from libremdb itself +- [x] serve images and videos from libremdb itself --- @@ -128,7 +129,10 @@ As libremdb is made with Next.js, you can deploy it anywhere where Next.js is su for Node.js, visit [their website](https://nodejs.org/en/). for Git, run `sudo apt install git` if you're on a Debian-based distro. Else visit [their website](https://git-scm.com/). -2. Clone and set up the repo. +2. Install redis + You can install redis from [here](https://redis.io). + +3. Clone and set up the repo. ```bash git clone https://github.com/zyachel/libremdb.git # replace github.com with codeberg.org if you wish so. @@ -144,12 +148,16 @@ As libremdb is made with Next.js, you can deploy it anywhere where Next.js is su libremdb will start running at http://localhost:3000. To change port, modify the last command like this: `pnpm start -- -p `. -### Docker +### Docker (Local & Recommended) + +You can build the docker image using the provided Dockerfile and set it up using the example docker-compose file. + +Change the docker-compose file to your liking and run `docker-compose up -d` to start the container, that's all! + +### Docker (Built) There's a [docker image](https://github.com/PussTheCat-org/docker-libremdb-quay) made by [@TheFrenchGhosty](https://github.com/TheFrenchGhosty) for [PussTheCat.org's instance](https://libremdb.pussthecat.org). You can use that in case you wish to use docker. ---- - ## Miscellaneous ### Automatic redirection diff --git a/docker-compose.example.yml b/docker-compose.example.yml new file mode 100644 index 0000000..ce76c57 --- /dev/null +++ b/docker-compose.example.yml @@ -0,0 +1,23 @@ +# docker-compose.yml + +version: '3' + +services: + frontend: + container_name: libremdb + build: + context: . + network: host + ports: + - "3000:3000" + env_file: .env.local + depends_on: + - redis + restart: always + redis: + container_name: libremdb_redis + image: redis + # FOR DEBUGGING ONLY + # ports: + # - "6379:6379" + restart: always \ No newline at end of file diff --git a/package.json b/package.json index ab1886a..f30d178 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "dependencies": { "axios": "^0.27.2", "cheerio": "1.0.0-rc.12", + "ioredis": "^5.2.3", "next": "12.2.5", + "node-fetch": "^3.2.10", "react": "18.2.0", "react-dom": "18.2.0", "sharp": "^0.31.0" @@ -33,4 +35,4 @@ "node": ">=16.5.0", "pnpm": ">=7.0.0" } -} \ No newline at end of file +} diff --git a/src/components/title/Basic.tsx b/src/components/title/Basic.tsx index acc990e..9627e6d 100644 --- a/src/components/title/Basic.tsx +++ b/src/components/title/Basic.tsx @@ -1,22 +1,27 @@ -import { Fragment } from 'react'; -import Image from 'next/future/image'; -import Link from 'next/link'; +import { Fragment } from 'react' +import Image from 'next/future/image' +import Link from 'next/link' -import { formatNumber, formatTime, modifyIMDbImg } from '../../utils/helpers'; -import { Basic } from '../../interfaces/shared/title'; -import styles from '../../styles/modules/components/title/basic.module.scss'; +import { + formatNumber, + formatTime, + getProxiedIMDbImgUrl, + modifyIMDbImg, +} from '../../utils/helpers' +import { Basic } from '../../interfaces/shared/title' +import styles from '../../styles/modules/components/title/basic.module.scss' type Props = { - className: string; - data: Basic; -}; + className: string + data: Basic +} const Basic = ({ data, className }: Props) => { - const titleType = data.type.id; + const titleType = data.type.id const releaseTime = titleType === 'tvSeries' ? `${data.releaseYear?.start}-${data.releaseYear?.end || 'present'}` - : data.releaseYear?.start; + : data.releaseYear?.start return (
{ className={styles.imageContainer} style={{ backgroundImage: - data.poster && `url(${modifyIMDbImg(data.poster.url, 300)})`, + data.poster && + `url(${getProxiedIMDbImgUrl(modifyIMDbImg(data.poster.url, 300))})`, }} > {data.poster ? ( @@ -39,11 +45,11 @@ const Basic = ({ data, className }: Props) => { alt={data.poster.caption} priority fill - sizes='300px' + sizes="300px" /> ) : ( - + )} @@ -51,7 +57,7 @@ const Basic = ({ data, className }: Props) => {

{data.title}

-
- ); -}; + ) +} -export default Basic; +export default Basic diff --git a/src/components/title/Media.tsx b/src/components/title/Media.tsx index 583aa17..117ed39 100644 --- a/src/components/title/Media.tsx +++ b/src/components/title/Media.tsx @@ -1,41 +1,43 @@ -import Image from 'next/future/image'; -import Link from 'next/link'; -import { NextRouter } from 'next/router'; -import { Media } from '../../interfaces/shared/title'; -import { modifyIMDbImg } from '../../utils/helpers'; +import Image from 'next/future/image' +import Link from 'next/link' +import { NextRouter } from 'next/router' +import { Media } from '../../interfaces/shared/title' +import { getProxiedIMDbImgUrl, modifyIMDbImg } from '../../utils/helpers' -import styles from '../../styles/modules/components/title/media.module.scss'; +import styles from '../../styles/modules/components/title/media.module.scss' type Props = { - className: string; - media: Media; - router: NextRouter; -}; + className: string + media: Media + router: NextRouter +} const Media = ({ className, media, router }: Props) => { return (
{(media.trailer || !!media.videos.total) && (
-

Videos

+

Videos

{media.trailer && (
- ); -}; -export default Media; + ) +} +export default Media diff --git a/src/pages/about/index.tsx b/src/pages/about/index.tsx index b0f6418..5960897 100644 --- a/src/pages/about/index.tsx +++ b/src/pages/about/index.tsx @@ -1,19 +1,19 @@ /* eslint-disable react/no-unescaped-entities */ -import Link from 'next/link'; -import Meta from '../../components/Meta/Meta'; -import Layout from '../../layouts/Layout'; +import Link from 'next/link' +import Meta from '../../components/Meta/Meta' +import Layout from '../../layouts/Layout' -import styles from '../../styles/modules/pages/about/about.module.scss'; +import styles from '../../styles/modules/pages/about/about.module.scss' const About = () => { return ( <> -
+

@@ -22,12 +22,12 @@ const About = () => {
  • {

  • {

  • {

-
+

Questions you may have

@@ -91,21 +91,21 @@ const About = () => {

Replace `imdb.com` in any IMDb URL with any of the instances. For example: ` - + imdb.com/title/tt1049413 ` to ` libremdb.iket.me/title/tt1049413 ` . To avoid changing the URLs manually, you can use extensions like{' '} LibRedirect @@ -133,12 +133,21 @@ const About = () => {

- I see connection being made to some Amazon domains. + Is content served from third-parties, like Amazon?

- For now, images and videos are directly served from Amazon. If I - have enough time in the future, I'll implement a way to serve - the images from libremdb instead. + Nope, libremdb proxies all image and video requests through the + instance to avoid exposing your IP address, browser information + and other personally identifiable metadata ( + + Contributor + + ).

@@ -146,9 +155,9 @@ const About = () => { Will Amazon track me then?

- They may log your IP address, useragent, and other such - identifiers. I'd recommend using a VPN, or accessing the website - through TOR for mitigating this risk. + Also nope. All Amazon will see is the libremdb instance making + the request, not you. IP address, browser information and other + personally identifiable metadata is hidden from Amazon.

@@ -157,7 +166,7 @@ const About = () => {

Refer to the{' '} - + features section {' '} above. @@ -187,8 +196,8 @@ const About = () => {

That's great! I've a couple of{' '} - - contact methods + + contact methods . Send your beautiful suggestions(or complaints), or just drop a hi. @@ -198,7 +207,7 @@ const About = () => {

- ); -}; + ) +} -export default About; +export default About diff --git a/src/pages/api/media_proxy.ts b/src/pages/api/media_proxy.ts new file mode 100644 index 0000000..f35b3be --- /dev/null +++ b/src/pages/api/media_proxy.ts @@ -0,0 +1,59 @@ +import { NextApiRequest, NextApiResponse } from 'next' +import redis from '../../utils/redis' +import axiosInstance from '../../utils/axiosInstance' +import { AxiosResponse } from 'axios' + +const regex = + /^https:\/\/((m\.)?media-amazon\.com|imdb-video\.media-imdb\.com).*\.(jpg|jpeg|png|mp4|gif|webp).*$/ + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const mediaUrl = req.query.url as string | undefined + + if (!mediaUrl || !regex.test(mediaUrl)) + return res.status(400).json({ + success: false, + message: 'Invalid query', + }) + + if (process.env.USE_REDIS === 'true') { + const cachedMedia = await redis.getBuffer(mediaUrl) + + if (cachedMedia) { + res.setHeader('x-cached', 'true') + res.status(302).send(cachedMedia) + return + } + } + + let mediaRes: AxiosResponse + try { + mediaRes = await axiosInstance(mediaUrl, { responseType: 'arraybuffer' }) + } catch { + res.status(404) + res.json({ + success: false, + message: 'Error from IMDb', + }) + return + } + + const data = mediaRes.data + + if (process.env.USE_REDIS === 'true') { + // save in redis for 30 minutes + await redis.setex(mediaUrl, 30 * 60, Buffer.from(data)) + } + + // send media + res.setHeader('x-cached', 'false') + res.send(data) +} + +export const config = { + api: { + responseLimit: false, + }, +} diff --git a/src/pages/privacy/index.tsx b/src/pages/privacy/index.tsx index f78b9c8..4f40577 100644 --- a/src/pages/privacy/index.tsx +++ b/src/pages/privacy/index.tsx @@ -1,14 +1,14 @@ -import Meta from '../../components/Meta/Meta'; -import Layout from '../../layouts/Layout'; +import Meta from '../../components/Meta/Meta' +import Layout from '../../layouts/Layout' -import styles from '../../styles/modules/pages/privacy/privacy.module.scss'; +import styles from '../../styles/modules/pages/privacy/privacy.module.scss' const Privacy = () => { return ( <>
@@ -41,24 +41,11 @@ const Privacy = () => { Local Storage for libremdb.

-
-

- Information collected by other services -

-

- libremdb connects to 'media-amazon.com' and 'media-imdb.com' for - fetching images and videos. So, Amazon might log your IP - address, and other information(such as http headers) sent by - your browser. -

-