import browser from 'webextension-polyfill'; const parseItemImage = (item: Element) => { const images = Array.from(item.getElementsByTagName('media:content')) .filter((content) => (content.getAttribute('type') || '').startsWith('image/') || content.getAttribute('medium') === 'image' ) if (!images.length) return const { url } = images.reduce((maxImage, content) => { const width = parseFloat(content.getAttribute('width') || '0') if (width > maxImage.width) { maxImage.url = content.getAttribute('url') || '' maxImage.width = width } return maxImage }, { width: parseFloat(images[0].getAttribute('width') || '0'), url: images[0].getAttribute('url'), }) return { url: url } } const pubDateToInterval = (item: Element) => { const dateStr = getNodeContent(item, 'pubDate') if (!dateStr?.length) return // @ts-ignore let interval = ((new Date()) - (new Date(dateStr))) / 1000 let unit = 'seconds' if (interval >= 60) { interval /= 60 unit = 'minutes' } if (unit == 'minutes' && interval >= 60) { interval /= 60 unit = 'hours' } if (unit == 'hours' && interval >= 24) { interval /= 24 unit = 'days' } if (unit == 'days' && interval >= 30) { interval /= 30 unit = 'months' } return `${interval.toFixed(0)} ${unit}` } const getNodeContent = (parent: Element, tagName: string) => // @ts-ignore parent.getElementsByTagName(tagName)[0]?.firstChild?.wholeText const parseFeed = (channel: Element) => { const imageElement = channel.getElementsByTagName('image')[0] const itemTime = (item: {pubDate: string}) => { const dateStr = item.pubDate if (!dateStr?.length) return 0 return (new Date(dateStr)).getTime() } return { feedData: { title: getNodeContent(channel, 'title'), description: getNodeContent(channel, 'description'), feedUrl: window.location.href, homeUrl: getNodeContent(channel, 'link'), image: imageElement ? { title: getNodeContent(imageElement, 'title'), imageUrl: getNodeContent(imageElement, 'url'), targetUrl: getNodeContent(imageElement, 'link'), } : null, items: Array.from(channel.getElementsByTagName('item')).map((item) => { return { title: getNodeContent(item, 'title'), description: getNodeContent(item, 'description'), url: getNodeContent(item, 'link'), image: parseItemImage(item), pubDate: getNodeContent(item, 'pubDate'), age: pubDateToInterval(item), categories: Array.from(item.getElementsByTagName('category')).map((cat) => cat.firstChild?.textContent ), } }).sort((a, b) => itemTime(b) - itemTime(a)) } } } const getFeedRoot = () => { const xmlDoc = document.documentElement if (xmlDoc.tagName === 'rss') return xmlDoc // Chrome-based browsers may wrap the XML into an HTML view const webkitSource = document.getElementById('webkit-xml-viewer-source-xml') if (webkitSource) return webkitSource // For some ugly reasons, some RSS feeds are rendered inside of a
 in a normal HTML DOM
  const preElements = document.getElementsByTagName('pre')
  if (preElements.length !== 1)
    return

  const text = preElements[0].innerText
  const parser = new DOMParser()
  let innerXmlDoc = null

  try {
    // @ts-ignore
    innerXmlDoc = parser.parseFromString(text, 'text/xml')
  } catch (e) { }

  if (!innerXmlDoc)
    return

  // @ts-ignore
  const root = innerXmlDoc.documentElement
  if (root.tagName === 'rss')
    return root
}

browser.runtime.onMessage.addListener(async (message: {type: Object}) => {
  if (message.type !== 'renderFeed')
    return

  const xmlDoc = getFeedRoot()
  if (!xmlDoc)
    // Not an RSS feed
    return

  const channel = xmlDoc.getElementsByTagName('channel')[0]
  if (!channel)
    return

  browser.storage.local.set(parseFeed(channel))
  window.location.href = browser.runtime.getURL('viewer/index.html')
})