antisocial is the other half of a StopTheMadness redirect rule. Links to X, Threads, Instagram, TikTok and Bluesky get rewritten to /<prefix>/<original path>, and this resolves the post and shows the media and the words, with a badge saying where it came from and a button to copy the original URL. Every request drives a real headless Chromium, logged out, from a residential IP. One code path, and it survives markup changes better than parsing from the outside would. Extraction is layered, most structured first: the platform's own API response caught in flight, then an inline payload, then the rendered DOM, then Open Graph tags. Media is never linked straight at a CDN. Instagram and TikTok reject requests without a matching Referer and cookies, and proxying keeps the viewer's browser from talking to the platform at all. Range is forwarded so the native video scrubber can seek. HLS is the exception, since proxying it would mean rewriting playlists. TikTok sometimes answers with a slider puzzle. Rather than reporting that as a failure, the page is parked and the viewer is handed the puzzle: screenshots stream out, pointer events are replayed back. Solving it leaves the cookie in the shared browser context, so the retry is an ordinary request. A failed resolve is never a blank error page. The card carries the platform, the original URL and the copy button, so a broken adapter still leaves the link one tap away. Verified end to end against real shared links on all five platforms, in the container, including multi-image carousels, reels, TikTok short links and photo posts. 49 tests run the adapters against captured payloads with no network. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01BGkRmLfiWuJHx6tQ12EELY
55 lines
1.7 KiB
Docker
55 lines
1.7 KiB
Docker
# antisocial — reads social posts back to you without the app.
|
|
#
|
|
# Both stages sit on the Playwright image so the Node that compiles the code
|
|
# is the Node that runs it, and so the Chromium build matches the Playwright
|
|
# package exactly. The image is large because a browser is large; the
|
|
# alternative, apt Chromium on a slim Node base, saves a few hundred MB and
|
|
# is where arm64 browser builds usually go wrong.
|
|
#
|
|
# Pinned in lockstep with the `playwright` dependency in package.json.
|
|
ARG PLAYWRIGHT_VERSION=1.62.1
|
|
|
|
FROM mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-noble AS build
|
|
|
|
WORKDIR /app
|
|
|
|
# Browsers are already in the base image; downloading them again during
|
|
# `npm ci` would double the build for nothing.
|
|
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
|
|
|
COPY package.json package-lock.json ./
|
|
RUN npm ci
|
|
|
|
COPY tsconfig.json tsconfig.build.json ./
|
|
COPY src ./src
|
|
RUN npm run build && npm prune --omit=dev
|
|
|
|
|
|
FROM mcr.microsoft.com/playwright:v${PLAYWRIGHT_VERSION}-noble AS runtime
|
|
|
|
WORKDIR /app
|
|
|
|
ENV NODE_ENV=production \
|
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \
|
|
HOST=0.0.0.0 \
|
|
PORT=8080 \
|
|
PROFILE_DIR=/data/profile
|
|
|
|
COPY --from=build /app/node_modules ./node_modules
|
|
COPY --from=build /app/dist ./dist
|
|
COPY package.json ./
|
|
COPY public ./public
|
|
|
|
# The profile directory is normally a mounted volume; create it anyway so
|
|
# the image runs standalone.
|
|
RUN mkdir -p /data/profile && chown -R pwuser:pwuser /data /app
|
|
|
|
USER pwuser
|
|
|
|
EXPOSE 8080
|
|
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
|
CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||8080)+'/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
|
|
CMD ["node", "dist/server.js"]
|