I[★] Fork

    @@ -3,7 +3,6 @@ * @description greedy quoted Hatena Star * @include http://* * @include https://* - * @contributor Hatena https://s.hatena.ne.jp/images/add.gif * @contributor a-kuma3 http://let.hatelabo.jp/a-kuma3/let/hJmc_YyG8sE- * @nitpicker noromanba * @license MIT License https://opensource.org/licenses/MIT
    @@ -15,89 +14,71 @@ (() => { 'use strict'; - function change_button_color(img) { - // change color with canvas - // https://developer.mozilla.org/ja/docs/Web/Guide/HTML/Canvas_tutorial/Pixel_manipulation_with_canvas - let canvas; - document.body.querySelectorAll([ - 'img.hatena-star-add-button[src*="s.hatena.ne.jp"]', - ]).forEach(button => { - if (!canvas) { - canvas = Object.assign(document.createElement('canvas'), { - width: img.width, - height: img.height, - }); - const ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0); - const imageData = ctx.getImageData(0, 0, img.width, img.height); - let data = imageData.data; - for (let i = 0 ; i < data.length ; i += 4) { - if (data[i] < 200) { - // gold : rgb(255,215,0) - data[i] = 255; // red - data[i + 1] = 215; // green - data[i + 2] = 0; // blue - // data[i + 3] = 255; // opacity - } - } - ctx.putImageData(imageData, 0, 0); - } - button.src = canvas.toDataURL(); - button.title = button.title + ' (force quote)'; - }); - } + document.body.querySelectorAll([ + 'img.hatena-star-add-button[src*="s.hatena.ne.jp"]', + ]).forEach(button => { + // https://developer.mozilla.org/en-US/docs/Web/CSS/filter + // NOTE order sensitive + button.style.filter = [ + // reset to black + 'grayscale(1) brightness(0)', + // w/ black to hex CSS filter generator by Barrett Sonntag c.f. + // https://codepen.io/sosuke/pen/Pjoqqp + // https://codepen.io/sosuke/pen/Pjoqqp/license + // via + // https://gist.github.com/barretts/e90d7e5251f36b183c67e02ba54c9ae1 + // + // gold | rgb(255,215,0) | hex #ffd700 + 'invert(74%)', + 'sepia(56%)', + 'saturate(811%)', + 'hue-rotate(0deg)', + 'brightness(106%)', + 'contrast(103%)', + ].join(' '); - // IDK necessary needs star icon at bottom is? - //document.body.appendChild(Object.assign(document.createElement('img'), { - Object.assign(document.createElement('img'), { - // TODO over CORS, canvas too - //src: 'https://s.hatena.com/images/add.gif', - //src: 'https://s.hatena.ne.jp/images/add.gif', - // XXX check term of use - src: 'data:image/gif;base64,R0lGODlhEAAQAIABALrJ9f///yH5BAEAAAEALAAAAAAQABAAAAIojI+pm+APYQCIMlfZtLOvSEkexhmchXkjaHYlGpLpqrZNqbngnvd+AQA7', - // https://stackoverflow.com/questions/17035106/context-getimagedata-operation-is-insecure - crossOrigin: 'anonymous', - onload() { - change_button_color(this); - }, - //})); + button.title += ' (force quote)'; }); // http://q.hatena.ne.jp/1487227736#a1262105 - function convert_numeric_reference_quote(url_string) { - // https://developer.mozilla.org/en-US/docs/Web/API/URL - // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams + const to_numeric_reference = (str) => { + let letter = '', i = 0; + while (i < str.length) { + const code = str.codePointAt(i); + letter += code < 128 ? String.fromCharCode(code) : `&#${code};`; + i += 1; + // for surrogate pair + if (code > 0xffff) { + i += 1; + } + } + return letter; + }; + + const enforce_quote = (url_string) => { const url = new URL(url_string); - let quote = url.searchParams.get('quote'); - if (quote) { - // to numeric reference - quote = (s => { - let o = '', i = 0; - while (i < s.length) { - const code = s.codePointAt(i); - o += code < 128 ? String.fromCharCode(code) : `&#${code};`; - i += 1; - if (code > 0xffff) { // for surrogate pair - i += 1; - } - } - return o; - })(quote); - url.searchParams.set('quote', quote); - url_string = url.href; + // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams + const quote = url.searchParams.get('quote'); + if (!quote) { + return url_string; } - return url_string; - } + const safed = to_numeric_reference(quote); + + url.searchParams.set('quote', safed); + + return url.href; + }; + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy // https://s.hatena.ne.jp/js/HatenaStar.js:111 - // Ten.JSONP(URL, CALLBACK, METHOD) + // Ten.JSONP(<URL_STRING>, <CALLBACK>, <HTTP_METHOD>) window.Ten.JSONP = new Proxy(window.Ten.JSONP, { construct: (target, args) => { const url_string = args[0]; if (url_string.includes('//s.hatena.ne.jp/star.add.json')) { - args[0] = convert_numeric_reference_quote(url_string); + args[0] = enforce_quote(url_string); } return Reflect.construct(target, args);
  • /*
     * @title I[★]
     * @description greedy quoted Hatena Star
     * @include http://*
     * @include https://*
     * @contributor a-kuma3     http://let.hatelabo.jp/a-kuma3/let/hJmc_YyG8sE-
     * @nitpicker   noromanba
     * @license     MIT License https://opensource.org/licenses/MIT
     * @javascript_url
     */
    
    // TBD .com handling
    // TBC spec and behavior
    (() => {
        'use strict';
    
        document.body.querySelectorAll([
            'img.hatena-star-add-button[src*="s.hatena.ne.jp"]',
        ]).forEach(button => {
            // https://developer.mozilla.org/en-US/docs/Web/CSS/filter
            // NOTE order sensitive
            button.style.filter = [
                // reset to black
                'grayscale(1) brightness(0)',
                // w/ black to hex CSS filter generator by Barrett Sonntag c.f.
                //  https://codepen.io/sosuke/pen/Pjoqqp
                //   https://codepen.io/sosuke/pen/Pjoqqp/license
                // via
                //  https://gist.github.com/barretts/e90d7e5251f36b183c67e02ba54c9ae1
                //
                // gold | rgb(255,215,0) | hex #ffd700
                'invert(74%)',
                'sepia(56%)',
                'saturate(811%)',
                'hue-rotate(0deg)',
                'brightness(106%)',
                'contrast(103%)',
            ].join(' ');
    
            button.title += ' (force quote)';
        });
    
        // http://q.hatena.ne.jp/1487227736#a1262105
        const to_numeric_reference = (str) => {
            let letter = '', i = 0;
            while (i < str.length) {
                const code = str.codePointAt(i);
                letter += code < 128 ? String.fromCharCode(code) : `&#${code};`;
                i += 1;
                // for surrogate pair
                if (code > 0xffff) {
                    i += 1;
                }
            }
            return letter;
        };
    
        const enforce_quote = (url_string) => {
            const url = new URL(url_string);
            // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
            const quote = url.searchParams.get('quote');
            if (!quote) {
                return url_string;
            }
    
            const safed = to_numeric_reference(quote);
    
            url.searchParams.set('quote', safed);
    
            return url.href;
        };
    
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
        // https://s.hatena.ne.jp/js/HatenaStar.js:111
        //      Ten.JSONP(<URL_STRING>, <CALLBACK>, <HTTP_METHOD>)
        window.Ten.JSONP = new Proxy(window.Ten.JSONP, {
            construct: (target, args) => {
                const url_string = args[0];
    
                if (url_string.includes('//s.hatena.ne.jp/star.add.json')) {
                    args[0] = enforce_quote(url_string);
                }
    
                return Reflect.construct(target, args);
            },
        });
    
    })();
    
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。