-
-
Save izuolan/b8fc562894fabd127590b8a0f0a615c4 to your computer and use it in GitHub Desktop.
| // Your domain name | |
| const MY_DOMAIN = 'note.example.com' | |
| // Website language | |
| const LANG = 'en' | |
| // Favicon url | |
| const FAVICON_URL = 'https://example.com/favicon.ico' | |
| // Your config page link | |
| const CONFIG_URL = 'https://www.craft.do/s/XXXXXXXXX' | |
| // Your Telegram Token and ID | |
| const TG_TOKEN = "" | |
| const TG_CHAT_ID = "" | |
| // END | |
| // Default function | |
| addEventListener('fetch', event => { | |
| event.respondWith(fetchAndApply(event.request)) | |
| }) | |
| // Fetch url | |
| async function fetchAndApply(request) { | |
| let url = new URL(request.url) | |
| // Set upstream domain | |
| url.host = 'www.craft.do' | |
| let pathname = url.pathname | |
| let response = null | |
| const config_obj = await configParser() | |
| // Automatically generate robots.txt and sitemap.xml | |
| if (pathname === '/robots.txt') { | |
| return new Response('Sitemap: https://' + MY_DOMAIN + '/sitemap.xml') | |
| } | |
| if (pathname === '/sitemap.xml') { | |
| let sitemap = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' | |
| for(var path in config_obj){ | |
| sitemap += '<url><loc>https://' + MY_DOMAIN + '/' + path + '</loc></url>' | |
| } | |
| sitemap += '</urlset>' | |
| response = new Response(sitemap) | |
| response.headers.set('content-type', 'application/xml') | |
| return response | |
| } | |
| if (pathname === '/favicon.svg') { | |
| response = new Response('<svg xmlns="http://www.w3.org/2000/svg" width="100pt" height="100pt" viewBox="0 0 100 100"><g fill="blue" transform="translate(0.000000,100) scale(0.080000,-0.080000)"><path d="M762 1203 c-6 -15 -13 -46 -17 -68 -4 -22 -13 -49 -20 -61 -15 -23 -122 -69 -257 -109 -49 -14 -88 -28 -88 -29 0 -2 33 -20 73 -40 49 -24 87 -36 115 -36 28 0 42 -4 42 -13 0 -34 -295 -517 -390 -639 -40 -52 -4 -28 86 56 49 46 105 109 124 141 19 31 64 98 100 148 77 108 125 186 173 283 20 39 46 78 59 86 13 8 69 34 126 58 107 45 118 57 110 111 -3 21 -10 25 -78 34 l-75 10 -5 45 c-5 42 -7 45 -36 48 -26 3 -33 -1 -42 -25z"/><path d="M754 616 c-40 -19 -88 -39 -108 -46 -43 -14 -45 -30 -7 -72 25 -28 33 -31 80 -30 39 1 54 -3 58 -15 7 -18 -30 -140 -58 -192 -36 -67 6 -93 135 -84 l86 6 0 -26 c0 -14 -4 -37 -10 -51 -5 -14 -8 -26 -6 -26 7 0 110 68 129 85 11 10 17 30 17 60 0 62 -22 70 -150 57 -52 -5 -98 -6 -103 -2 -4 3 3 31 16 61 13 30 32 78 42 108 10 30 28 70 41 89 26 38 30 63 14 93 -17 31 -91 25 -176 -15z"/></g></svg>') | |
| response.headers.set('content-type', 'image/svg+xml') | |
| return response | |
| } | |
| // Default path | |
| if (pathname === '/') { | |
| url.pathname = '/s/' + config_obj['index'].slice(23) | |
| } | |
| // Prohibit other Craft.do share pages | |
| else if (pathname.startsWith('/s/')) { | |
| url.pathname = "/404" | |
| } | |
| // Proxy index blocks pages | |
| else if (pathname.startsWith('/b/')) { | |
| url.pathname = '/s/' + config_obj['index'].slice(23) + pathname | |
| } | |
| // TODO: There is an unresolved issue here | |
| // External pages url is troublesome to deal with. | |
| else if (pathname.includes('/x/')) { | |
| // url.pathname = '/s/' + config_obj['index'].slice(23) + pathname | |
| return Response.redirect('https://' + MY_DOMAIN, 301) | |
| } | |
| // Proxy js | |
| else if (pathname.startsWith('/share/') && pathname.endsWith('.js')) { | |
| response = await fetch(url) | |
| let body = await response.text() | |
| // replace all js files domain | |
| response = new Response(body.replace(/www.craft.do/g, MY_DOMAIN), response) | |
| response.headers.set('Content-Type', 'application/x-javascript') | |
| } | |
| // Proxy images | |
| else if (pathname.startsWith('/img/')) { | |
| // Proxy api.craft.do, pages preview api | |
| // This code can proxy "MY_DOMAIN/img/<pathname>" --> "api.craft.do/render/preview/<slug>" | |
| url.host = 'api.craft.do' | |
| let path_name = pathname.slice(5) | |
| let img_slug = config_obj[path_name].slice(23) | |
| url.pathname = pathname.replace(pathname, '/render/preview/' + img_slug) | |
| // Cache images | |
| const cacheImage = `https://${url.host}${url.pathname}` | |
| let response = await fetch(url.href, { | |
| cf: { | |
| // Always cache this fetch regardless of content type | |
| // for a max of 86400 seconds before revalidating the resource | |
| cacheTtl: 86400, | |
| cacheEverything: true, | |
| //Enterprise only feature, see Cache API for other plans | |
| cacheKey: cacheImage, | |
| }, | |
| }) | |
| // Reconstruct the Response object to make its headers mutable. | |
| response = new Response(response.body, response) | |
| // Set cache control headers to cache on browser for 25 minutes | |
| response.headers.set("Cache-Control", "max-age=1500") | |
| return response | |
| } | |
| // Disable Craft log. | |
| else if (pathname.startsWith('/api/log/')) { | |
| return new Response('Disable loging.') | |
| } | |
| // Proxy comment API. | |
| else if (pathname.startsWith('/api/') && (pathname.includes('submitAnon'))) { | |
| const init = { | |
| body: request.body, | |
| method: 'PUT', | |
| headers: { | |
| "content-type": "application/json;charset=UTF-8", | |
| }, | |
| } | |
| const response = await fetch(url.href, init) | |
| const resp_json = await response.json() | |
| const resp_str = JSON.stringify(resp_json) | |
| // Telegram notify | |
| const comment_message = resp_json.comments[0].content | |
| const craft_slug = pathname.split("/")[5] | |
| const craft_url = 'https://www.craft.do/s/' + craft_slug | |
| const comment_slug = findJsonKey(config_obj, craft_url) | |
| const tg_message = 'Comment URL:\n' | |
| + 'https://' + MY_DOMAIN + '/' + comment_slug | |
| + '\n\n' | |
| + 'Comment Message:\n' + comment_message | |
| await sendToTelegram(tg_message) | |
| return new Response(resp_str) | |
| } | |
| else { | |
| try { | |
| let urlIndexSlug = null | |
| if (pathname.startsWith('/en/') || pathname.startsWith('/p/') || pathname.startsWith('/page/')) { | |
| urlIndexSlug = pathname.split("/")[1] + '/' + pathname.split("/")[2] | |
| } else { | |
| urlIndexSlug = pathname.split("/")[1] | |
| } | |
| let configPath = config_obj[urlIndexSlug].slice(23) | |
| url.pathname = '/s/' + configPath | |
| console.log(url.pathname) | |
| if (typeof(configPath) == "undefined") { throw new Error('404 not found: ' + configPath) } | |
| } catch (error) { | |
| if (pathname.startsWith('/api/') || pathname.endsWith('.css') || pathname.endsWith('.webmanifest') || pathname.endsWith('.svg')) { | |
| // nothing | |
| } else { | |
| url.pathname = '/404' | |
| // return new Response(error.message) | |
| } | |
| } | |
| } | |
| class AttributeRewriter { | |
| element(element) { | |
| if (element.getAttribute('property') === 'og:url') { | |
| element.setAttribute('content', 'https://' + MY_DOMAIN + pathname) | |
| } | |
| if (element.getAttribute('property') === 'og:image') { | |
| if (pathname === '/') { pathname = '/index' } | |
| element.setAttribute('content', 'https://' + MY_DOMAIN + '/img' + pathname) | |
| } | |
| if (element.getAttribute('name') === 'luki:api-endpoint') { | |
| element.setAttribute('content', 'https://' + MY_DOMAIN + '/api/') | |
| } | |
| if (element.getAttribute('lang') === 'en') { | |
| element.setAttribute('lang', LANG) | |
| } | |
| if (element.getAttribute('rel') === 'icon') { | |
| element.setAttribute('href', FAVICON_URL) | |
| } | |
| if (element.getAttribute('rel') === 'apple-touch-icon') { | |
| element.setAttribute('href', FAVICON_URL) | |
| } | |
| } | |
| } | |
| class RemoveElement { | |
| element(element) { | |
| element.remove() | |
| } | |
| } | |
| async function rewriteHTML(res) { | |
| res.headers.delete("Content-Security-Policy") | |
| return new HTMLRewriter() | |
| .on('body', new BodyRewriter()) | |
| .on('head', new HeadRewriter()) | |
| .on('html', new AttributeRewriter()) | |
| .on('meta', new AttributeRewriter()) | |
| .on('link', new AttributeRewriter()) | |
| .on('meta[name="robots"]', new RemoveElement()) // SEO | |
| .on('head>style', new RemoveElement()) // Remove fonts | |
| .on('script[src="https://www.craft.do/assets/js/analytics2.js"]', new RemoveElement()) // Delete analytics js | |
| .transform(res) | |
| } | |
| let method = request.method | |
| let request_headers = request.headers | |
| let new_request_headers = new Headers(request_headers) | |
| new_request_headers.set('Host', url.hostname) | |
| new_request_headers.set('Referer', url.hostname) | |
| let original_response = await fetch(url.href, { | |
| method: method, | |
| headers: new_request_headers | |
| }) | |
| let response_headers = original_response.headers | |
| let new_response_headers = new Headers(response_headers) | |
| let status = original_response.status | |
| response = new Response(original_response.body, { | |
| status, | |
| headers: new_response_headers | |
| }) | |
| // If you want change anything in response. | |
| let text = await response.text() | |
| // Return modified response. | |
| let modified_response = new Response(text, { | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers: response.headers | |
| }) | |
| if (pathname.startsWith('/share/static/js/') && (pathname.includes('codehighlight'))) { | |
| return modified_response | |
| } else { | |
| return rewriteHTML(modified_response) | |
| } | |
| } | |
| async function configParser() { | |
| // Delete string "https://www.craft.do/s/" | |
| let config_slug = CONFIG_URL.slice(23) | |
| const api_url = 'https://www.craft.do/api/share/' + config_slug | |
| const init = { | |
| headers: { | |
| "content-type": "application/json;charset=UTF-8", | |
| }, | |
| } | |
| const config_response = await fetch(api_url, init) // Get www.craft.do/api/share/<slug> content. | |
| const response_json = await config_response.json() // Convert the content to json format (string). | |
| const content_json = response_json.blocks[1].content // Get the json data of the first block in the body. | |
| const content_str = JSON.stringify(content_json) // Convert json to string. | |
| // Handle escape characters. | |
| const config_json = content_str.replace(/\\t/g, '').replace(/\\n/g, '').replace(/\\/g, '').replace('"{', '{').replace('}"', '}') | |
| let config_obj = JSON.parse(config_json) | |
| return config_obj | |
| } | |
| async function sendToTelegram(message) { | |
| const tgUrl = "https://api.telegram.org/bot" + TG_TOKEN + "/sendMessage" | |
| const init = { | |
| method: 'POST', | |
| headers: { | |
| "content-type": "application/json;charset=UTF-8", | |
| }, | |
| body: JSON.stringify({ | |
| "chat_id": TG_CHAT_ID, | |
| "text": message | |
| }) | |
| } | |
| await fetch(tgUrl, init) | |
| // const response = await fetch(tgUrl, init) | |
| // const resp_text = await response.text() | |
| // return new Response(resp_text) | |
| } | |
| function findJsonKey(obj, value, compare = (a, b) => a === b) { | |
| return Object.keys(obj).find(k => compare(obj[k], value)) | |
| } | |
| class BodyRewriter { | |
| element(element) { | |
| // Append your html | |
| element.append(` | |
| `, { | |
| html: true | |
| }) | |
| } | |
| } | |
| class HeadRewriter { | |
| element(element) { | |
| element.append(` | |
| <style> | |
| /* Hide the Craft "Login in" button in comment board. */ | |
| .sc-CtfFt { | |
| visibility: hidden; | |
| } | |
| .hGGlzy { | |
| visibility: hidden; | |
| } | |
| </style> | |
| `, { | |
| html: true | |
| }) | |
| } | |
| } |
Thanks a lot !
I'm ok with points 1 and 2.
Point 3 has direct relation with the craft config page ?
Will investigate with access, i'm new with cloudflare, but seems i begin to understand.
One more time thanks for sharing your time and knowledge.
Hi there. Is it possible to use this custom domain setup with Craft pages that have password-protection enabled? At present, I am unable to use the password to gain access to the page.
@moondigital This version is adapted to the password access, but this worker.js has not yet.
You need insert this somewhere to support webfonts
// Proxy fonts
else if (pathname.startsWith('/share/') && pathname.endsWith('.woff2')) {
response = await fetch(url)
// The readable side will become our new response body.
let { readable, writable } = new TransformStream();
// Start pumping the body. NOTE: No await!
response.body.pipeTo(writable);
// ... and deliver our Response while that’s running.
return new Response(readable, response);
}
Hi! Thank you for sharing this. It has helped me a lot. Is it possible that it works with the Craft analytics? Right now it does not track any visitors. Thank you!
Hi! Thank you for sharing this. It has helped me a lot. Is it possible that it works with the Craft analytics? Right now it does not track any visitors. Thank you!
@gustavojellav Oh, I removed the Craft analysis code by default. You can delete this script line 210, then Craft's analysis will be enabled.
Hi, @datagitateur