packages feed

link-canonical-0.1.0.0: src/Link/Canonical/Rules/Reddit.hs

-- | Reddit URL normalization rule
--
-- == Canonical Form
--
-- @
-- https://www.reddit.com/r/{subreddit}/comments/{id}/{slug}/
-- https://www.reddit.com/user/{username}/
-- @
--
-- == Handled Variants
--
-- * @old.reddit.com@ → @www.reddit.com@
-- * @np.reddit.com@ → @www.reddit.com@
-- * @reddit.com@ → @www.reddit.com@
--
-- == Stripped Parameters
--
-- @ref@, @ref_source@
--
-- Note: @utm_*@ parameters are handled by the global tracking parameter stripping.
module Link.Canonical.Rules.Reddit
  ( redditRule,
    isRedditUrl,
    redditStrippedParams,
  )
where

import Data.Text qualified as T
import Link.Canonical.Prelude
import Link.Canonical.Rules.Types (DomainRule, domainRule, getHost)
import Text.URI qualified as URI

-- | Reddit domain rule
redditRule :: DomainRule
redditRule = domainRule "reddit" isRedditUrl normalizeReddit

-- | Parameters to strip from Reddit URLs
redditStrippedParams :: [Text]
redditStrippedParams =
  [ "ref", -- Reddit referral tracking
    "ref_source" -- Reddit referral source
  ]

-- | Reddit hostnames we recognize
redditHosts :: [Text]
redditHosts =
  [ "reddit.com",
    "www.reddit.com",
    "old.reddit.com",
    "np.reddit.com"
  ]

-- | Check if a URI is a Reddit URL
isRedditUrl :: URI -> Bool
isRedditUrl uri =
  case getHost uri of
    Nothing -> False
    Just host -> T.toLower host `elem` redditHosts

-- | Normalize a Reddit URL to canonical form
normalizeReddit :: URI -> URI
normalizeReddit uri =
  let -- Step 1: Normalize host to www.reddit.com
      withHost = normalizeHost uri
      -- Step 2: Strip Reddit-specific tracking parameters
      withQuery = stripRedditParams withHost
   in withQuery

-- | Normalize host to www.reddit.com
normalizeHost :: URI -> URI
normalizeHost uri =
  case URI.uriAuthority uri of
    Left _ -> uri
    Right auth ->
      let hostText = T.toLower (URI.unRText (URI.authHost auth))
       in if hostText `elem` ["reddit.com", "old.reddit.com", "np.reddit.com"]
            then case URI.mkHost "www.reddit.com" of
              Nothing -> uri
              Just newHost ->
                let newAuth = auth {URI.authHost = newHost}
                 in uri {URI.uriAuthority = Right newAuth}
            else uri

-- | Strip Reddit-specific tracking parameters
stripRedditParams :: URI -> URI
stripRedditParams uri =
  let params = URI.uriQuery uri
      filtered = filter (not . isRedditTrackingParam) params
   in uri {URI.uriQuery = filtered}

-- | Check if a query parameter is a Reddit tracking parameter
isRedditTrackingParam :: URI.QueryParam -> Bool
isRedditTrackingParam (URI.QueryFlag k) =
  URI.unRText k `elem` redditStrippedParams
isRedditTrackingParam (URI.QueryParam k _) =
  URI.unRText k `elem` redditStrippedParams