import PrDirectoryRoutes from "../routes/PrDirectoryRoutes"; import Requests from "../util/Requests"; import Response from "../util/Response"; import { AxiosPromise } from "axios"; /** * Allowed outlet types in the PR directory. */ export type PrPublicationType = "blog" | "podcast" | "publication"; /** * Eligibility state for a PR outlet. */ export type PrEligibilityStatus = "eligible" | "ineligible" | "needs_review" | "duplicate" | "archived"; /** * Verification state used by PR outlets, people, links, and contact points. */ export type PrVerificationStatus = "unverified" | "verified" | "stale" | "blocked" | "failed" | "needs_review"; /** * Email health status stored on the outlet-level PR email field. */ export type PrEmailStatus = "unknown" | "verified" | "bounced" | "missing" | "needs_review"; /** * Contact verification state for normalized contact points. */ export type PrContactVerificationStatus = | "unverified" | "verified" | "stale" | "bounced" | "invalid" | "blocked" | "needs_review"; /** * Link refresh status for evidence URLs. */ export type PrLinkStatus = "unverified" | "ok" | "redirected" | "broken" | "blocked" | "failed" | "stale"; /** * Refresh state for discovered RSS/Atom/JSON feeds. */ export type PrFeedStatus = "unverified" | "ok" | "empty" | "blocked" | "failed" | "stale" | "needs_review"; /** * Feed format detected during PR content ingestion. */ export type PrFeedType = "rss" | "atom" | "json" | "unknown"; /** * Filters accepted by `/pr/publications` and `/pr/report`. * * Tag filters are human-readable slugs from `/pr/tags`. The backend accepts * format, genre, platform, and audience as namespace-specific tag filters so * frontend screens can expose simple controls without knowing pivot table * details. */ export interface PrPublicationSearchParams { q?: string; type?: PrPublicationType; eligibility_status?: PrEligibilityStatus; verification_status?: PrVerificationStatus; dedicated_to_gaming?: boolean; has_email?: boolean; country?: string; language?: string; canonical_domain?: string; tag?: string; format?: string; genre?: string; platform?: string; audience?: string; sort?: "name" | "-name" | "type" | "-type" | "eligibility_status" | "-eligibility_status" | "verification_status" | "-verification_status" | "last_verified_at" | "-last_verified_at" | "updated_at" | "-updated_at" | string; page?: number; per_page?: number; } /** * Filters accepted by `/pr/people`. */ export interface PrPeopleSearchParams { q?: string; publication_id?: string; role_category?: string; is_active?: boolean; verification_status?: PrVerificationStatus; has_email?: boolean; tag?: string; role?: string; sort?: "name" | "-name" | "verification_status" | "-verification_status" | "last_verified_at" | "-last_verified_at" | "updated_at" | "-updated_at" | string; page?: number; per_page?: number; } /** * Filters accepted by `/pr/feeds`. */ export interface PrFeedSearchParams { q?: string; publication_id?: string; feed_type?: PrFeedType; status?: PrFeedStatus; source?: string; has_stories?: boolean; sort?: "title" | "-title" | "feed_type" | "-feed_type" | "status" | "-status" | "last_fetched_at" | "-last_fetched_at" | "next_fetch_at" | "-next_fetch_at" | "updated_at" | "-updated_at" | string; page?: number; per_page?: number; include_raw?: boolean; } /** * Filters accepted by `/pr/stories`. */ export interface PrStorySearchParams { q?: string; publication_id?: string; pr_feed_id?: string; pr_person_id?: string; story_type?: string; language?: string; ingestion_status?: string; has_author?: boolean; published_after?: string; published_before?: string; sort?: "published_at" | "-published_at" | "updated_at" | "-updated_at" | "title" | "-title" | string; page?: number; per_page?: number; } /** * Filters accepted by `/pr/tags`. */ export interface PrTagSearchParams { namespace?: string; q?: string; } /** * Query parameters accepted by `/titles/{title_id}/pr/matches`. * * The title matcher uses the title profile plus optional human-readable search * context to rank eligible outlets and explain why each outlet is a fit. */ export interface PrTitleMatchParams extends PrPublicationSearchParams { genres?: string[]; platforms?: string[]; audiences?: string[]; limit?: number; } /** * Query parameters accepted by `/titles/{title_id}/pr/research`. */ export interface PrTitleResearchParams extends PrStorySearchParams { limit?: number; story_sort?: string; } /** * Request body accepted by `/admin/pr/verification/queue`. */ export interface PrQueueVerificationRequest { due?: boolean; limit?: number; link_ids?: string[]; } /** * Review-only PR draft request for `/titles/{title_id}/pr/drafts`. */ export interface PrTitleDraftRequest { publication_id?: string; pr_person_id?: string; q?: string; prompt?: string; press_kit_url?: string; trailer_url?: string; demo_or_review_key?: string; embargo_or_timing?: string; sender?: { name?: string; }; use_ai?: boolean; } /** * Admin request body for `/admin/pr/feeds/refresh`. */ export interface PrFeedRefreshRequest { discover?: boolean; due?: boolean; queue?: boolean; limit?: number; feed_ids?: string[]; publication_ids?: string[]; } /** * A normalized metadata tag used to filter and match PR outlets, people, and * roles. */ export interface PrTag { id: string; namespace: string; slug: string; label: string; description?: string | null; pivot?: { confidence?: number | null; source?: string | null; source_link_id?: string | null; verified_at?: string | null; metadata?: Record | null; }; metadata?: Record; created_at?: string | null; updated_at?: string | null; } /** * A refreshable evidence URL that supports an outlet, person, role, or contact * point. */ export interface PrLink { id: string; linkable_type?: string | null; linkable_id?: string | null; link_type: string; url: string; canonical_url?: string | null; final_url?: string | null; domain?: string | null; priority: number; http_status?: number | null; status: PrLinkStatus; content_type?: string | null; content_hash?: string | null; etag?: string | null; last_modified_at?: string | null; last_checked_at?: string | null; next_check_at?: string | null; last_error?: string | null; is_source_of_truth: boolean; metadata?: Record; created_at?: string | null; updated_at?: string | null; } /** * RSS/Atom/JSON feed discovered for a publication and used for story research. */ export interface PrFeed { id: string; publication_id: string; pr_link_id?: string | null; feed_url: string; canonical_url?: string | null; feed_type: PrFeedType; title?: string | null; description?: string | null; language?: string | null; status: PrFeedStatus; http_status?: number | null; content_type?: string | null; etag?: string | null; raw_feed_hash?: string | null; raw_feed_content?: string | null; raw_feed_size?: number; last_modified_at?: string | null; last_fetched_at?: string | null; next_fetch_at?: string | null; item_count_last_fetch: number; source?: string | null; last_error?: string | null; publication?: PrPublication | null; stories_count?: number; stories?: PrStory[]; metadata?: Record; created_at?: string | null; updated_at?: string | null; } /** * A story, article, guide, review, or episode imported from a PR feed. */ export interface PrStory { id: string; publication_id: string; pr_feed_id?: string | null; canonical_url?: string | null; guid?: string | null; title: string; dek?: string | null; summary?: string | null; content_excerpt?: string | null; content_hash?: string | null; author_name_raw?: string | null; author_email_raw?: string | null; author_url_raw?: string | null; published_at?: string | null; updated_at_feed?: string | null; story_type?: string | null; language?: string | null; categories?: string[]; tags?: string[]; media_url?: string | null; analysis?: Record; ingestion_status?: string | null; publication?: PrPublication | null; feed?: PrFeed | null; authors?: PrStoryAuthor[]; created_at?: string | null; updated_at?: string | null; } /** * Raw byline evidence and optional match to a known PR contact. */ export interface PrStoryAuthor { id: string; pr_story_id: string; publication_id?: string | null; pr_person_id?: string | null; author_name?: string | null; author_email?: string | null; author_url?: string | null; confidence?: number | null; match_source?: string | null; evidence?: Record; story?: PrStory | null; person?: PrPerson | null; created_at?: string | null; updated_at?: string | null; } /** * A normalized way to reach an outlet, person, or publication role. */ export interface PrContactPoint { id: string; contactable_type: string; contactable_id: string; pr_link_id?: string | null; contact_type: "email" | "linkedin" | "x" | "bluesky" | "contact_form" | string; label?: string | null; value: string; normalized_value: string; verification_status: PrContactVerificationStatus; confidence?: number | null; is_primary: boolean; first_seen_at?: string | null; last_seen_at?: string | null; source_link?: PrLink | null; metadata?: Record; created_at?: string | null; updated_at?: string | null; } /** * The role a PR person has at one publication, blog, or podcast. */ export interface PublicationPerson { id: string; publication_id: string; pr_person_id: string; source_link_id?: string | null; role_title?: string | null; role_category?: string | null; email?: string | null; email_status: "unknown" | "verified" | "bounced" | "invalid" | "needs_review"; is_primary_contact: boolean; is_current: boolean; confidence?: number | null; started_at?: string | null; ended_at?: string | null; last_verified_at?: string | null; source_notes?: string | null; person?: PrPerson | null; publication?: PrPublication | null; source_link?: PrLink | null; contact_points?: PrContactPoint[]; tags?: PrTag[]; metadata?: Record; created_at?: string | null; updated_at?: string | null; } /** * A gaming-focused publication, independent blog, or podcast in the PR * directory. */ export interface PrPublication { id: string; name: string; slug?: string | null; type: PrPublicationType; url?: string | null; canonical_domain?: string | null; description?: string | null; main_pr_email?: string | null; main_pr_email_status: PrEmailStatus; dedicated_to_gaming: boolean; eligibility_status: PrEligibilityStatus; exclusion_reason?: string | null; language?: string | null; country?: string | null; network_or_owner?: string | null; verification_status: PrVerificationStatus; last_verified_at?: string | null; next_verification_at?: string | null; source_imported_at?: string | null; people_count?: number; contact_points_count?: number; links_count?: number; feeds_count?: number; stories_count?: number; people?: PublicationPerson[]; contact_points?: PrContactPoint[]; links?: PrLink[]; feeds?: PrFeed[]; stories?: PrStory[]; tags?: PrTag[]; metadata?: Record; created_at?: string | null; updated_at?: string | null; } /** * A journalist, editor, podcast host, producer, contributor, or other PR * contact associated with one or more gaming-focused outlets. */ export interface PrPerson { id: string; name: string; slug?: string | null; bio?: string | null; location?: string | null; linkedin_url?: string | null; x_url?: string | null; bluesky_url?: string | null; website_url?: string | null; is_active: boolean; verification_status: PrVerificationStatus; last_verified_at?: string | null; next_verification_at?: string | null; roles_count?: number; contact_points_count?: number; links_count?: number; stories_count?: number; roles?: PublicationPerson[]; contact_points?: PrContactPoint[]; links?: PrLink[]; stories?: PrStoryAuthor[]; tags?: PrTag[]; metadata?: Record; created_at?: string | null; updated_at?: string | null; } /** * Aggregate PR directory health metrics returned by `/pr/report`. */ export interface PrDirectoryReport { generated_at: string; publications: { total: number; by_type: Record; by_eligibility_status: Record; by_verification_status: Record; dedicated_to_gaming: number; with_email: number; due_for_verification: number; }; people: { total: number; active: number; with_email: number; by_verification_status: Record; }; links: { total: number; by_type: Record; by_status: Record; due_for_check: number; }; contacts: { total: number; by_type: Record; by_status: Record; }; tags: { total: number; by_namespace: Record; }; feeds: { total: number; by_status: Record; due_for_fetch: number; }; stories: { total: number; with_author: number; by_type: Record; }; } /** * A ranked title-to-outlet match with evidence, contact route, and plain-English * explanation. */ export interface PrTitleMatch { publication: PrPublication; score: number; matched_tags: string[]; best_contact_path?: Record | null; why: string[]; risks: string[]; evidence_links: PrLink[]; } /** * Title-scoped PR research workspace response. */ export interface PrTitleResearchResponse { generated_at: string; title: { id: string; name: string; slug?: string | null; short_description?: string | null; description?: string | null; genres?: string[]; platforms?: string[]; website_url?: string | null; steam_url?: string | null; itch_url?: string | null; demo_url?: string | null; video_url?: string | null; }; readiness: { score: number; strengths: string[]; gaps: string[]; }; publication_matches: PrTitleMatch[]; stories: PrStory[]; story_summary: { count: number; with_known_author: number; publication_count: number; }; suggested_next_steps: string[]; } /** * Structured draft fields returned alongside the formatted HTML email body. */ export interface PrOutreachDraft { subject?: string | null; opener?: string | null; body?: string | null; body_html?: string | null; key_points?: string[]; personalization_notes?: string[]; review_notes?: string[]; missing_context_warnings?: string[]; [key: string]: any; } /** * Review-only PR draft response. The API never sends email from this endpoint. */ export interface PrTitleDraftResponse { draft_status: "draft_only_not_sent" | "no_verified_email_found" | "no_publication_found" | string; emails_sent: boolean; publication?: PrPublication | null; person?: PrPerson | null; target: Record; recent_stories: PrStory[]; draft: PrOutreachDraft; body_html: string; review_notes: string[]; } /** * Response body returned after queueing PR verification jobs. */ export interface PrQueueVerificationResponse { queued: number; } /** * Response body returned after discovering, queueing, or fetching PR feeds. */ export interface PrFeedRefreshResponse { discovered: number; queued: number; fetched: number; } /** * SDK wrapper for the PR Directory API. * * The PR directory is read-friendly by default: public endpoints expose * searchable publications, people, feeds, stories, tags, and reporting metrics. * Authenticated title admins can request title-specific research and review-only * outreach drafts, and site admins can queue verification or feed refresh jobs. */ class PrDirectory { /** * Search gaming-focused PR publications, independent blogs, and podcasts. * * @example * ```ts * Glitch.api.PrDirectory.listPublications({ * q: "indie RPG", * has_email: true, * eligibility_status: "eligible", * sort: "-last_verified_at", * }); * ``` */ public static listPublications(params?: PrPublicationSearchParams): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.listPublications, {}, {}, params); } /** * Retrieve one PR publication profile with loaded people, contact points, * evidence links, and tags. */ public static viewPublication(publication_id: string, params?: Record): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.viewPublication, {}, { publication_id }, params); } /** * Search PR people and roles across all known publications. * * @example * ```ts * Glitch.api.PrDirectory.listPeople({ * q: "reviews editor", * has_email: true, * role_category: "editor", * }); * ``` */ public static listPeople(params?: PrPeopleSearchParams): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.listPeople, {}, {}, params); } /** * Retrieve one PR person profile with their outlet roles, profile links, * contact points, and metadata tags. */ public static viewPerson(person_id: string, params?: Record): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.viewPerson, {}, { person_id }, params); } /** * Search discovered RSS/Atom/JSON feeds across known publications. */ public static listFeeds(params?: PrFeedSearchParams): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.listFeeds, {}, {}, params); } /** * Retrieve one feed with freshness metadata and recent imported stories. * Pass `include_raw: true` to request the stored XML/RSS payload. */ public static viewFeed(feed_id: string, params?: PrFeedSearchParams): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.viewFeed, {}, { feed_id }, params); } /** * Search imported stories, reviews, guides, and episodes by outlet or byline. */ public static listStories(params?: PrStorySearchParams): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.listStories, {}, {}, params); } /** * Retrieve one imported story with feed, publication, and byline evidence. */ public static viewStory(story_id: string, params?: Record): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.viewStory, {}, { story_id }, params); } /** * List the normalized tag vocabulary used for PR search, filters, matching, * and reporting. */ public static listTags(params?: PrTagSearchParams): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.listTags, {}, {}, params); } /** * Get aggregate PR directory reporting metrics. Publication filters can be * supplied to scope the outlet portion of the report. */ public static report(params?: PrPublicationSearchParams): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.report, {}, {}, params); } /** * Match a registered game title to PR outlets. Requires an auth token for a * user who can administer the requested title. */ public static titleMatches(title_id: string, params?: PrTitleMatchParams): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.titleMatches, {}, { title_id }, params); } /** * Get a title-scoped PR research workspace with outlet matches, recent story * context, media kit readiness, and next steps. */ public static titleResearch(title_id: string, params?: PrTitleResearchParams): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.titleResearch, {}, { title_id }, params); } /** * Create a formatted, review-only PR email draft for a selected title target. * The backend returns HTML with paragraphs, bullets, and links but sends no email. */ public static titleDraft(title_id: string, data?: PrTitleDraftRequest, params?: Record): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.titleDraft, data || {}, { title_id }, params); } /** * Queue PR verification jobs. Requires a site-admin auth token. * * @example * ```ts * Glitch.api.PrDirectory.queueVerification({ due: true, limit: 250 }); * ``` */ public static queueVerification(data?: PrQueueVerificationRequest, params?: Record): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.queueVerification, data || {}, {}, params); } /** * Discover, queue, or synchronously refresh PR feeds. Requires a site-admin * auth token. */ public static refreshFeeds(data?: PrFeedRefreshRequest, params?: Record): AxiosPromise> { return Requests.processRoute(PrDirectoryRoutes.routes.refreshFeeds, data || {}, {}, params); } } export default PrDirectory;