├── .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 = ` 6 | 7 | ` 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 | --------------------------------------------------------------------------------