๐Ÿ“ฆ hyoban / kodoku

๐Ÿ“„ utils.ts ยท 96 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96import { clsx } from "clsx"
import { twMerge } from "tailwind-merge"

import type { FeedInfo } from "./notion"
import type { ClassValue } from "clsx"
import type Parser from "rss-parser"

const ICON_MAP = [
  ["twitter.com", "i-simple-icons-twitter", "Twitter"],
  ["github.com", "i-simple-icons-github", "GitHub"],
  ["youtube.com", "i-simple-icons-youtube", "YouTube"],
  ["bilibili.com", "i-simple-icons-bilibili", "Bilibili"],
  ["discord.com", "i-simple-icons-discord", "Discord"],
  ["t.me", "i-simple-icons-telegram", "Telegram"],
] as const

export function getPlatformIcon(url: string): string | undefined {
  const icon = ICON_MAP.find(([domain]) => url.includes(domain))
  return icon?.[1]
}

export function getPlatformName(url: string): string | undefined {
  const name = ICON_MAP.find(([domain]) => url.includes(domain))
  return name?.[2]
}

export function notNullish<T>(v: T | null | undefined): v is NonNullable<T> {
  return v != null
}

export function extractFirstImageUrl(html: string): string | undefined {
  const img = html.match(/<img.*?src="(.*?)"/)
  if (!img) return
  return img[1]
}

export function isExternalLink(url?: string): boolean {
  if (!url) return false
  return url.startsWith("http") || url.startsWith("//")
}

export function joinFeedItemUrl(feedUrl: string, itemUrl?: string): string {
  if (!itemUrl) return feedUrl
  if (isExternalLink(itemUrl)) return itemUrl
  if (feedUrl.endsWith("/")) feedUrl = feedUrl.slice(0, -1)
  if (itemUrl.startsWith("/")) itemUrl = itemUrl.slice(1)
  return feedUrl + "/" + itemUrl
}

export function isFeedItemValid(
  item: Parser.Item & { id?: string },
  feedInfo: FeedInfo,
): boolean {
  if (
    feedInfo.type === "GitHub" &&
    feedInfo.feedUrl?.endsWith(".atom") &&
    ["PushEvent"].some((i) => {
      return item.id?.includes(i)
    })
  )
    return false

  if (!item.link) return false
  if (item.link.includes("[object Object]")) return false
  if (!item.title) return false
  if (!item.isoDate) return false
  if (item.title === "No title") return false
  return true
}

export function delay(ms: number) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms)
  })
}

export function timeout<T>(
  ms: number,
  promise: Promise<T>,
): Promise<Awaited<T>> {
  return Promise.race([
    delay(ms).then(() => {
      throw new Error("timeout")
    }),
    promise,
  ])
}

export function capitalize(text: string): string {
  return text.charAt(0).toUpperCase() + text.slice(1)
}

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}