Skip to content

RED-104: Real shadowban detection with profile analysis#180

Merged
aegloist merged 2 commits into
mainfrom
feature/RED-104-shadowban-real-check
Mar 17, 2026
Merged

RED-104: Real shadowban detection with profile analysis#180
aegloist merged 2 commits into
mainfrom
feature/RED-104-shadowban-real-check

Conversation

@aegloist
Copy link
Copy Markdown
Owner

Summary

Upgrade shadowban checker from basic reachability check to real profile analysis.

Before: Only checked if profile URL returns 200/403. Always showed "Unreachable" on Vercel.

After: Fetches profile data + recent activity via Cloudflare proxy. Returns specific verdicts:

  • CLEAR — Account healthy, has activity
  • SHADOWBANNED — Profile exists but no visible posts/comments
  • SUSPENDED — Reddit flagged the account
  • AT_RISK — Negative karma (auto-filtered by many subreddits)
  • NOT_FOUND — Account doesn't exist
  • UNREACHABLE — Can't reach Reddit

Frontend

Shows actual account details: karma, account age, email verified, activity count, suspension status. Color-coded results.

Test Plan

  • All 463 unit tests pass (7 shadowban tests covering all result paths)
  • TypeScript compiles cleanly

🤖 Generated with Claude Code

- Fetch profile data + recent activity in parallel from Reddit
- Detect: shadowban (no visible activity), suspension, negative karma, not found
- Show actual account details: karma, age, email verified, activity count
- Return specific result codes: CLEAR, SHADOWBANNED, SUSPENDED, AT_RISK, NOT_FOUND, UNREACHABLE
- Updated frontend with color-coded results and account detail cards
- Tests cover all result paths (7 tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 17, 2026 19:06
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openfast Ready Ready Preview, Comment Mar 17, 2026 7:15pm

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Upgrades the shadowban-check tool from a simple reachability test to a more detailed Reddit profile + recent-activity analysis (via the existing Cloudflare proxy), and updates the public UI and unit tests to match the richer verdicts and payload.

Changes:

  • Update API route to fetch /about.json plus recent activity, derive new results (CLEAR/SHADOWBANNED/SUSPENDED/AT_RISK/NOT_FOUND/UNREACHABLE), and return a reason + profile summary.
  • Update public shadowban-check page to display the new result categories and account details.
  • Rewrite unit tests to mock fetchRedditJson and cover the new result paths.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
tests/unit/api/shadowbanCheckToolRoute.test.ts Updates tests to mock the proxy fetch and assert new response/result shapes.
src/app/api/tools/shadowban-check/route.ts Implements profile/activity fetching and new verdict logic; extends response payload.
src/app/(public)/tools/shadowban-check/page.tsx Updates UI to present new verdicts, reason text, and account detail fields.
Comments suppressed due to low confidence (1)

tests/unit/api/shadowbanCheckToolRoute.test.ts:75

  • beforeEach uses jest.clearAllMocks(), which does not reset mock implementations. Since tests set mockImplementation / mockRejectedValue on fetchRedditJson, the implementation can leak across tests and make ordering matter. Prefer jest.resetAllMocks() here or explicitly mockedProxy.fetchRedditJson.mockReset() in beforeEach before re-stubbing.
  beforeEach(() => {
    jest.clearAllMocks();
    mockedGuards.requireSession.mockRejectedValue(new Error("UNAUTHORIZED"));
    mockedRateLimit.enforcePublicToolRateLimit.mockResolvedValue({
      allowed: true,
      limit: 20,
      remaining: 19,
      resetAfterSeconds: 60,
    });
    mockedPrisma.visibilityCheck.findMany.mockResolvedValue([]);
  });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +34 to +41
const RESULT_DISPLAY: Record<string, { label: string; color: string }> = {
CLEAR: { label: "Account looks healthy", color: "text-emerald-600" },
SHADOWBANNED: { label: "Likely shadowbanned", color: "text-red-600" },
SUSPENDED: { label: "Account suspended", color: "text-red-600" },
AT_RISK: { label: "At risk", color: "text-amber-600" },
NOT_FOUND: { label: "Account not found", color: "text-muted-foreground" },
UNREACHABLE: { label: "Check failed", color: "text-muted-foreground" },
};
Comment on lines +125 to +147
const [profileRes, activityRes] = await Promise.all([
fetchRedditJson(`/user/${encodeURIComponent(username)}/about.json`, {
signal: controller.signal,
}),
fetchRedditJson(`/user/${encodeURIComponent(username)}/.json`, {
signal: controller.signal,
}).catch(() => null),
]);

profileStatus = profileRes.status;
profileOk = profileRes.ok;

if (profileOk) {
const body = (await profileRes.json()) as { data?: ProfileData };
profileData = body?.data ?? null;
}

if (activityRes?.ok) {
const body = (await activityRes.json()) as {
data?: { children?: unknown[] };
};
activityCount = body?.data?.children?.length ?? 0;
}
Comment on lines +64 to +69
if (s.internalRisk > 0.5)
return {
result: "AT_RISK",
reason:
"Internal signals indicate elevated visibility risk from past checks.",
};
- Remove cross-workspace data leakage (internal signals no longer exposed)
- Fix username validation: allow u/ prefix (max 22 chars), strip before validate
- Separate JSON parse try/catch to avoid overwriting profileStatus
- Treat 403 as shadowban signal (not just unreachable)
- Remove Prisma dependency (public tool no longer queries internal data)
- Add tests: rate limiting, malformed JSON, u/ prefix, 403 shadowban, no leakage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aegloist aegloist merged commit 878428a into main Mar 17, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants