├── .gitignore
├── .prettierrc
├── README.md
├── index.js
├── package.json
├── wrangler.toml
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /dist
3 | **/*.rs.bk
4 | Cargo.lock
5 | bin/
6 | pkg/
7 | wasm-pack.log
8 | worker/
9 | node_modules/
10 | .cargo-ok
11 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false,
4 | "trailingComma": "all",
5 | "tabWidth": 2,
6 | "printWidth": 80
7 | }
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Get the favicon from every URL by utilizing Cloudflare Workers
2 |
3 | ## Demo
4 |
5 | `https://favicon.splitbee.io/?url=workers.cloudflare.com`
6 |
7 |
8 |
9 |
10 |
11 |
12 | ## Deploy
13 |
14 | Fill out the missing parameters in `wrangler.toml` and run `wrangler publish` to upload your worker to cloudflare!
15 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | let cache = caches.default
2 |
3 | const svgFavicon = 'data:image/svg+xml,'
4 |
5 | const defaultIconSvg = ``
8 |
9 | async function handleRequest(request) {
10 | const init = {
11 | headers: {
12 | 'content-type': 'text/html;charset=UTF-8',
13 | },
14 | redirect: 'follow',
15 | }
16 | let requestURL = new URL(request.url)
17 |
18 | const url = requestURL.searchParams.get('url')
19 |
20 | const targetURL = new URL(url.startsWith('https') ? url : 'https://' + url)
21 |
22 | let favicon = ''
23 | const response = await fetch(targetURL.origin, init).catch(() => {
24 | console.log('failed')
25 | })
26 |
27 | let newResponse = new HTMLRewriter()
28 | .on('link[rel*="icon"]', {
29 | async element(element) {
30 | if (element.getAttribute('rel') === 'mask-icon' || favicon) return
31 | favicon = element.getAttribute('href')
32 | if (favicon.startsWith('/')) {
33 | const prefix = favicon.startsWith('//') ? 'https:' : targetURL.origin
34 | favicon = prefix + favicon
35 | } else if (!favicon.startsWith('http')) {
36 | favicon = targetURL.origin + '/' + favicon
37 | }
38 | },
39 | })
40 | .transform(response)
41 |
42 | await newResponse.text()
43 |
44 | if (!favicon) {
45 | const fav = await fetch(targetURL.origin + '/favicon.ico')
46 | if (fav.status === 200) {
47 | const resss = new Response(fav.body, { headers: fav.headers })
48 | resss.headers.set('Cache-Control', 'max-age=86400')
49 |
50 | return resss
51 | }
52 |
53 | const defaultIcon = new Response(defaultIconSvg, {
54 | headers: {
55 | 'content-type': 'image/svg+xml',
56 | },
57 | })
58 |
59 | defaultIcon.headers.set('Cache-Control', 'max-age=36000')
60 |
61 | return defaultIcon
62 | }
63 |
64 | const isRaw = requestURL.searchParams.get('raw')
65 |
66 | if (isRaw !== null) {
67 | const ic = new Response(favicon)
68 | ic.headers.set('Cache-Control', 'max-age=86400')
69 | return ic
70 | }
71 |
72 | let icon = await fetch(favicon)
73 |
74 | if (favicon.includes(svgFavicon)) {
75 | return new Response(decodeURI(favicon.split(svgFavicon)[1]), {
76 | headers: {
77 | 'content-type': 'image/svg+xml',
78 | },
79 | })
80 | }
81 |
82 | const ct = icon.headers.get('content-type')
83 |
84 | if (ct.includes('application') || ct.includes('text')) {
85 | icon = await fetch(`https://www.google.com/s2/favicons?domain=${url}`)
86 | }
87 |
88 | const iconRes = new Response(icon.body)
89 |
90 | iconRes.headers.set('Cache-Control', 'max-age=86400')
91 | iconRes.headers.set('Content-Type', icon.headers.get('content-type'))
92 |
93 | return iconRes
94 | }
95 |
96 | addEventListener('fetch', event => {
97 | return event.respondWith(handleRequest(event.request))
98 | })
99 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "favicon-worker",
3 | "version": "1.0.0",
4 | "description": "Get the favicon of a URL",
5 | "main": "index.js",
6 | "scripts": {
7 | "format": "prettier --write '**/*.{js,css,json,md}'"
8 | },
9 | "author": "Tobias Lins ",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "prettier": "^1.18.2"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/wrangler.toml:
--------------------------------------------------------------------------------
1 | name = "favicon"
2 |
3 | type = "javascript"
4 | account_id = ""
5 | zone_id = ""
6 |
7 | workers_dev = true
8 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | prettier@^1.18.2:
6 | version "1.19.1"
7 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
8 | integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
9 |
--------------------------------------------------------------------------------