{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "discord",
  "type": "registry:component",
  "title": "Discord Presence",
  "description": "A component for displaying Discord presence.",
  "files": [
    {
      "path": "registry/v3cn/discord/discord.tsx",
      "content": "\"use client\";\n\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\n\nimport React, { useEffect, useState } from \"react\";\n\nimport Image from \"next/image\";\nimport { cn } from \"@/lib/utils\";\n\ntype TDiscord = {\n  userId: string;\n  userName: string;\n  activityDetailClass?: string;\n  activityDescriptionClass?: string;\n  activityImageClassName?: string;\n  progressBarClassName?: string;\n  localTimeClass?: string;\n};\n\ntype TActivityImage = {\n  largeActivityImage: string;\n  smallActivityImage: string;\n  isActivity: boolean;\n  isSpotifyPlaying: boolean;\n};\n\n/**\n * visual studio code activity\n */\ntype TActivity = {\n  application_id: string;\n  assets: {\n    large_image: string;\n    large_text: string;\n    small_image: string;\n    small_text: string;\n  };\n  details: string;\n  id: string;\n  name: string;\n  state: string;\n};\n\ntype TDiscordUser = {\n  details: string;\n  discord_status: string;\n  username: string;\n  avatar: string;\n  id: string;\n};\n\ntype TSpotifyData = {\n  album: string;\n  album_art_url: string;\n  artist: string;\n  song: string;\n  timestamps: {\n    start: number;\n    end: number;\n  };\n  track_id: string;\n};\n\ntype TActivityDetail = {\n  detail: string;\n  description: string;\n};\n\nexport const DiscordComponent: React.FC<TDiscord> = ({\n  userId,\n  userName,\n  activityDescriptionClass,\n  activityImageClassName,\n  activityDetailClass,\n  progressBarClassName,\n  localTimeClass,\n}: TDiscord) => {\n  const [isLoading, setIsLoading] = useState<boolean>(true);\n  const [activityImage, setActivityImage] = useState<TActivityImage>({\n    largeActivityImage: \"\",\n    smallActivityImage: \"\",\n    isActivity: true,\n    isSpotifyPlaying: false,\n  });\n  const [activityDetais, setActivityDetails] = useState<TActivityDetail>({\n    detail: \"\",\n    description: \"\",\n  });\n\n  const [progress, setProgress] = useState<number>(0);\n  function musicProgress(spotify: Pick<TSpotifyData, \"timestamps\">) {\n    const totalTime = spotify.timestamps.end - spotify.timestamps.start;\n    const progress =\n      100 - (100 * (spotify.timestamps.end - new Date().getTime())) / totalTime;\n    setProgress(progress);\n  }\n   useDiscord({\n    userId,\n    setIsLoading,\n    setActivityImage,\n    musicProgress,\n    setActivityDetails,\n  });\n\n  return (\n    <div className=\"my-6\">\n      {isLoading ? (\n        <DiscordSkeleton />\n      ) : (\n        <div className=\"flex gap-2\">\n          <ImageCont\n            activityImage={activityImage}\n            activityImageClassName={activityImageClassName}\n          />\n          <div className=\"flex flex-col justify-center items-start gap-2\">\n            <AcitvityInfo\n              activityDetais={activityDetais}\n              activityDescriptionClass={activityDescriptionClass}\n              activityDetailClass={activityDetailClass}\n            />\n            {activityImage.isSpotifyPlaying ? (\n              <Progress\n                value={progress}\n                className=\" w-[100px] md:w-[200px] h-[3px] text-red-500 \"\n                progressBarClassName={progressBarClassName}\n              />\n            ) : null}\n            <div className=\"flex flex-row flex-nowrap gap-2\">\n              <span className=\"capitalize\">{userName}</span> •{\" \"}\n              <LocalTime localTimeClass={localTimeClass} />\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n\nconst AcitvityInfo = ({\n  activityDetais,\n  activityDetailClass,\n  activityDescriptionClass,\n}: {\n  activityDetais: TActivityDetail;\n  activityDetailClass?: string;\n  activityDescriptionClass?: string;\n}) => {\n  return (\n    <div className=\"flex flex-col gap-1\">\n      <span className={cn(\"text-base font-semibold mb-1\", activityDetailClass)}>\n        {activityDetais.detail}\n      </span>\n      <span className={cn(\"text-sm\", activityDescriptionClass)}>\n        {activityDetais.description}\n      </span>\n    </div>\n  );\n};\n\ntype TUseDiscord = {\n  userId: string;\n  setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;\n  setActivityImage: React.Dispatch<React.SetStateAction<TActivityImage>>;\n  musicProgress: (spotify: Pick<TSpotifyData, \"timestamps\">) => void;\n  setActivityDetails: React.Dispatch<React.SetStateAction<TActivityDetail>>;\n};\n\nconst useDiscord = ({\n  userId,\n  setIsLoading,\n  setActivityImage,\n  musicProgress,\n  setActivityDetails,\n}: TUseDiscord) => {\n  useEffect(() => {\n    let lanyard: WebSocket | null = null;\n    const connect = () => {\n      lanyard = new WebSocket(`wss://api.lanyard.rest/socket`);\n      if (!lanyard) {\n        return;\n      }\n      lanyard.onmessage = (event) => {\n        const json = JSON.parse(event.data);\n        const opCode = json.op;\n        const data: {\n          discord_user: TDiscordUser;\n          discord_status: string;\n          heartbeat_interval: number;\n          activities: TActivity[];\n          listening_to_spotify: boolean;\n          spotify: TSpotifyData;\n        } = json.d;\n\n        if (opCode === 1) {\n          let heartbeatInterval: NodeJS.Timer | number =\n            data.heartbeat_interval;\n          lanyard?.send(\n            JSON.stringify({\n              op: 2,\n              d: { subscribe_to_id: userId },\n            })\n          );\n\n          if (heartbeatInterval) {\n            heartbeatInterval = setInterval(() => {\n              lanyard?.send(\n                JSON.stringify({\n                  op: 3,\n                })\n              );\n            }, heartbeatInterval);\n          }\n        } else if (opCode === 0) {\n          if (data.listening_to_spotify) {\n            const spotifyData = data.spotify;\n            setActivityImage({\n              largeActivityImage: spotifyData.album_art_url,\n              smallActivityImage: spotifyData.album_art_url,\n              isActivity: true,\n              isSpotifyPlaying: true,\n            });\n            musicProgress(spotifyData);\n            setActivityDetails({\n              detail: spotifyData.artist,\n              description: spotifyData.song,\n            });\n            setIsLoading(false);\n          } else if (data.activities && data.activities[0]) {\n            const activity = data.activities[0];\n            const largeImage = activity.assets?.large_image.includes(\"http\")\n              ? \"https://\" +\n                activity.assets.large_image.replace(\n                  /^mp:external\\/[^\\/]+\\/https\\//,\n                  \"\"\n                )\n              : `https://cdn.discordapp.com/app-assets/${activity.application_id}/${activity.assets.large_image}.webp?size=512`;\n            const smallImage = activity.assets?.small_image.includes(\"http\")\n              ? \"https://\" +\n                activity.assets.small_image.replace(\n                  /^mp:external\\/[^\\/]+\\/https\\//,\n                  \"\"\n                )\n              : `https://cdn.discordapp.com/app-assets/${activity.application_id}/${activity.assets.small_image}.webp?size=512`;\n            setActivityImage({\n              largeActivityImage: largeImage,\n              smallActivityImage: smallImage,\n              isActivity: true,\n              isSpotifyPlaying: false,\n            });\n            setActivityDetails({\n              detail: activity.details,\n              description: activity.state,\n            });\n            setIsLoading(false);\n          } else {\n            const user = data;\n            const largeImage = `https://cdn.discordapp.com/avatars/${user.discord_user.id}/${user.discord_user?.avatar}.png?size=512`;\n\n            setActivityImage({\n              largeActivityImage: largeImage,\n              smallActivityImage: largeImage,\n              isActivity: false,\n              isSpotifyPlaying: false,\n            });\n            let status =\n              user.discord_status.charAt(0).toUpperCase() +\n              user.discord_status.slice(1);\n            status = status === \"Dnd\" ? \"Do Not Disturb\" : status;\n\n            setActivityDetails({\n              detail: user.discord_user.username,\n              description: status,\n            });\n\n            setIsLoading(false);\n          }\n        }\n      };\n    };\n    connect();\n  });\n};\n\ntype TImageCont = {\n  activityImageClassName?: string;\n  activityImage: TActivityImage;\n};\n\nconst ImageCont: React.FC<TImageCont> = ({\n  activityImageClassName,\n  activityImage,\n}: TImageCont) => {\n  return (\n    <div\n      className={cn(\n        \"relative mt-1  max-w-[100px] h-[100px] \",\n        activityImageClassName\n      )}\n    >\n      {activityImage.largeActivityImage == \"\" ? (\n        <div className=\"animate-pulse bg-gray-500 rounded-2xl\"></div>\n      ) : (\n        <Image\n          src={activityImage.largeActivityImage}\n          className={cn(\n            \"rounded-2xl relative select-none\",\n            `${\n              activityImage.isSpotifyPlaying &&\n              \"animate-[spin_40s_linear_infinite] rounded-full\"\n            }`\n          )}\n          width={100}\n          height={100}\n          alt=\"Activity Image\"\n        />\n      )}\n\n      {!activityImage.isSpotifyPlaying && activityImage.isActivity && (\n        <Image\n          src={activityImage.smallActivityImage}\n          height={40}\n          width={40}\n          className={cn(\n            \"rounded-full bottom-[-10px] right-0 select-none absolute p-2 bg-black/90\",\n            {}\n          )}\n          alt=\"Activity Image\"\n        />\n      )}\n    </div>\n  );\n};\n\ntype TLocalTime = {\n  localTimeClass?: string;\n};\n\nconst LocalTime: React.FC<TLocalTime> = ({ localTimeClass }: TLocalTime) => {\n  const [localTime, setLocalTime] = useState<string>(\n    new Date().toLocaleTimeString()\n  );\n\n  const setLocalTimeState = () => {\n    setLocalTime(new Date().toLocaleTimeString());\n  };\n\n  useEffect(() => {\n    if (typeof window === \"undefined\") return;\n\n    const intervalId = window.setInterval(setLocalTimeState, 1000); // Update local time every second\n\n    return () => clearInterval(intervalId);\n  }, []);\n\n  return <span className={localTimeClass}>{localTime}</span>;\n};\n\ntype progressProps = {\n  progressBarClassName?: string;\n  value?: number;\n};\n\nconst Progress = React.forwardRef<\n  React.ElementRef<typeof ProgressPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> & progressProps\n>(({ className, progressBarClassName, value, ...props }, ref) => (\n  <ProgressPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative h-4 w-full overflow-hidden rounded-full bg-secondary\",\n      className\n    )}\n    {...props}\n  >\n    <ProgressPrimitive.Indicator\n      className={cn(\n        \"h-full w-full flex-1 bg-purple-400 transition-all \",\n        progressBarClassName\n      )}\n      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n    />\n  </ProgressPrimitive.Root>\n));\n\nProgress.displayName = \"Progress\";\n\nexport default Progress;\n\n/**\n *\n * @description Clean skeleton component for discordPresence\n */\nconst DiscordSkeleton: React.FC = () => {\n  return (\n    <div className=\"flex justify-start items-center gap-4\">\n      <div className=\"w-24  h-24 bg-gray-700 animate-pulse rounded-2xl\"></div>\n      <div className=\"flex flex-col  gap-3 items-start\">\n        <div className=\"w-32  h-5 bg-gray-700 animate-pulse rounded-2xl\"></div>\n        <div className=\"w-36  h-5 bg-gray-700 animate-pulse rounded-2xl\"></div>\n        <div className=\"flex justify-start items-center gap-3\">\n          <div className=\"w-16  h-5 bg-gray-700 animate-pulse rounded-2xl\"></div>\n          <div className=\"w-16  h-5 bg-gray-700 animate-pulse rounded-2xl\"></div>\n        </div>\n      </div>\n    </div>\n  );\n};\n",
      "type": "registry:component",
      "target": "components/discord.tsx"
    }
  ]
}