packages feed

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

-- | Instagram URL normalization rule
--
-- == Canonical Forms
--
-- @
-- https://www.instagram.com/p/{POST_ID}/
-- https://www.instagram.com/reel/{REEL_ID}/
-- https://www.instagram.com/{username}/
-- @
--
-- == Handled Variants
--
-- * @instagram.com@ → @www.instagram.com@
--
-- == Stripped Parameters
--
-- @igshid@ (Instagram share ID)
--
-- Note: @utm_*@ parameters are handled by the global tracking parameter stripping.
module Link.Canonical.Rules.Instagram
  ( instagramRule,
    isInstagramUrl,
    instagramStrippedParams,
  )
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

-- | Instagram domain rule
instagramRule :: DomainRule
instagramRule = domainRule "instagram" isInstagramUrl normalizeInstagram

-- | Parameters to strip from Instagram URLs
instagramStrippedParams :: [Text]
instagramStrippedParams =
  [ "igshid" -- Instagram share ID
  ]

-- | Instagram hostnames we recognize
instagramHosts :: [Text]
instagramHosts =
  [ "instagram.com",
    "www.instagram.com"
  ]

-- | Check if a URI is an Instagram URL
isInstagramUrl :: URI -> Bool
isInstagramUrl uri =
  case getHost uri of
    Nothing -> False
    Just host -> T.toLower host `elem` instagramHosts

-- | Normalize an Instagram URL to canonical form
normalizeInstagram :: URI -> URI
normalizeInstagram uri =
  let -- Step 1: Normalize host to www.instagram.com
      withHost = normalizeHost uri
      -- Step 2: Strip Instagram-specific tracking parameters
      withQuery = stripInstagramParams withHost
   in withQuery

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

-- | Strip Instagram-specific tracking parameters
stripInstagramParams :: URI -> URI
stripInstagramParams uri =
  let params = URI.uriQuery uri
      filtered = filter (not . isInstagramTrackingParam) params
   in uri {URI.uriQuery = filtered}

-- | Check if a query parameter is an Instagram tracking parameter
isInstagramTrackingParam :: URI.QueryParam -> Bool
isInstagramTrackingParam (URI.QueryFlag k) =
  URI.unRText k `elem` instagramStrippedParams
isInstagramTrackingParam (URI.QueryParam k _) =
  URI.unRText k `elem` instagramStrippedParams