Greasy Fork is available in English.

fix-image-error at inoreader

Fix image load error caused by CSP(Content Security Policy)

// ==UserScript==
// @name                 fix-image-error at inoreader
// @name:zh-CN           修复inoreader图片异常
// @version              0.5.0
// @namespace            https://github.com/mengtao-code
// @description          Fix image load error caused by CSP(Content Security Policy)
// @description:zh-CN    修复inoreader的图片加载问题
// @author               Mengtao Xin
// @license              MIT
// @supportURL           https://github.com/mengtao-code/tampermonkey-scripts
// @include              http*://*.inoreader.com/*
// @icon                 http://www.inoreader.com/favicon.ico
// @grant                GM_xmlhttpRequest
// @connect              *
// ==/UserScript==

const LOADING_PROMPT = 'Loading...'

const config = {
    name: 'fix-image-error',
    data: [
        // weibo images prefix
        {
            imageServer: 'sinaimg.cn',
            mockHeader: {
                Referer: 'https://weibo.com'
            }
        }
    ]
}

/**
 *
 * @link https://stackoverflow.com/questions/8778863/downloading-an-image-using-xmlhttprequest-in-a-userscript
 * @param {string} input
 * @returns {string}
 */
function toBase64(input) {
    var bbLen = 3,
        enCharLen = 4,
        inpLen = input.length,
        inx = 0,
        jnx,
        keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + '0123456789+/=',
        output = '',
        paddingBytes = 0
    var bytebuffer = new Array(bbLen),
        encodedCharIndexes = new Array(enCharLen)

    while (inx < inpLen) {
        for (jnx = 0; jnx < bbLen; ++jnx) {
            if (inx < inpLen) bytebuffer[jnx] = input.charCodeAt(inx++) & 0xff
            else bytebuffer[jnx] = 0
        }
        encodedCharIndexes[0] = bytebuffer[0] >> 2
        encodedCharIndexes[1] = ((bytebuffer[0] & 0x3) << 4) | (bytebuffer[1] >> 4)
        encodedCharIndexes[2] = ((bytebuffer[1] & 0x0f) << 2) | (bytebuffer[2] >> 6)
        encodedCharIndexes[3] = bytebuffer[2] & 0x3f
        paddingBytes = inx - (inpLen - 1)
        switch (paddingBytes) {
            case 1:
                // Set last character to padding char
                encodedCharIndexes[3] = 64
                break
            case 2:
                // Set last 2 characters to padding char
                encodedCharIndexes[3] = 64
                encodedCharIndexes[2] = 64
                break
            default:
                break // No padding - proceed
        }
        for (jnx = 0; jnx < enCharLen; ++jnx) output += keyStr.charAt(encodedCharIndexes[jnx])
    }
    return output
}

/**
 *
 * @param responseData data from http request
 * @returns {string}
 */
const getImageUrl = responseData => {
    const binResp = toBase64(responseData.responseText)
    return `data:image/jpeg;base64,${binResp}`
}

/**
 * send get http request
 * @param {string}url
 * @param customHeader
 * @returns {Promise<unknown>}
 */
const httpGetRequest = (url, customHeader) => {
    return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({
            method: 'GET',
            url: url,
            headers: {
                Accept: '*/*',
                referrerPolicy: 'no-referrer',
                ...customHeader
            },
            onload: resolve,
            onerror: reject,
            overrideMimeType: 'text/plain; charset=x-user-defined'
        })
    })
}

/**
 * 
 * @param {Element} dom 
 * @param {*} mockHeader 
 */
const processDetailImage = (dom, mockHeader) => {
    if (dom.getAttribute('process-tag') !== 'doing' && dom.getAttribute('process-tag') !== 'done') {
        dom.setAttribute('process-tag', 'doing')
        dom.setAttribute('alt', LOADING_PROMPT)
        const originUrl = dom.getAttribute('src')
        httpGetRequest(originUrl, mockHeader)
            .then(responseData => {
                const goodUrl = getImageUrl(responseData)
                dom.setAttribute('src', goodUrl)
            })
            .catch(e => console.error(`${config.name} load image failed! ${e}`))
            .finally(() => dom.setAttribute('process-tag', 'done'))
    }
}

const processListImage = (dom, mockHeader) => {
    if (dom.getAttribute('process-tag') !== 'doing' && dom.getAttribute('process-tag') !== 'done') {
        dom.setAttribute('process-tag', 'doing')
        const style = dom.getAttribute('style')
        const originUrl = style.substring(style.indexOf('https')).replace("')", '')
        console.log(`originUrl:${originUrl}`)
        httpGetRequest(originUrl, mockHeader)
            .then(responseData => {
                const goodUrl = getImageUrl(responseData)
                dom.setAttribute('style', `background-image:url('${goodUrl}')`)
            })
            .catch(e => console.error(`${config.name} load image failed! ${e}`))
            .finally(() => dom.setAttribute('process-tag', 'done'))
    }
}

/**
 * 检测到有异常图片,就调整成正常的图片
 */
const main = () => {
    config.data.forEach(({ imageServer, mockHeader }) => {
        // 1. detail pages
        Array.from(document.querySelectorAll(`.article_content img[src*='${imageServer}']`)).forEach(image => processDetailImage(image, mockHeader))

        // 2. list pages
        Array.from(document.querySelectorAll(`.article_magazine_picture, .article_tile_picture`))
            .filter(dom => dom.getAttribute('style').includes(imageServer))
            .forEach(image => processListImage(image, mockHeader))
    })
}

setInterval(main, 3000)