Greasy Fork is available in English.

Feedly Full Feed

Read full story in Feedly. / Feedlyで記事の全文を表示します。

// ==UserScript==
// @name    Feedly Full Feed
// @description    Read full story in Feedly. / Feedlyで記事の全文を表示します。
// @namespace    https://userscripts.org/scripts/show/171868
// @homepage    https://greasyfork.org/scripts/896
// @match    https://feedly.com/*
// @connect    *
// @grant    GM_addStyle
// @grant    GM_openInTab
// @grant    GM_registerMenuCommand
// @grant    GM_setClipboard
// @grant    GM_xmlhttpRequest
// @noframes
// @version    0.43
// ==/UserScript==

(() => {
  'use strict';

  // == [CSS] =====================================
  const CSS = [
    '#timeline .entryTitle img { border: none; vertical-align: text-top; }',
    '#timeline .entryTitle span a { margin: 0 3px; }',
    '#timeline .entryBody .clear { font-size: inherit !important; height: auto !Important; width: auto !important; }',
    '.u100Entry .title { display: inline !important; }',
    '.gm_fullfeed_loading { border-color: limegreen !important; background-color: limegreen !important; }',
    '.gm_fullfeed_loaded * { position: static !important; }',
    '.gm_fullfeed_opened { border-color: gold !important; background-color: gold !important; }',
    '.gm_fullfeed_ap_on { color: #009900; }',
    '.gm_fullfeed_ap_off { color: #990000; }',
    '.gm_fullfeed_pager { margin: 3em 0; }',
    '.gm_fullfeed_pager hr { margin-bottom: 5px; }',
    '.gm_fullfeed_pager_differenthost { margin-left: 2em; font-weight: bold; }',
    '.gm_fullfeed_entry_url { font-size: 80%; }',
    '.gm_fullfeed_hidden { display: none; }',
    '#gm_fullfeed_message { position: fixed; top: 4px; left: 40vw; z-index: 90900; color: black; background-color: #FFEEAA; box-shadow: 1px 1px 2px #CCCCCC; padding: 4px 8px; }',
    '.gm_fullfeed_warning { background-color: #FF9999 !important; }',
    '.gm_fullfeed_checked { margin-right: 8px; }',
    '.gm_fullfeed_checked_icon { color: white; font-size: 0.8em; margin-left: 1em; padding: 0 1.25em; text-decoration:none; cursor: pointer; position: relative; vertical-align: top; border-radius: 3px; box-shadow: 1px 1px 2px #CCCCCC; }',
    '.gm_fullfeed_checked_icon:active { box-shadow: none; }',
    '.gm_fullfeed_checked_icon_info { background: #FFCC00; background: linear-gradient(top, #FFCC00, #FF9900); border: 1px solid #EE8800; }',
    '.gm_fullfeed_checked_icon_info:hover { background: #FFEE00; background: linear-gradient(top, #FFEE00, #FFBB00); border: 1px solid #EEAA00; }',
    '.gm_fullfeed_checked_icon_noinfo { background: #CCCCCC; background: linear-gradient(top, #CCCCCC, #999999); border: 1px solid #888888; }',
    '.gm_fullfeed_checked_icon_noinfo:hover { background: #EEEEEE; background: linear-gradient(top, #EEEEEE, #BBBBBB); border: 1px solid #AAAAAA; }',
    '.gm_fullfeed_checked_icon_next { background: #77CC77; background: linear-gradient(top, #77CC77, #55AA55); border: 1px solid #449944; }',
    '.gm_fullfeed_checked_icon_next:hover { background: #99EE99; background: linear-gradient(top, #99EE99, #77CC77); border: 1px solid #66BB66; }',
    '.gm_fullfeed_checked_icon_nonext { background: #BBEEBB; background: linear-gradient(top, #BBEEBB, #99CC99); border: 1px solid #88BB88; }',
    '.gm_fullfeed_checked_icon_nonext:hover { background: #DDFFDD; background: linear-gradient(top, #DDFFDD, #BBEEBB); border: 1px solid #AADDAA; }',
    '.gm_fullfeed_checked_icon_as_next { background: #7777CC; background: linear-gradient(top, #7777CC, #5555AA); border: 1px solid #444499; }',
    '.gm_fullfeed_checked_icon_as_next:hover { background: #9999EE; background: linear-gradient(top, #9999EE, #7777CC); border: 1px solid #6666BB; }',
    '.gm_fullfeed_checked_icon_as_nonext { background: #BBBBEE; background: linear-gradient(top, #BBBBEE, #9999CC); border: 1px solid #8888BB; }',
    '.gm_fullfeed_checked_icon_as_nonext:hover { background: #DDDDFF; background: linear-gradient(top, #DDDDFF, #BBBBEE); border: 1px solid #AAAADD; }',
    '.gm_fullfeed_socialicon { font-size: 12px; }',
    '#gm_fullfeed_settings { background-color: #FFCC66; z-index: 90000; position: fixed; top: 8px; left: 8px; padding: 4px; width: 600px; border-radius: 4px; }',
    '#gm_fullfeed_settings input, #gm_fullfeed_settings textarea { padding: 2px; }',
    '#gm_fullfeed_settings fieldset { border: 1px solid #FFCC66; margin: 4px 2px; }',
    '#gm_fullfeed_settings-header { padding: 0 4px 8px 4px; }',
    '#gm_fullfeed_settings-header_title { font-size: 120%; font-weight: bold; }',
    '#gm_fullfeed_settings-header_title a { color: #224488; text-decoration: none; }',
    '#gm_fullfeed_settings-header_button { font-size: 90%; float: right; }',
    '#gm_fullfeed_settings-header input { margin: 0 4px; width: 7em; }',
    '#gm_fullfeed_settings-tab { overflow: auto; margin: 0; }',
    '#gm_fullfeed_settings-tab .selected { padding: 4px 8px 6px 8px; background-color: #FFF7D7; color: black; text-decoration: none; }',
    '.gm_fullfeed_settings-title { float: left; padding: 4px 8px; margin: 0; color: #224488; cursor: pointer; text-decoration: underline; font-size: 100%; }',
    '#gm_fullfeed_settings-list { background-color: #FFF7D7; padding: 8px 4px 4px 4px; margin: 0; border-radius: 0 0 4px 4px; }',
    '#gm_fullfeed_settings-footer { text-align: right; padding: 4px; }',
    '.gm_fullfeed_settings-footer input { margin: 0 4px; }',
    '#gm_fullfeed_settings-ok { padding: 0 2em; }',
    '#gm_fullfeed_settings-general_key, #gm_fullfeed_settings-general_apheight { width: 7ex; text-align: center; margin-right: 1ex; ime-mode: disabled !important; }',
    '#gm_fullfeed_settings-general_keyhelpicon { margin-left: 1em; }',
    '#gm_fullfeed_settings-general_keyhelpbody { margin-top: 0.5em; display: none; }',
    '#gm_fullfeed_settings-general_cantdisplay { margin-bottom: 0.5em; }',
    '#gm_fullfeed_settings-general_notread { width: 536px; margin-bottom: 0.8em;}',
    '#gm_fullfeed_settings-siteinfo div { margin: 0 0 1em 0; }',
    '.gm_fullfeed_settings-siteinfo_link { color: #224488; padding: 0 0.5em; cursor: pointer; }',
    '#gm_fullfeed_settings-siteinfo .selected { color: black; border-bottom: medium solid #FFCC66; }',
    '#gm_fullfeed_settings-siteinfo fieldset { display: none; }',
    '#gm_fullfeed_settings-siteinfo textarea { width: 560px; height: 10em; }',
    '.gm_fullfeed_settings-autoload_title { width: 450px; margin-right: 8px; }',
    '.gm_fullfeed_settings-autoload_list { width: 560px; height: 8em; margin-top: 8px; }',
    '#gm_fullfeed_settings-siteinfo_disableitemlist { width: 560px; overflow: hidden; white-space: nowrap; }',
    '#gm_fullfeed_settings-etc button { margin: 0 4px; letter-spacing: 1px; }',
    '.gm_fullfeed_siteinfourl_list fieldset { background-color: #FFF7D7; }',
    '#gm_fullfeed_settings-security input[type="text"], #gm_fullfeed_settings-etc input[type="text"] { width: 560px; }',
    '.gm_fullfeed_hatena1 span { color: #FF6563 !important; background-color: #FFF0F0 !important; }',
    '.gm_fullfeed_hatena2 span { color: #FF4444 !important; background-color: #FFEEEE !important; }',
    '.gm_fullfeed_hatena3 span { color: #FF2222 !important; background-color: #FFDDDD !important; }',
    '.gm_fullfeed_hatena4 span { color: #FF0000 !important; background-color: #FFCCCC !important; }',
    'a[class*="gm_fullfeed_hatena"] > img { height: 14px; width: 14px; vertical-align: sub; }',
  ].join('');

  // == [Locale] ==================================
  const localeJa = [
    '全文を読み込み中…',
    '全文を読み込み中… Auto Search…',
    '全文を読み込み中… 完了',
    'サイト情報が古いか間違っているため、全文を表示できません',
    '次のページを読み込み中…',
    '次のページを読み込み中… 完了',
    '最後のページを読み込み中… 完了',
    '次のページは見つかりません',
    '読み込みをブロックしました',
    'すでに全文を読み込みました',
    '全文を表示するための情報が見つからないため、新しいタブで開きました',
    'キャッシュをリセットしています…',
    'キャッシュをリセットしています… 完了',
    'ポップアップできません',
    'エラー:サーバーからキャッシュを読み込めませんでした',
    'キャッシュをリセットしますか?',
    'キャッシュを消去しますか?',
    'エラー:JSONPはサポートしていません',
    '全文読み込み',
    'はてなブックマーク',
    '',
    '設定',
    'キャンセル',
    'ショートカットキー',
    'キー',
    'キーコード',
    'Auto Load 切替',
    'autoPagerize 切替',
    '設定欄を表示',
    'キャッシュリセット',
    'Full Feed URL',
    'AutoPagerize URL',
    'Full Feed ユーザーSITE_INFO',
    'AutoPagerize ユーザーSITE_INFO',
    '次のページを読み込み中… Auto Search… 完了',
    '最後のページを読み込み中… Auto Search… 完了',
    'ホワイトリスト',
    'フィードタイトル (正規表現)',
    '追加',
    'ホワイトリスト/ブラックリストに追加',
    'クリック',
    '次のページを読み込む',
    'noscriptタグ内のコンテンツを表示',
    '自動的に全文を読み込むアイテム',
    'すべて',
    'ホワイトリストのみ',
    'なし',
    '全文を読み込まないアイテム (正規表現)',
    '読み込まない代わりに新しいタブで開く',
    '新しいタブで開きました',
    'デバッグログ',
    '全文を読み込み中… Auto Search… 完了',
    'インラインフレームを取り除く',
    'エラー:dom.storage.enabledをtrueに設定してください',
    'お待ちください…',
    'ソーシャルアイコンを表示',
    '',
    '',
    '',
    '警告:次ページを継ぎ足してもよろしいですか?\n\n今のページ:',
    '\n次のページ:',
    '次ページを継ぎ足しません',
    '次ページの継ぎ足しを許可するURL (正規表現)',
    '次ページの継ぎ足しを禁止するURL (正規表現)',
    'サイト情報がなくても全文の読み込みを試みる',
    '表示を許可するインラインフレームのソースURL (正規表現)',
    '設定をエクスポートしてクリップボードへコピーしますか?',
    'クリップボードへコピーしました',
    '設定をエクスポートしました。\n以下の文字列をコピーしてください\n\n',
    'エクスポートした文字列を入力してください',
    '設定をインポートしました',
    'インポートできません',
    '設定をリセットしますか?',
    '設定をリセットしました',
    'サイト情報が見つからないため全文を表示できません',
    '全文を表示できないときは新しいタブで開く',
    'ブラックリスト',
    'ブラックリスト以外',
  ];
  const localeEn = [
    'Loading Full Feed...',
    'Loading Full Feed... Auto Search...',
    'Loading Full Feed... Done',
    'This site info is unmatched to this entry',
    'Loading next page...',
    'Loading next page... Done',
    'Loading last page... Done',
    'Next page is not found',
    'Blocked loading full story',
    'This entry has been already loaded',
    'Information to display full story is not found, Opened in new tab',
    'Resetting cache. Please wait...',
    'Resetting cache. Please wait... Done',
    'Cannot popup',
    'Cache Request Error',
    'Reset cache?',
    'Delete cache?',
    'Error: not support JSONP',
    'read full story',
    'Hatena Bookmark',
    '',
    'Settings',
    'Cancel',
    'shortcut key',
    'key',
    'key code',
    'change Auto Load',
    'change autoPagerize',
    'view settings',
    'reset cache',
    'Full Feed URL',
    'AutoPagerize URL',
    'Full Feed user SITE_INFO',
    'AutoPagerize user SITE_INFO',
    'Loading next page... Auto Search... Done',
    'Loading last page... Auto Search... Done',
    'Whitelist',
    'Feed title (regular expression)',
    'Add',
    'Add whitelist/blacklist',
    'Click',
    'Loading next page',
    'Display content in noscript tags',
    'Read full story automatically',
    'All',
    'Whitelist only',
    'None',
    'Item which does not read full story (regular expression)',
    'Instead of not reading, open in new tab',
    'Opened in new tab',
    'Debug log',
    'Loading Full Feed... Auto Search... Done',
    'Remove iframe',
    'Error: Please set "dom.storage.enabled" in "true".',
    'Wait...',
    'Display social icon',
    '',
    '',
    '',
    'Warning: Add the next page?\n\nreading page: ',
    '\nnext page: ',
    'Do not add the next page',
    'URL to allow the addition of the next page (regular expression)',
    'URL to deny the addition of the next page (regular expression)',
    'Try loading full story without a site info',
    'The source URL of the inline frame to display (regular expression)',
    'Export settings and copy it to clipboard?',
    'Copied it to clipboard',
    'Exported settings.\nPlease copy the following character string\n\n',
    'Please input the character string that exported',
    'Imported settings',
    'Cannot import',
    'Reset settings?',
    'Reset settings',
    'Site info to display full story is not found',
    'Open in new tab if cannot display full story',
    'blacklist',
    'Except blacklist',
  ];
  const loc = /ja|ja-JP/.test(window.navigator.language) ? localeJa : localeEn;

  // == [Application] =============================
  try {
    if (typeof localStorage !== 'object') return window.alert(loc[53]);
  } catch (e) {
    console.error(e);
    return window.alert(loc[53]);
  }
  const $id = function (id) {
    return document.getElementById(id);
  };
  const $ids = function (id) {
    return $id(`gm_fullfeed_settings-${id}`);
  };

  class FullFeed {
    constructor(info, c, flag) {
      state = 'ready';
      this.itemInfo = c;
      this.info = info;
      this.requestURL = this.itemInfo.itemURL;
      let u = this.requestURL;
      if (/^https?:\/\/www\.feedly\.com\/home#subscription\/feed\/https?:\/\/.+/.test(u)) {
        u = u.slice(u.indexOf('/feed/') + 6);
        this.requestURL = decodeURIComponent(u.slice(0, u.indexOf('&')));
      } else if (/^http:\/\/rd\.yahoo\.co\.jp\/rss\/l\/.+/.test(u)) {
        if (/\/\*-http/.test(u)) {
          this.requestURL = decodeURIComponent(u.slice(u.indexOf('*-http') + 2));
        } else if (/\/\*http/.test(u)) {
          this.requestURL = decodeURIComponent(u.slice(u.indexOf('*http') + 1));
        }
      }
      this.itemInfo.itemBody = document.querySelector(`${returnSelectorEntry()} .entryBody`);
      let encode = this.info.enc || document.characterSet;
      if (flag === 'next' && this.itemInfo.innerContents) {
        this.itemInfo.innerContents.className.split(/\s+/).some(i => {
          if (!/^entry|^gm_|^http/.test(i)) {
            encode = i;
            return true;
          }
        });
      }
      this.mime = `text/html; charset=${encode}`;
      //if (flag === 'search') this.request('HEAD', c);
      //else this.request('GET', c);
      if (debugLog) {
        try {
          console.log(new Date());
          console.log(`Item Title: ${c.item.title}`);
          console.log(`Item URL: ${c.item.url}`);
          console.log(`Feed URL: ${c.feed.url}`);
          console.log(`FullFeed URL: ${info.url}`);
          console.log(`FullFeed XPath: ${info.xpath}`);
          console.log(`FullFeed Encode: ${info.enc}`);
          console.log(this.info);
        } catch (e) { console.log(e); }
      }
    }
    static checkScroll() {
      if (!nextPageLink || (state && state !== 'loaded' && state !== 'wait')) return;
      window.clearTimeout(scrollInterval);
      scrollInterval = window.setTimeout(() => {
        const e1 = $id('box'),
          e2 = document.querySelector(returnSelectorEntry()),
          e3 = $id('floatingEntry');
        let remain;
        if (!e1 || !e2) return;
        if (e3 && e3.getElementsByClassName('slideEntryContent').length) {
          remain = nextPageLink.offsetTop - e2.clientHeight - e2.scrollTop;
        } else remain = e2.offsetTop + nextPageLink.offsetTop - e1.clientHeight - e1.scrollTop;
        if (remain < st.apheight) {
          nextPageLink = null;
          initFullFeed();
        }
      }, 100);
    }
    static checkNextPage(con) {
      const con2 = con.cloneNode(true);
      const aClass = con.className.split(/\s+/);
      const finalUrl = aClass[aClass.length - 1];
      const c = new GetCurrentItem();
      let bMatch;
      const check = function (data, flag) {
        let nextEl, nextEl2;
        if (
          data.some(info => {
            if (info.url && info.url.length <= 12 && !/^\^?https?/i.test(info.url)) {
              if (new RegExp('^://', 'i').test(info.url)) info.url = `^https?${info.url}`;
              else if (new RegExp('^//', 'i').test(info.url)) info.url = `^https?:${info.url}`;
              else info.url = `^https?://${info.url}`;
            }
            if (!bMatch && new RegExp(info.url).test(finalUrl) && info.url.length > 12) {
              let elms,
                elms2,
                bCache,
                bList,
                nextLink = info.nextLink;
              if (nextLink) nextLink = nextLink.replace(/id\(([^)]+)\)/g, '//*[@id=$1]');
              if (nextLink) {
                elms2 = getElementsByXPath(nextLink, con2);
                elms = getElementsByXPath(nextLink, con);
              }
              if (!elms2) elms2 = getElementsByXPath('//a[@rel="next"]', con2);
              if (!elms) elms = getElementsByXPath('//a[@rel="next"]', con);
              if (elms2 && elms2.length > 0) nextEl2 = elms2[elms2.length - 1];
              if (elms && elms.length > 0 && nextEl2 && nextEl2.href) {
                const arr = [];
                for (let i = 0, j = elms.length; i < j; i++) {
                  if (nextEl2.href === elms[i].href) arr.push(elms[i]);
                }
                nextEl = arr.length ? arr[arr.length - 1] : elms[elms.length - 1];
              }
              if (nextEl2 && finalUrl !== nextEl2.href) {
                if (flag === 'cache') {
                  if (
                    userCacheListAP.some(u => {
                      if (u === c.feed.url) {
                        return true;
                      }
                    })
                  ) {
                    userCacheAP.some(u => {
                      if (u === info) {
                        bMatch = true;
                        return true;
                      }
                    });
                  }
                } else bMatch = true;
                if (flag === 'full') {
                  if (
                    userCacheListAP.some(u => {
                      if (u === c.feed.url) {
                        bList = true;
                        return true;
                      }
                    })
                  ) {
                    userCacheAP.some(u => {
                      if (u === info) {
                        bCache = true;
                        return true;
                      }
                    });
                  }
                  if (!bCache) {
                    delete info.exampleUrl;
                    delete info.insertBefore;
                    userCacheAP.push(info);
                    try {
                      localStorage.setItem('FeedlyFullFeed_userCacheAP', JSON.stringify(userCacheAP));
                      if (!bList) {
                        userCacheListAP.push(c.feed.url);
                        localStorage.setItem('FeedlyFullFeed_userCacheListAP', JSON.stringify(userCacheListAP));
                      }
                    } catch (er) { console.log(er); }
                  }
                }
                return true;
              }
              return false;
            }
            return false;
          })
        ) {
          nextPageLink = nextEl ? nextEl : null;
        }
      };
      check(userSiteInfoAP, 'set');
      if (!bMatch) check(userCacheAP, 'cache');
      if (!bMatch) {
        siteInfoAP.some(ctx => {
          if (cacheAPInfo[ctx.url]) {
            check(cacheAPInfo[ctx.url].info, 'full');
            if (bMatch) return true;
          }
        });
      }
      if (bMatch && nextPageLink && st.denynexturl) {
        const re = new RegExp(st.denynexturl, 'i');
        if (re.test(nextPageLink.href)) nextPageLink = null;
      }
    }
    static resetCache() {
      message(loc[11], -1);
      const getSiteinfo = function (data, flag) {
        let nCache = 0;
        const nCaches = flag ? siteInfo.length : siteInfoAP.length;
        data.forEach(ctx => {
          const opt = {
            method: 'GET',
            url: ctx.url,
            nocache: true,
            onload: function (res) {
              nCache += 1;
              if (res.status === 200) {
                if (flag) {
                  FullFeed.setCache(res, ctx, 'cache');
                  if (nCache === nCaches) getSiteinfo(siteInfoAP);
                } else {
                  if (nCache === nCaches) FullFeed.setCache(res, ctx, 'cacheAP', true);
                  else FullFeed.setCache(res, ctx, 'cacheAP');
                }
              } else if (flag && nCache === nCaches) getSiteinfo(siteInfoAP);
            },
            onerror: function () {
              nCache += 1;
              if (flag && nCache === nCaches) getSiteinfo(siteInfoAP);
            },
          };
          GM_xmlhttpRequest(opt);
        });
      };
      getSiteinfo(siteInfo, true);
    }
    static removeCache() {
      if (window.confirm(loc[16])) {
        localStorage.removeItem('FeedlyFullFeed_cache');
        localStorage.removeItem('FeedlyFullFeed_cacheAP');
        localStorage.removeItem('FeedlyFullFeed_userCache');
        localStorage.removeItem('FeedlyFullFeed_userCacheAP');
        localStorage.removeItem('FeedlyFullFeed_userCacheList');
        localStorage.removeItem('FeedlyFullFeed_userCacheListAP');
        cacheInfo = {};
        cacheAPInfo = {};
        userCache = [];
        userCacheAP = [];
        userCacheList = [];
        userCacheListAP = [];
      }
    }
    static setCache(res, ctx, item, flag) {
      let info = [];
      const setJson = function (t) {
        try {
          info = JSON.parse(t)
            .sort((a, b) => {
              a = new Date(a.updated_at).getTime();
              b = new Date(b.updated_at).getTime();
              if (!isNaN(a) && !isNaN(b)) {
                return new Date(b).getTime() - new Date(a).getTime();
              }
              return b;
            })
            .map(i => {
              return i.data;
            })
            .sort((lhs, rhs) => {
              return rhs.priority - lhs.priority;
            });
        } catch (er1) { console.log(er1); }
      };
      switch (ctx.format.toUpperCase()) {
        case 'JSON':
          setJson(res.responseText);
          break;
        case 'JSONP':
          if (ctx.callback) {
            try {
              let txt = res.responseText;
              txt = txt.replace(new RegExp(`${ctx.callback}\\s*\\(\\s*\\[\\s*\\{`, 'gm'), `${ctx.callback}([{`).replace(/\}\s*\]\s*\)/gm, '}])');
              txt = txt.slice(txt.indexOf(`${ctx.callback}([{`) + ctx.callback.length + 1);
              txt = txt.slice(0, txt.lastIndexOf('}])') + 2);
              setJson(txt);
            } catch (er2) { console.log(er2); }
          } else message(loc[17], 5000, 'warning');
          break;
      }
      if (info.length > 0) {
        if (item === 'cache') {
          cacheInfo[ctx.url] = {
            url: ctx.url,
            info: info,
          };
          try {
            localStorage.setItem('FeedlyFullFeed_cache', JSON.stringify(cacheInfo));
          } catch (er3) {
            if (er3 && er3.name && er3.message) window.alert(`${er3.name}\n${er3.message}`);
          }
          localStorage.removeItem('FeedlyFullFeed_userCache');
          localStorage.removeItem('FeedlyFullFeed_userCacheList');
          userCache = [];
          userCacheList = [];
        } else if (item === 'cacheAP') {
          cacheAPInfo[ctx.url] = {
            url: ctx.url,
            info: info,
          };
          try {
            localStorage.setItem('FeedlyFullFeed_cacheAP', JSON.stringify(cacheAPInfo));
          } catch (er4) {
            if (er4 && er4.name && er4.message) window.alert(`${er4.name}\n${er4.message}`);
          }
          localStorage.removeItem('FeedlyFullFeed_userCacheAP');
          localStorage.removeItem('FeedlyFullFeed_userCacheListAP');
          userCacheAP = [];
          userCacheListAP = [];
        }
        if (flag) {
          const hasFF = localStorage.getItem('FeedlyFullFeed_cache') ? true : false;
          const hasAP = localStorage.getItem('FeedlyFullFeed_cacheAP') ? true : false;
          if (hasFF && hasAP) message(loc[12], 5000);
          else message(loc[14], 5000, 'warning');
        }
      }
    }
    static getCache(key) {
      let js = '{}';
      if (localStorage.getItem(`FeedlyFullFeed_${key}`)) {
        js = localStorage.getItem(`FeedlyFullFeed_${key}`);
      } else if (/^cache.+/.test(key)) FullFeed.resetCache();
      else if (/^user.+/.test(key)) js = '[]';
      try {
        return JSON.parse(js);
      } catch (er) {
        console.error('getCache', er);
        if (/^user.+/.test(key)) return [];
        return {};
      }
    }
    static saveSettings() {
      try {
        localStorage.setItem('FeedlyFullFeed_settings', JSON.stringify(st));
      } catch (er) {
        console.error('saveSettings', er);
        window.alert('Error: Save Settings');
      }
    }
    static loadSettings(data) {
      if (data === 'reset') st = {};
      else if (data) st = data;
      else {
        st = {};
        try {
          if (localStorage.getItem('FeedlyFullFeed_settings')) {
            st = JSON.parse(localStorage.getItem('FeedlyFullFeed_settings') || '{}');
          }
        } catch (er1) { console.log(er1); }
      }
      const notA = function (a) {
        return Object.prototype.toString.call(a) !== '[object Array]' ? true : false;
      };
      const notB = function (a) {
        return typeof a !== 'boolean' ? true : false;
      };
      const notN = function (a) {
        return typeof a !== 'number' ? true : false;
      };
      const notS = function (a) {
        return typeof a !== 'string' ? true : false;
      };
      if (notS(st.allowiframeurl)) st.allowiframeurl = '';
      if (notS(st.allownexturl)) st.allownexturl = '';
      if (notN(st.apheight) || st.apheight < 200 || st.apheight > 2000) st.apheight = 500;
      if (notN(st.autoload) || st.autoload < 0 || st.autoload > 3) st.autoload = 2;
      if (notA(st.autoloadfeed)) st.autoloadfeed = [];
      if (notB(st.autopagerize)) st.autopagerize = true;
      if (notB(st.autosearch)) st.autosearch = true;
      if (notB(st.cantdisplay)) st.cantdisplay = false;
      if (isFinite(st.basekey)) st.basekey = +st.basekey;
      else if (typeof st.basekey === 'string' && st.basekey.length === 1) st.basekey = st.basekey.toUpperCase();
      else st.basekey = 'Z';
      if (notS(st.denynexturl)) st.denynexturl = '';
      if (notA(st.disableitem)) st.disableitem = [];
      if (notB(st.iframetag)) st.iframetag = true;
      if (notB(st.noscripttag)) st.noscripttag = true;
      if (notA(st.notloadfeed)) st.notloadfeed = [];
      if (notS(st.notread)) st.notread = '';
      if (notB(st.openitem)) st.openitem = false;
      if (notS(st.replacefullfeedurl)) st.replacefullfeedurl = '';
      if (notB(st.socialicon)) st.socialicon = true;
      if (notB(st.socialhatena)) st.socialhatena = false;
      try {
        siteInfo = JSON.parse(st.siteinfo);
      } catch (er2) {
        console.log('loadSettings', er2);
        siteInfo = [{ format: 'JSON', url: 'http://wedata.net/databases/LDRFullFeed/items.json' }];
        st.siteinfo = JSON.stringify(siteInfo);
      }
      try {
        siteInfoAP = JSON.parse(st.siteinfoap);
      } catch (er3) {
        console.log('loadSettings', er3);
        siteInfoAP = [{ format: 'JSON', url: 'http://wedata.net/databases/AutoPagerize/items.json' }];
        st.siteinfoap = JSON.stringify(siteInfoAP);
      }
      try {
        userSiteInfo = JSON.parse(st.usersiteinfo);
      } catch (er4) {
        console.log('loadSettings', er4);
        userSiteInfo = [{ url: '', xpath: '', type: '', enc: '' }];
        st.usersiteinfo = JSON.stringify(userSiteInfo);
      }
      try {
        userSiteInfoAP = JSON.parse(st.usersiteinfoap);
      } catch (er5) {
        console.log('loadSettings', er5);
        userSiteInfoAP = [{ url: '', nextLink: '', pageElement: '' }];
        st.usersiteinfoap = JSON.stringify(userSiteInfoAP);
      }
    }
    static createSettings() {
      const div = document.createElement('div');
      div.id = 'gm_fullfeed_settings';
      div.style.display = 'none';
      div.innerHTML = `<div id="gm_fullfeed_settings-header"><span id="gm_fullfeed_settings-header_title"><a href="https://greasyfork.org/scripts/896-feedly-full-feed" target="_blank">Feedly Full Feed ${loc[21]}</a></span><span id="gm_fullfeed_settings-header_button"><input type="button" id="gm_fullfeed_settings-ok" value="OK"><input type="button" id="gm_fullfeed_settings-cancel" value="${loc[22]}"></span></div><div id="gm_fullfeed_settings-tab"></div><ul id="gm_fullfeed_settings-list"></ul>`;
      document.body.appendChild(div);
      const list = [
        {
          tab: 'General',
          id: 'general',
          body: `<fieldset><legend>${loc[23]}</legend>${loc[18]} : <input type="text" id="gm_fullfeed_settings-general_key"><span id="gm_fullfeed_settings-general_keydesc"></span><a href="javascript:void(0);" id="gm_fullfeed_settings-general_keyhelpicon">[ ? ]</a><div id="gm_fullfeed_settings-general_keyhelpbody">${loc[26]} : Ctrl+ (${loc[18]}) ${loc[24]}<br>${loc[27]} : Shift+ (${loc[18]}) ${loc[24]}<br>${loc[28]} : Ctrl+Shift+ (${loc[18]}) ${loc[24]}<br>${loc[29]} : Alt+Ctrl+Shift+ (${loc[18]}) ${loc[24]}</div></fieldset><fieldset><legend>${loc[18]}</legend><label><input type="checkbox" id="gm_fullfeed_settings-general_autopagerize">${loc[41]}</label><label> ( <input type="text" id="gm_fullfeed_settings-general_apheight">px )</label><br><label><input type="checkbox" id="gm_fullfeed_settings-general_autosearch">${loc[64]}</label><br><label><input type="checkbox" id="gm_fullfeed_settings-general_cantdisplay">${loc[75]}</label><fieldset><legend>${loc[47]} : </legend><input type="text" id="gm_fullfeed_settings-general_notread"><br><label><input type="checkbox" id="gm_fullfeed_settings-general_openitem">${loc[48]}</label></fieldset></fieldset>`,
        },
        {
          tab: 'SITE INFO',
          id: 'siteinfo',
          body:
            `<div><span id="gm_fullfeed_settings-siteinfo_fullfeed_link" class="gm_fullfeed_settings-siteinfo_link">FF URL</span><span id="gm_fullfeed_settings-siteinfo_autopagerize_link" class="gm_fullfeed_settings-siteinfo_link">AP URL</span><span id="gm_fullfeed_settings-siteinfo_userfullfeed_link" class="gm_fullfeed_settings-siteinfo_link">FF SiteInfo</span><span id="gm_fullfeed_settings-siteinfo_userautopagerize_link" class="gm_fullfeed_settings-siteinfo_link">AP SiteInfo</span><span id="gm_fullfeed_settings-siteinfo_disableitem_link" class="gm_fullfeed_settings-siteinfo_link">disable Item</span></div><fieldset><legend>${loc[30]}</legend><textarea id="gm_fullfeed_settings-siteinfo_fullfeed"></textarea></fieldset><fieldset><legend>${loc[31]}</legend><textarea id="gm_fullfeed_settings-siteinfo_autopagerize"></textarea></fieldset><fieldset><legend>${loc[32]}</legend><textarea id="gm_fullfeed_settings-siteinfo_userfullfeed"></textarea></fieldset><fieldset><legend>${loc[33]}</legend><textarea id="gm_fullfeed_settings-siteinfo_userautopagerize"></textarea></fieldset><fieldset><legend>` +
            'disable SITE_INFO Item' +
            '</legend><div id="gm_fullfeed_settings-siteinfo_disableitemlist"></div><textarea id="gm_fullfeed_settings-siteinfo_disableitem"></textarea></fieldset>',
        },
        {
          tab: 'Auto Load',
          id: 'autoload',
          body: `<label>${loc[43]} : <select id="gm_fullfeed_settings-autoload_mode"><option>${loc[44]}</option><option>${loc[77]}</option><option>${loc[45]}</option><option>${loc[46]}</option></select></label><fieldset id="gm_fullfeed_settings-autoload_whitelist"><legend>${loc[36]}</legend>${loc[37]} : <br><input type="text" id="gm_fullfeed_settings-autoload_whitelist_title" class="gm_fullfeed_settings-autoload_title"><input type="button" id="gm_fullfeed_settings-autoload_whitelist_add" value="${loc[38]}"><br><textarea id="gm_fullfeed_settings-autoload_whitelist_list" class="gm_fullfeed_settings-autoload_list"></textarea></fieldset><fieldset id="gm_fullfeed_settings-autoload_blacklist"><legend>${loc[76]}</legend>${loc[37]} : <br><input type="text" id="gm_fullfeed_settings-autoload_blacklist_title" class="gm_fullfeed_settings-autoload_title"><input type="button" id="gm_fullfeed_settings-autoload_blacklist_add" value="${loc[38]}"><br><textarea id="gm_fullfeed_settings-autoload_blacklist_list" class="gm_fullfeed_settings-autoload_list"></textarea></fieldset>`,
        },
        {
          tab: 'Security',
          id: 'security',
          body: `<label><input type="checkbox" id="gm_fullfeed_settings-security_noscripttag">${loc[42]}</label><br><label><input type="checkbox" id="gm_fullfeed_settings-security_iframetag">${loc[52]}</label><fieldset><legend>${loc[65]} : </legend><input type="text" id="gm_fullfeed_settings-security_allowiframeurl"></fieldset><fieldset><legend>${loc[63]} : </legend><input type="text" id="gm_fullfeed_settings-security_denynexturl"></fieldset><fieldset><legend>${loc[62]} : </legend><input type="text" id="gm_fullfeed_settings-security_allownexturl"></fieldset>`,
        },
        {
          tab: 'Social',
          id: 'social',
          body: `<fieldset><legend><label><input type="checkbox" id="gm_fullfeed_settings-social_socialicon">${loc[55]}</label></legend><label><input type="checkbox" id="gm_fullfeed_settings-social_hatena">${loc[19]}</label></fieldset>`,
        },
        {
          tab: 'Etc',
          id: 'etc',
          body: `<label><input type="checkbox" id="gm_fullfeed_settings-etc_debuglog">${loc[50]}</label><br><fieldset><legend>Settings Data</legend><button id="gm_fullfeed_settings-etc_settingsimport">Import</button><button id="gm_fullfeed_settings-etc_settingsexport">Export</button><button id="gm_fullfeed_settings-etc_settingsreset">Reset</button></fieldset><fieldset><legend>Cache Data</legend><button id="gm_fullfeed_settings-etc_cachereset">Reset Cache</button><button id="gm_fullfeed_settings-etc_cachedelete">Delete Cache</button></fieldset><fieldset><legend>Replace Full Feed URL : </legend><input type="text" id="gm_fullfeed_settings-etc_replacefullfeedurl"></fieldset>`,
        },
      ];
      list.forEach(i => {
        const h3 = document.createElement('h3');
        h3.className = 'gm_fullfeed_settings-title';
        h3.innerHTML = i.tab;
        $ids('tab').appendChild(h3);
        const li = document.createElement('li');
        li.id = `gm_fullfeed_settings-${i.id}`;
        li.style.display = 'none';
        li.innerHTML = i.body;
        $ids('list').appendChild(li);
      });
      const eList = document.querySelector('.LeftnavList__feed-list + div');
      let eItem;
      if (eList) {
        eItem = document.createElement('div');
        eItem.className = 'LeftnavListRow';
        eItem.innerHTML = `<i class="icon icon-sm icon-tertiary LeftnavListRow__icon"></i><span id="gm_fullfeed_settings-menu" class="LeftnavListRow__text LeftnavListRow__text--bold">Full Feed ${loc[21]}</span>`;
        eList.appendChild(eItem);
      }
      $ids('general_key').addEventListener(
        'keypress',
        e1 => {
          e1.preventDefault();
        },
        false
      );
      $ids('autoload_mode').addEventListener(
        'change',
        () => {
          const si = $ids('autoload_mode').selectedIndex;
          if (si === 1) {
            $ids('autoload_blacklist').style.display = 'block';
            $ids('autoload_whitelist').style.display = 'none';
          } else if (si === 2) {
            $ids('autoload_blacklist').style.display = 'none';
            $ids('autoload_whitelist').style.display = 'block';
          } else {
            $ids('autoload_blacklist').style.display = 'none';
            $ids('autoload_whitelist').style.display = 'none';
          }
        },
        false
      );
    }
    static viewSettings(id) {
      if ($id('gm_fullfeed_settings') && $id('gm_fullfeed_settings').style.display === 'block') {
        $id('gm_fullfeed_settings').style.display = 'none';
        return;
      }
      const tab = $ids('tab').childNodes;
      const list = $ids('list').childNodes;
      $ids('general_key').value = st.basekey;
      $ids('general_keydesc').textContent = isNaN(st.basekey) ? loc[24] : `(${loc[25]})`;
      $ids('general_autopagerize').checked = st.autopagerize ? true : false;
      $ids('general_apheight').value = st.apheight;
      $ids('general_autosearch').checked = st.autosearch ? true : false;
      $ids('general_cantdisplay').checked = st.cantdisplay ? true : false;
      $ids('general_notread').value = st.notread;
      $ids('general_openitem').checked = st.openitem ? true : false;
      const siff = $ids('siteinfo_fullfeed');
      const siap = $ids('siteinfo_autopagerize');
      const siuff = $ids('siteinfo_userfullfeed');
      const siuap = $ids('siteinfo_userautopagerize');
      const beautifier = function (str) {
        try {
          return JSON.stringify(str)
            .replace(/^\[/g, '[\n')
            .replace(/\{"/g, '  {\n    "')
            .replace(/"\}/g, '"\n  }')
            .replace(/\},/g, '},\n')
            .replace(/",/g, '",\n    ')
            .replace(/\{"/g, '{\n    "')
            .replace(/\]$/g, '\n]');
        } catch (er) {
          console.log('viewSettings', er);
          return '';
        }
      };
      siff.value = beautifier(siteInfo);
      siap.value = beautifier(siteInfoAP);
      siuff.value = beautifier(userSiteInfo);
      siuap.value = beautifier(userSiteInfoAP);
      let chklist = '';
      itemSiteInfo.forEach(a => {
        chklist += `<label title="${a.url}"><input type="checkbox"`;
        if (itemSiteInfo.length === 1) chklist += ' disabled="disabled"';
        else {
          st.disableitem.forEach(b => {
            if (a.url === b) chklist += ' checked';
          });
        }
        chklist += `>${a.url}</label><br>`;
      });
      $ids('siteinfo_disableitemlist').innerHTML = chklist;
      if (st.disableitem) $ids('siteinfo_disableitem').value = st.disableitem.join('\n');
      let title;
      $ids('autoload_mode').selectedIndex = st.autoload;
      try {
        title = document.querySelector(`${returnSelectorEntry()} .EntryMetadataBasic__source-info > a`)?.textContent;
      } catch (er) { console.log(er); }
      if (title) {
        title = title.replace(/([*+?.()[\]\\|^$])/g, '\\$1');
        $ids('autoload_whitelist_title').value = `^${title}$`;
        $ids('autoload_blacklist_title').value = `^${title}$`;
      }
      if (st.autoloadfeed) $ids('autoload_whitelist_list').value = st.autoloadfeed.join('\n');
      if (st.notloadfeed) $ids('autoload_blacklist_list').value = st.notloadfeed.join('\n');
      for (let i = 0; i < tab.length; i++) {
        if (tab[i].classList.contains('selected')) {
          tab[i].classList.remove('selected');
          list[i].style.display = 'none';
        }
      }
      $ids('autoload_whitelist').style.display = 'none';
      $ids('autoload_blacklist').style.display = 'none';
      if (st.autoload === 1) $ids('autoload_blacklist').style.display = 'block';
      else if (st.autoload === 2) $ids('autoload_whitelist').style.display = 'block';
      $ids('security_noscripttag').checked = st.noscripttag ? true : false;
      $ids('security_iframetag').checked = st.iframetag ? true : false;
      $ids('security_allowiframeurl').value = st.allowiframeurl;
      $ids('security_denynexturl').value = st.denynexturl;
      $ids('security_allownexturl').value = st.allownexturl;
      if ($ids('security_iframetag').checked) {
        $ids('security_allowiframeurl').disabled = false;
        $ids('security_allowiframeurl').parentNode.style.color = 'black';
      } else {
        $ids('security_allowiframeurl').disabled = true;
        $ids('security_allowiframeurl').parentNode.style.color = 'gray';
      }
      $ids('social_socialicon').checked = st.socialicon ? true : false;
      $ids('social_hatena').checked = st.socialhatena ? true : false;
      ['hatena'].forEach(a => {
        if ($ids('social_socialicon').checked) {
          $ids(`social_${a}`).disabled = false;
          $ids(`social_${a}`).parentNode.style.color = 'black';
        } else {
          $ids(`social_${a}`).disabled = true;
          $ids(`social_${a}`).parentNode.style.color = 'gray';
        }
      });
      $ids('etc_replacefullfeedurl').value = st.replacefullfeedurl;

      if (id) {
        //※ createSettings()のlistと並び順を合わせること
        const idlist = ['general', 'siteinfo', 'autoload', 'security', 'social', 'etc'];
        idlist.forEach((s, i) => {
          if (id === s) {
            tab[i].classList.add('selected');
            list[i].style.display = 'block';
          }
        });
      } else {
        tab[0].classList.add('selected');
        list[0].style.display = 'block';
      }
      $id('gm_fullfeed_settings').style.display = 'block';
    }
    static checkRegister() {
      window.setTimeout(() => {
        const currentItem = getActiveItem();
        if (currentItem && currentItem.url) {
          FullFeed.registerWidgets();
          FullFeed.registerSocialIcons();
        }
      }, 10);
    }
    static registerWidgets() {
      const c = new GetCurrentItem();
      let flag = false;
      if (!c.innerContents) return;
      const se = returnSelectorEntry(),
        el = document.querySelectorAll(`${se} .EntryMetadataWrapper > span`);
      if (el) {
        for (let i = 0; i < el.length; i++) {
          if (el[i].classList.contains('gm_fullfeed_checked')) {
            flag = true;
            break;
          }
        }
      }
      if (flag) return;
      const container = document.querySelector(`${se} .EntryMetadataWrapper`);
      if (!container) return;
      itemSiteInfo = [];
      const description = `${loc[40]} : ${loc[18]} / Ctrl+${loc[40]} : ${loc[39]}`;
      const feed = c.feed;
      let bFound = false;
      const check = function (b) {
        bFound = true;
        const icon = document.createElement('span');
        icon.title = description;
        icon.innerHTML = 'F';
        icon.classList.add('gm_fullfeed_checked');
        icon.classList.add('gm_fullfeed_checked_icon');
        if (b) icon.classList.add('gm_fullfeed_checked_icon_info');
        else icon.classList.add('gm_fullfeed_checked_icon_noinfo');
        container.appendChild(icon);
        if (st.autoload === 0) initFullFeed();
        else if (st.autoload === 1 && feed.title) {
          if (
            st.notloadfeed.some(i => {
              if (!i) return false;
              if (new RegExp(i).test(feed.title)) return true;
            })
          ) {
            flag = true;
          }
          if (!flag) initFullFeed();
        } else if (st.autoload === 2 && feed.title) {
          if (
            st.autoloadfeed.some(i => {
              if (!i) return false;
              if (new RegExp(i).test(feed.title)) return true;
            })
          ) {
            initFullFeed();
          }
        }
      };
      userSiteInfo.some(i => {
        try {
          const reg = new RegExp(i.url);
          if (i.url && (reg.test(c.itemURL) || reg.test(feed.url))) {
            check(true);
            return true;
          }
        } catch (er1) {
          console.log('registerWidgets 1', er1, i.url);
        }
      });
      if (!bFound) {
        if (
          userCacheList.some(i => {
            if (i === feed.url) {
              return true;
            }
          })
        ) {
          userCache.some(i => {
            try {
              const reg = new RegExp(i.url);
              if (i.url && (reg.test(c.itemURL) || reg.test(feed.url))) {
                check(true);
                return true;
              }
            } catch (er2) {
              console.log('registerWidgets 2', er2, i.url);
            }
          });
        }
      }
      if (!bFound) {
        siteInfo.some(ctx => {
          if (cacheInfo[ctx.url]) {
            cacheInfo[ctx.url].info.some(i => {
              try {
                const reg = new RegExp(i.url);
                if (i.url && (reg.test(c.itemURL) || reg.test(feed.url))) {
                  check(true);
                  return true;
                }
              } catch (er3) {
                console.log('registerWidgets 3', er3, i.url);
              }
            });
          }
        });
      }
      if (!bFound) check();
    }
    static registerSocialIcons() {
      if (!st.socialicon) return;
      const c = new GetCurrentItem();
      if (!c.innerContents) return;
      const se = returnSelectorEntry(),
        el = document.querySelector(`${se} .EntryMetadataWrapper > .gm_fullfeed_socialicon`);
      if (el) return;
      const container = document.querySelector(`${se} .EntryMetadataWrapper`);
      const itemScheme = c.itemURL.slice(0, c.itemURL.indexOf('://') + 3);
      const itemUrl2 = c.itemURL.slice(c.itemURL.indexOf('://') + 3).replace(/#/g, '%23');
      if (!container) return;
      const hatena = {
        method: 'GET',
        url: `http://api.b.st-hatena.com/entry.count?url=${itemScheme}${itemUrl2}`,
        onload: function (res) {
          if (res.status === 200) {
            const total = Number(res.responseText);
            if (isNaN(total) || total <= 0) return;
            const str = total > 1 ? ' users' : ' user';
            const style = total < 10 ? '1' : total < 100 ? '2' : total < 1000 ? '3' : '4';
            const ssl = itemScheme === 'https://' ? 's/' : '';
            sH.innerHTML = `<a href="http://b.hatena.ne.jp/entry/${ssl}${itemUrl2}" target="_blank" title="${loc[19]}: ${total}${str}" class="gm_fullfeed_hatena gm_fullfeed_hatena${style}"><img src="http://b.hatena.ne.jp/favicon.ico"><span>${total}</span></a>`;
          }
        },
      };
      const ce = function () {
        return document.createElement('span');
      };
      const sT = ce(),
        sD = ce(),
        sF = ce(),
        sH = ce(),
        sL = ce(),
        sY = ce(),
        sB = ce(),
        sC = ce();
      sT.className = sD.className = sF.className = sH.className = sL.className = sY.className = sB.className = sC.className = 'gm_fullfeed_socialicon';
      const ac1 = function (s, f) {
        container.appendChild(s);
        GM_xmlhttpRequest(f);
      };
      if (st.socialhatena) ac1(sH, hatena);
      let inter,
        repeat = 0;
      const check = function () {
        const e = document.querySelectorAll(`${se} .EntryMetadataWrapper > .gm_fullfeed_socialicon > a > img`);
        let f = true;
        repeat += 1;
        if (e) {
          e.forEach(i => {
            if (i.clientHeight === 1) i.parentNode?.parentNode?.removeChild(i.parentNode);
            else if (i.clientHeight === 0 && repeat < 10) f = false;
          });
        }
        if (f) window.clearInterval(inter);
      };
      inter = window.setInterval(() => {
        check();
      }, 1000);
    }
    request(m, c) {
      if (!this.requestURL) return;
      state = 'request';
      const self = this;
      let retry = 0;
      const opt = {
        method: m,
        url: this.requestURL,
        overrideMimeType: this.mime,
        onerror: function () {
          state = null;
          self.requestError.apply(self, ['Request Error']);
        },
        onload: function (res) {
          if ((res.status >= 301 && res.status <= 303) || res.status === 307) {
            try {
              opt.url = /\sLocation:\s+(\S+)/.exec(res.responseHeaders)[1];
              GM_xmlhttpRequest(opt);
              return;
            } catch (er) {
              console.log('request', er);
              state = null;
              self.requestError.apply(self, [`Request Error : ${res.status}`]);
              return;
            }
          } else if (res.status === 200) {
            if (res.finalUrl) opt.url = this.requestURL = res.finalUrl;
            readingPageUrl = c.innerContents.className
              .split(/\s+/)
              .filter(i => {
                return new RegExp(/^http/).test(i);
              })
              .slice(-1);
            readingPageUrl = readingPageUrl.length ? readingPageUrl.toString() : opt.url;
            const rh = /content-type:\s?\S+\s?charset=\S+/i.exec(res.responseHeaders);
            let cset = (rh && rh[0]) ? rh[0] : '';
            if (cset) cset = cset.slice(cset.lastIndexOf('charset=') + 1);
            if (opt.method === 'GET') {
              if (cset && !new RegExp(cset, 'i').test(self.info.enc) && retry < 2) {
                self.info.enc = cset;
                opt.overrideMimeType = `text/html; charset=${cset}`;
                GM_xmlhttpRequest(opt);
                retry += 1;
              } else {
                const h1 = readingPageUrl.split('/')[2],
                  h2 = opt.url.split('/')[2];
                const re = new RegExp(st.allownexturl, 'i');
                if (h1 === h2 || (st.allownexturl && re.test(readingPageUrl))) {
                  self.requestLoad.call(self, res, c);
                } else if (window.confirm(loc[59] + readingPageUrl + loc[60] + opt.url)) {
                  self.requestLoad.call(self, res, c);
                } else self.requestEnd(c, false, true);
              }
            } else if (opt.method === 'HEAD') {
              if (cset && st.autosearch) {
                self.info.enc = cset;
                opt.method = 'GET';
                opt.overrideMimeType = `text/html; charset=${cset}`;
                message(loc[1], -1);
                loadingStyle('add', c.articleContainer);
                GM_xmlhttpRequest(opt);
              } else {
                state = null;
                if (st.cantdisplay) {
                  message(loc[10], 2000);
                  loadingStyle('open', c.articleContainer);
                  window.setTimeout(() => {
                    GM_openInTab(opt.url, true);
                  }, 10);
                  window.setTimeout(() => {
                    loadingStyle('remove', c.articleContainer);
                  }, 1000);
                } else message(loc[74], 3000);
              }
            }
          } else {
            state = null;
            const stat = res.status ? ` : ${res.status}` : '';
            self.requestError.apply(self, [`Request Error${stat}`]);
          }
        },
      };
      if (opt.method === 'HEAD') message(loc[54], -1);
      else if (opt.method === 'GET') {
        message(loc[0], -1);
        loadingStyle('add', c.articleContainer);
      }
      if (opt.url.indexOf('http:') !== 0 && this.info.base) {
        opt.url = pathToURL(this.info.base, opt.url);
      }
      GM_xmlhttpRequest(opt);
    }
    requestLoad(res, c) {
      state = 'loading';
      let text = res.responseText;
      const html = createHTMLDocumentByString(text);
      const tmpElm = this.info.xpath ? getElementsByXPath(this.info.xpath, html) : null;
      if (tmpElm) {
        let doc,
          tmpNode = document.createDocumentFragment().appendChild(document.createElement('div'));
        try {
          doc = document.cloneNode(false);
          doc.appendChild(doc.importNode(document.documentElement, false));
        } catch (er1) {
          console.log('requestLoad 1', er1);
          doc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
        }
        try {
          tmpNode = doc.adoptNode(tmpNode);
        } catch (er2) {
          console.log('requestLoad 2', er2);
          tmpNode = doc.importNode(tmpNode, true);
        }
        tmpElm.forEach(elm => {
          tmpNode.appendChild(elm);
        });
        if (tmpNode.innerHTML) text = tmpNode.innerHTML;
      }
      ['head', 'isindex', 'link', 'script', 'style', 'title'].forEach(t => {
        const r = new RegExp(`<(?:!--\\s*)?${t}(?:\\s[^>]+?)?>[\\S\\s]*?<\\/${t}\\s*>`, 'gi');
        text = text.replace(r, '');
      });
      const re =
        /(<[^>]+?[\s"'])(?:on(?:(?:un)?load|(?:dbl)?click|mouse(?:down|up|over|move|out)|key(?:press|down|up)|focus|blur|submit|reset|select|change|error)|submit|target|usemap|formaction)\s*=\s*(?:"(?:\\"|[^"])*"?|'(\\'|[^'])*'?|[^\s>]+(?=[\s>]|<\w))(?=[^>]*?>|<\w|\s*$)/gi;
      while (re.test(text)) {
        text = text.replace(re, '$1');
      }
      if (st.noscripttag) text = text.replace(/<noscript(?:\s[^>]+?)?>([\S\s]*?)<\/noscript\s*>/gi, '<div>$1</div>');
      else text = text.replace(/<noscript(?:\s[^>]+?)?>([\S\s]*?)<\/noscript\s*>/gi, '');
      const htmldoc = createHTMLDocumentByString(text);
      if (st.iframetag) {
        const iframe = getElementsByXPath('//iframe', htmldoc);
        if (iframe) {
          iframe.forEach(a => {
            if (!st.allowiframeurl || (a.src && !new RegExp(st.allowiframeurl).test(a.src))) a.parentNode.removeChild(a);
          });
        }
      }
      removeXSSRisks(htmldoc);
      if (res.finalUrl) this.requestURL = res.finalUrl;
      relativeToAbsolutePath(htmldoc, this.requestURL);
      const self = this;
      let entry;
      FullFeed.documentFilters.forEach(filter => {
        filter(htmldoc, self.requestURL, self.info);
      });
      try {
        entry = getElementsByXPath('//html/child::node()', htmldoc);
      } catch (er1) {
        message(er1, 5000, 'warning');
        return;
      }
      if (!tmpElm) {
        if (st.autosearch) {
          if (debugLog) {
            try {
              console.log('Auto Search');
            } catch (er2) { console.log(er2); }
          }
          message(loc[1], -1);
          entry = searchEntry(htmldoc);
        } else {
          state = null;
          window.setTimeout(() => {
            GM_openInTab(self.requestURL, true);
          }, 10);
          window.setTimeout(() => {
            loadingStyle('remove', c.articleContainer);
          }, 1000);
          return message(loc[10], 2000);
        }
      }
      if (entry) {
        if (c.innerContents && !c.innerContents.classList.contains('gm_fullfeed_loaded')) {
          this.removeEntry();
        }
        entry = this.addEntry(entry, c);
        if (!tmpElm && st.autosearch) this.requestEnd(c, true);
        else this.requestEnd(c);
      } else {
        state = null;
        this.requestError(loc[3]);
      }
    }
    requestEnd(c, as, halt) {
      state = 'loaded';
      window.setTimeout(() => {
        state = 'wait';
      }, 1000);
      loadingStyle('remove', c.articleContainer);
      c.innerContents.classList.add(this.info.enc || document.characterSet);
      c.innerContents.classList.remove('entry-body-empty');
      c.innerContents.classList.add('gm_fullfeed_loaded');
      c.innerContents.classList.toggle(this.requestURL);
      const el = document.querySelector(`${returnSelectorEntry()} .gm_fullfeed_checked_icon`);
      FullFeed.checkNextPage(c.innerContents);
      if (
        c.innerContents.className.split(/\s+/).filter(i => {
          return new RegExp(/^http/).test(i);
        }).length > 1
      ) {
        if (nextPageLink) {
          if (as) message(loc[34], 3000);
          else message(loc[5]);
        } else {
          if (as) message(loc[35], 3000);
          else message(loc[6]);
          if (el) {
            el.classList.remove('gm_fullfeed_checked_icon_next');
            el.classList.remove('gm_fullfeed_checked_icon_as_next');
            if (as) el.classList.add('gm_fullfeed_checked_icon_as_nonext');
            else el.classList.add('gm_fullfeed_checked_icon_nonext');
            el.title = `Ctrl+${loc[40]} : ${loc[39]}`;
          }
        }
      } else {
        if (as) message(loc[51], 3000);
        else message(loc[2]);
        if (el) {
          el.classList.remove('gm_fullfeed_checked_icon_info');
          el.classList.remove('gm_fullfeed_checked_icon_noinfo');
          if (nextPageLink) {
            if (as) el.classList.add('gm_fullfeed_checked_icon_as_next');
            else el.classList.add('gm_fullfeed_checked_icon_next');
            el.title = `${loc[40]} : ${loc[41]} / Ctrl+${loc[40]} : ${loc[39]}`;
            if (st.autopagerize) {
              window.setTimeout(() => {
                FullFeed.checkScroll();
              }, 2000);
            }
          } else {
            if (as) el.classList.add('gm_fullfeed_checked_icon_as_nonext');
            else el.classList.add('gm_fullfeed_checked_icon_nonext');
            el.title = `Ctrl+${loc[40]} : ${loc[39]}`;
          }
        }
      }
      if (halt) message(loc[61], 3000);
    }
    requestError(e) {
      state = null;
      message(`Error: ${e}`, 5000, 'warning');
      loadingStyle('remove', this.itemInfo.articleContainer);
      this.itemInfo.innerContents.classList.add('gm_fullfeed_error');
    }
    removeEntry() {
      if (this.itemInfo && this.itemInfo.itemBody) {
        while (this.itemInfo.itemBody.firstChild) {
          this.itemInfo.itemBody.removeChild(this.itemInfo.itemBody.firstChild);
        }
      }
    }
    addEntry(entry, c) {
      const url = this.requestURL || c.itemURL;
      const ic = c.innerContents.id ? $id(c.innerContents.id) : c.innerContents;
      const div = document.createElement('div');
      if (ic && ic.hasChildNodes()) {
        div.className = 'gm_fullfeed_pager';
        const http = c.innerContents.className.split(/\s+/).filter(i => {
          return new RegExp(/^http/).test(i);
        });
        const host = readingPageUrl.split('/')[2] !== url.split('/')[2] ? `<span class="gm_fullfeed_pager_differenthost">( ${readingPageUrl.split('/')[2]} &rarr; ${url.split('/')[2]} )</span>` : '';
        div.innerHTML = `<hr />page: <a href="${url}" target="_blank">${http.length + 1}</a>${host}`;
        ic.appendChild(div);
      } else {
        div.className = 'gm_fullfeed_entry_url';
        div.innerHTML = `<a href="${url}" target="_blank">${url}</a>`;
        try {
          if (ic) ic.parentNode.insertBefore(div, ic);
        } catch (er) {
          message(`Error: ${er}`, 5000, 'warning');
        }
      }
      return entry.map(i => {
        const pe = document.importNode(i, true);
        if (ic) ic.appendChild(pe);
        return pe;
      });
    }
    load(flag) {
      if (flag === 'search') this.request('HEAD', this.itemInfo);
      else this.request('GET', this.itemInfo);
    }
  }

  FullFeed.documentFilters = [
    // addTargetAttr
    function (doc) {
      const anchors = getElementsByXPath('descendant-or-self::a', doc);
      if (anchors) {
        anchors.forEach(i => {
          i.target = '_blank';
        });
      }
    },
  ];

  const getActiveItem = function () {
    const item = {};
    const exp = document.querySelector(`${returnSelectorEntry()} .entryHeader > a`);
    if (exp) {
      item.url = exp.href && /^(https?|ftp):\/\/.+$/.test(exp.href) ? exp.href : '';
      item.title = exp.textContent ? exp.textContent : '';
    }
    return item;
  };

  const getActiveFeed = function () {
    const feed = {};
    feed.url = '';
    feed.title = '';
    try {
      const est = document.querySelector(`${returnSelectorEntry()} .EntryMetadataBasic__source-info > a`);
      if (est) {
        feed.url = decodeURIComponent(est.href.replace(/^.*\/(?=http)/, ''));
        feed.title = est.textContent;
      }
    } catch (e) { console.log(e); }
    return feed;
  };

  // itemの情報を格納するobjectのconstructor
  class GetCurrentItem {
    constructor() {
      this.item = getActiveItem();
      this.feed = getActiveFeed();
      this.itemURL = this.item.url;
      let u = this.itemURL;
      const se = returnSelectorEntry();
      if (/^https?:\/\/www\.feedly\.com\/home#subscription\/feed\/https?:\/\/.+/.test(u)) {
        u = u.slice(u.indexOf('/feed/') + 6);
        this.itemURL = decodeURIComponent(u.slice(0, u.indexOf('&')));
      } else if (/^http:\/\/rd\.yahoo\.co\.jp\/rss\/l\/.+/.test(u)) {
        if (/\/\*-http/.test(u)) {
          this.itemURL = decodeURIComponent(u.slice(u.indexOf('*-http') + 2));
        } else if (/\/\*http/.test(u)) {
          this.itemURL = decodeURIComponent(u.slice(u.indexOf('*http') + 1));
        }
      }
      this.articleContainer = document.querySelector(se);
      this.innerContents = document.querySelector(`${se} .entryBody`);
      this.found = false;
    }
  }

  const launchFullFeed = function (list, c, flag) {
    if (typeof list.some !== 'function') return;
    state = 'launch';
    itemSiteInfo = [];
    let bCache, bList;
    const check = function (lis) {
      lis.forEach(i => {
        try {
          const reg = new RegExp(i.url);
          if (i.url && (i.url.length <= 12 || !/^\^?https?/i.test(i.url))) {
            if (new RegExp('^://', 'i').test(i.url)) i.url = `^https?${i.url}`;
            else if (new RegExp('^//', 'i').test(i.url)) i.url = `^https?:${i.url}`;
            else i.url = `^https?://${i.url}`;
          }
          if ((reg.test(c.itemURL) || reg.test(c.feed.url)) && i.url.length > 12) {
            if (flag === 'cache') {
              if (
                userCacheList.some(u => {
                  if (u === c.feed.url) {
                    return true;
                  }
                })
              ) {
                c.found = true;
                itemSiteInfo.push(i);
              }
            } else {
              c.found = true;
              itemSiteInfo.push(i);
            }
          }
        } catch (er1) {
          console.log('check', er1, i.url);
        }
      });
    };
    check(list);
    if (/^next|^search/.test(flag)) {
      if (!c.found) {
        siteInfoAP.some(ctx => {
          if (cacheAPInfo[ctx.url]) {
            check(cacheAPInfo[ctx.url].info);
            return c.found;
          }
        });
      }
      if (!c.found) check([{ url: nextPageLink.href, xpath: '' }]);
    }
    nextPageLink = null;
    itemSiteInfo.sort((a, b) => {
      const reA = new RegExp(a.url);
      const reB = new RegExp(b.url);
      const a1 = reA.test(c.itemURL) ? reA.source.length : 0;
      const b1 = reB.test(c.itemURL) ? reB.source.length : 0;
      const a2 = reA.test(c.feed.url) ? reA.source.length : 0;
      const b2 = reB.test(c.feed.url) ? reB.source.length : 0;
      if (a.url.search(/\\w|\.\+|\.\*|\[\^\.?\/?\]\+/) !== -1) return 1;
      else if (b.url.search(/\\w|\.\+|\.\*|\[\^\.?\/?\]\+/) !== -1) return -1;
      return (b1 >= b2 ? b1 : b2) - (a1 >= a2 ? a1 : a2);
    });
    const type = ['^IND', '^SUB', '^GEN'];
    itemSiteInfo.sort((a, b) => {
      let n1 = 0,
        n2 = 0;
      type.forEach((d, i) => {
        if (new RegExp(d).test(a.type)) n1 = i;
        if (new RegExp(d).test(b.type)) n2 = i;
      });
      return n1 - n2;
    });
    if (flag === 'full') {
      if (!bCache && itemSiteInfo.length > 0) {
        userCache.some(u => {
          if (u === itemSiteInfo[0]) {
            bCache = true;
            return true;
          }
        });
        userCacheList.some(u => {
          if (u === c.feed.url) {
            bList = true;
            return true;
          }
        });
        if (!bCache) {
          delete itemSiteInfo[0].base;
          delete itemSiteInfo[0].microformats;
          delete itemSiteInfo[0].priority;
          userCache.push(itemSiteInfo[0]);
          try {
            localStorage.setItem('FeedlyFullFeed_userCache', JSON.stringify(userCache));
          } catch (er2) { console.log(er2); }
        }
        if (!bList) {
          userCacheList.push(c.feed.url);
          try {
            localStorage.setItem('FeedlyFullFeed_userCacheList', JSON.stringify(userCacheList));
          } catch (er3) { console.log(er3); }
        }
      }
    }
    if (c.found && itemSiteInfo.length > 0) {
      let data, bool, info;
      if (
        itemSiteInfo.some(a => {
          data = a;
          bool = true;
          st.disableitem.forEach(b => {
            if (a.url === b) bool = false;
          });
          if (bool) return true;
        })
      ) {
        info = data;
      } else {
        info = itemSiteInfo[0];
      }
      const fullfeed = new FullFeed(info, c, flag);
      fullfeed.load(flag);
    } else state = null;
  };

  const initFullFeed = function (scroll) {
    const c = new GetCurrentItem();
    if ((state && state !== 'loaded' && state !== 'wait') || !c.item.title || !c.innerContents) return;
    message(loc[54], -1);
    if (st.notread) {
      const re = new RegExp(st.notread);
      if (re.test(c.item.title) || re.test(c.item.url) || re.test(c.feed.title)) {
        if (st.openitem) {
          loadingStyle('open', c.articleContainer);
          window.setTimeout(() => {
            GM_openInTab(c.itemURL, true);
          }, 10);
          window.setTimeout(() => {
            loadingStyle('remove', c.articleContainer);
          }, 1000);
          return message(loc[49], 2000);
        }
        return message(loc[8]);
      }
    }
    const launch = function () {
      if (c.innerContents && c.innerContents.classList.contains('gm_fullfeed_loaded')) {
        if (st.autopagerize) {
          FullFeed.checkNextPage(c.innerContents);
          if (nextPageLink) {
            c.itemURL = nextPageLink.href;
            launchFullFeed(userSiteInfo, c, 'set');
            if (!c.found) launchFullFeed(userCache, c, 'cache');
            if (!c.found) {
              siteInfo.some(ctx => {
                if (cacheInfo[ctx.url]) {
                  launchFullFeed(cacheInfo[ctx.url].info, c, 'next');
                  return c.found;
                }
              });
            }
            if (!c.found) launchFullFeed([], c, 'search');
            loadingStyle('add', c.articleContainer);
            return message(loc[4], -1);
          } else if (!scroll) {
            if (
              c.innerContents.className.split(/\s+/).filter(i => {
                return new RegExp(/^http/).test(i);
              }).length > 1
            ) {
              return message(loc[7]);
            }
            return message(loc[9]);
          }
        } else if (!scroll) return message(loc[9]);
      } else {
        if (scroll) return;
        launchFullFeed(userSiteInfo, c, 'set');
        if (!c.found) launchFullFeed(userCache, c, 'cache');
        if (
          !c.found &&
          !siteInfo.some(ctx => {
            if (cacheInfo[ctx.url]) {
              launchFullFeed(cacheInfo[ctx.url].info, c, 'full');
              return c.found;
            }
          })
        ) {
          const fullfeed = new FullFeed({}, c, 'search');
          fullfeed.load('search');
        }
      }
    };
    const unshortenit = function (u) {
      GM_xmlhttpRequest({
        method: 'GET',
        url: `http://www.unshorten.it/api1.0.php?responseFormat=text&shortURL=${u}`,
        onload: function (res) {
          if (res.status === 200) {
            try {
              const longUrl = res.responseText;
              if (longUrl && /^https?:/.test(longUrl)) {
                c.itemURL = c.feed.url = longUrl;
              } else checkShortUrl();
            } catch (er) { console.log(er); }
            checkShortUrl();
          }
        },
        onerror: function () {
          checkShortUrl();
        },
      });
    };
    const checkShortUrl = function () {
      if (c.itemURL !== tempUrl) {
        tempUrl = c.itemURL;
        GM_xmlhttpRequest({
          method: 'HEAD',
          url: c.itemURL,
          onload: function (res) {
            if (res.status === 200 && res.finalUrl) {
              if (/^https?:\/\/t.co\/.+/.test(res.finalUrl)) unshortenit(res.finalUrl);
              else {
                c.itemURL = c.feed.url = res.finalUrl;
                launch();
              }
            } else if (((res.status >= 301 && res.status <= 303) || res.status === 307) && res.finalUrl) {
              unshortenit(res.finalUrl);
            } else launch();
          },
          onerror: function () {
            launch();
          },
        });
      } else launch();
    };
    if (!st.replacefullfeedurl) {
      launch();
      return;
    }
    let tempUrl = c.itemURL;
    const rep = st.replacefullfeedurl.replace(/\s+/g, ' ').split(' ');
    if (rep.length >= 2) {
      const excludeUrl = function (e) {
        e.some(a => {
          if (a.href.split('/')[2].indexOf('xn--') === -1 && /^https?:\/\/.+/.test(a.textContent)) {
            c.itemURL = c.feed.url = a.href;
          }
        });
      };
      for (let i = 0; i < rep.length - 1; i = i + 2) {
        if (rep[i] && new RegExp(rep[i]).test(c.itemURL)) {
          if (rep[i + 1] && /^http/.test(rep[i + 1])) {
            c.itemURL = c.itemURL.replace(new RegExp(rep[i]), rep[i + 1]);
            c.feed.url = c.itemURL;
          } else if (rep[i + 1] === 'Link') {
            try {
              const u = document.querySelectorAll(`${returnSelectorEntry()} .entryBody a`);
              if (u) excludeUrl(u);
            } catch (er) { console.log(er); }
          }
          break;
        }
      }
    } else launch();
    checkShortUrl();
  };

  const loadingStyle = function (flag, elm) {
    if (!elm) return;
    const s1 = 'gm_fullfeed_loading';
    const s2 = 'gm_fullfeed_opened';
    if (flag === 'add') elm.classList.add(s1);
    else if (flag === 'open') elm.classList.add(s2);
    else if (flag === 'remove') {
      elm.classList.remove(s1);
      elm.classList.remove(s2);
    }
  };

  const createMessageBox = function () {
    const div = document.createElement('div');
    div.id = 'gm_fullfeed_message';
    div.className = 'gm_fullfeed_hidden';
    div.innerHTML = '';
    document.body.appendChild(div);
  };

  const returnSelectorEntry = function () {
    //Article View
    if (document.querySelector('.InlineArticle-in-viewport')) {
      return '.InlineArticle-in-viewport';
    }
    //not use the right slider
    if (document.querySelector('.InlineArticle')) {
      return '.InlineArticle';
    }
    //use the right slider
    if (document.querySelector('.floatingEntryContent')) {
      return '.floatingEntryContent:last-child article';
    }
    return '.InlineArticle';
  };

  let debugLog = false,
    itemSiteInfo = [],
    messageInterval,
    scrollInterval,
    nextPageLink = null,
    readingPageUrl = null,
    state = null,
    st,
    siteInfo,
    siteInfoAP,
    userSiteInfo,
    userSiteInfoAP,
    cacheInfo,
    cacheAPInfo,
    userCache,
    userCacheAP,
    userCacheList,
    userCacheListAP;

  const init = function () {
    FullFeed.loadSettings();
    cacheInfo = FullFeed.getCache('cache');
    cacheAPInfo = FullFeed.getCache('cacheAP');
    userCache = FullFeed.getCache('userCache');
    userCacheAP = FullFeed.getCache('userCacheAP');
    userCacheList = FullFeed.getCache('userCacheList');
    userCacheListAP = FullFeed.getCache('userCacheListAP');
    if (GM_registerMenuCommand && typeof GM_registerMenuCommand === 'function') {
      GM_registerMenuCommand('Feedly Full Feed - reset cache', () => {
        if (window.confirm(loc[15])) FullFeed.resetCache();
      });
      GM_registerMenuCommand('Feedly Full Feed - delete cache', () => {
        FullFeed.removeCache();
      });
    }
    GM_addStyle(CSS);
    FullFeed.createSettings();
    createMessageBox();
    document.addEventListener(
      'keyup',
      e => {
        const key = isNaN(st.basekey) ? st.basekey.toUpperCase().charCodeAt(0) : Number(st.basekey),
          el1 = $ids('general_key');
        let el2,
          flag = false;
        if (e.target?.id === 'gm_fullfeed_settings-general_key') {
          el2 = $ids('general_keydesc');
          const code = e.keyCode;
          const cha = String.fromCharCode(code);
          if (/[A-Z]/.test(cha)) {
            el1.value = cha;
            el2.textContent = loc[24];
          } else if (code < 16 || code > 18) {
            el1.value = code;
            el2.textContent = loc[25];
          }
        } else if (e.keyCode === key && !/^input|^textarea/i.test(e.target?.tagName)) {
          if (e.altKey && e.ctrlKey && e.shiftKey) {
            if (window.confirm(loc[15])) FullFeed.resetCache();
          } else if (e.ctrlKey && e.shiftKey) {
            el2 = document.querySelectorAll(`${returnSelectorEntry()} .gm_fullfeed_checked`);
            if (el2) {
              el2.forEach(i => {
                if (i.classList.contains('gm_fullfeed_checked')) flag = true;
              });
            }
            if (flag) FullFeed.viewSettings('autoload');
            else FullFeed.viewSettings();
          } else if (e.ctrlKey) {
            if (st.autoload === 0) st.autoload = 1;
            else if (st.autoload === 1) st.autoload = 2;
            else if (st.autoload === 2) st.autoload = 3;
            else st.autoload = 0;
            FullFeed.saveSettings();
            message(`Auto Load : ${st.autoload === 0 ? loc[44] : st.autoload === 1 ? loc[77] : st.autoload === 2 ? loc[45] : loc[46]}`);
          } else if (e.shiftKey) {
            st.autopagerize = !st.autopagerize;
            FullFeed.saveSettings();
            message(`AutoPagerize : ${st.autopagerize ? '<span class="gm_fullfeed_ap_on">ON</span>' : '<span class="gm_fullfeed_ap_off">OFF</span>'}`);
          } else initFullFeed();
        }
        FullFeed.checkRegister();
      },
      false
    );
    document.addEventListener(
      'click',
      e => {
        if (e.button >= 2) return;
        if (e.button === 0 && e.target?.id && /^gm_fullfeed_settings-/.test(e.target.id)) {
          const set = $id('gm_fullfeed_settings');
          const wlTitle = $ids('autoload_whitelist_title');
          const wlList = $ids('autoload_whitelist_list');
          const blTitle = $ids('autoload_blacklist_title');
          const blList = $ids('autoload_blacklist_list');
          if (e.target.id === 'gm_fullfeed_settings-menu') {
            FullFeed.viewSettings();
          } else if (e.target.id === 'gm_fullfeed_settings-menu2' || e.target.id === 'gm_fullfeed_settings-menu2child') {
            $id('settings-button').blur();
            FullFeed.viewSettings();
          } else if (e.target.id === 'gm_fullfeed_settings-general_keyhelpicon') {
            if (e.target.nextSibling.style.display !== 'block') {
              e.target.nextSibling.style.display = 'block';
            } else e.target.nextSibling.style.display = 'none';
          } else if (e.target.id === 'gm_fullfeed_settings-autoload_whitelist_add') {
            if (wlTitle.value) {
              if (wlList.value) wlList.value += `\n${wlTitle.value}`;
              else wlList.value = wlTitle.value;
              wlTitle.value = null;
            }
          } else if (e.target.id === 'gm_fullfeed_settings-autoload_blacklist_add') {
            if (blTitle.value) {
              if (blList.value) blList.value += `\n${blTitle.value}`;
              else blList.value = blTitle.value;
              blTitle.value = null;
            }
          } else if (e.target.className === 'gm_fullfeed_settings-siteinfo_link') {
            getElementsByXPath('id("gm_fullfeed_settings-siteinfo")//fieldset', document).forEach(i => {
              i.style.display = 'none';
            });
            getElementsByXPath('//span[contains(concat(" ", @class, " "), " gm_fullfeed_settings-siteinfo_link ")]', document).forEach(i => {
              if (i.classList.contains('selected')) i.classList.remove('selected');
            });
            e.target.classList.add('selected');
            $id(e.target.id.slice(0, -5)).parentNode.style.display = 'block';
          } else if (e.target.id === 'gm_fullfeed_settings-security_iframetag') {
            if ($ids('security_iframetag').checked) {
              $ids('security_allowiframeurl').disabled = false;
              $ids('security_allowiframeurl').parentNode.style.color = 'black';
            } else {
              $ids('security_allowiframeurl').disabled = true;
              $ids('security_allowiframeurl').parentNode.style.color = 'gray';
            }
          } else if (e.target.id === 'gm_fullfeed_settings-social_socialicon') {
            ['hatena'].forEach(a => {
              if ($ids('social_socialicon').checked) {
                $ids(`social_${a}`).disabled = false;
                $ids(`social_${a}`).parentNode.style.color = 'black';
              } else {
                $ids(`social_${a}`).disabled = true;
                $ids(`social_${a}`).parentNode.style.color = 'gray';
              }
            });
          } else if (e.target.id === 'gm_fullfeed_settings-etc_settingsexport') {
            if (typeof GM_setClipboard === 'function') {
              if (window.confirm(loc[66])) {
                GM_setClipboard(JSON.stringify(st));
                message(loc[67], 3000);
              }
            } else window.confirm(loc[68] + JSON.stringify(st));
          } else if (e.target.id === 'gm_fullfeed_settings-etc_settingsimport') {
            const imp = window.prompt(loc[69]);
            try {
              if (imp && imp.length > 100 && JSON.parse(imp)) {
                FullFeed.loadSettings(JSON.parse(imp));
                FullFeed.saveSettings();
                set.style.display = 'none';
                message(loc[70], 3000);
              }
            } catch (er1) {
              console.log('init 1', er1);
              message(loc[71], 3000, 'warning');
            }
          } else if (e.target.id === 'gm_fullfeed_settings-etc_settingsreset') {
            if (window.confirm(loc[72])) {
              FullFeed.loadSettings('reset');
              FullFeed.saveSettings();
              set.style.display = 'none';
              message(loc[73], 3000);
            }
          } else if (e.target.id === 'gm_fullfeed_settings-etc_cachereset') {
            if (window.confirm(loc[15])) FullFeed.resetCache();
          } else if (e.target.id === 'gm_fullfeed_settings-etc_cachedelete') {
            FullFeed.removeCache();
          } else if (e.target.id === 'gm_fullfeed_settings-ok') {
            const key = $ids('general_key'),
              hei = $ids('general_apheight'),
              v = key.value,
              h = hei.value;
            let problem = false;
            if (v && ((isNaN(v) && v.length === 1) || (!isNaN(v) && (v < 16 || v > 18)))) st.basekey = v;
            st.autopagerize = $ids('general_autopagerize').checked;
            if (h && !isNaN(h)) st.apheight = h < 200 ? 200 : h > 2000 ? 2000 : h;
            st.autosearch = $ids('general_autosearch').checked;
            st.cantdisplay = $ids('general_cantdisplay').checked;
            st.notread = $ids('general_notread').value;
            st.openitem = $ids('general_openitem').checked;
            const rep = function (str) {
              return str
                .replace(/^(\s*\{)/g, '[$1')
                .replace(/(\}),?(\s*)$/g, '$1$2]')
                .replace(/\\\\/g, '\\')
                .replace(/\\/g, '\\\\')
                .replace(/\\\\\/\\\\\//g, '\\/\\/')
                .replace(/"/g, "'")
                .replace(/(\{\s*|:\s*|'\s*,\s*|\}\s*,\s*)'/g, '$1"')
                .replace(/'(\s*:|\s*,\s*"|\s*,?\s*\})/g, '"$1')
                .replace(/("\s*),(\s*\})/g, '$1$2')
                .replace(/\}(\s*)\{/g, '},$1{');
            };
            const siff = $ids('siteinfo_fullfeed');
            const siap = $ids('siteinfo_autopagerize');
            const siuff = $ids('siteinfo_userfullfeed');
            const siuap = $ids('siteinfo_userautopagerize');
            if (!siff.value) siff.value = '[{"format": "JSON", "url": "http://wedata.net/databases/LDRFullFeed/items.json"}]';
            if (!siap.value) siap.value = '[{"format": "JSON", "url": "http://wedata.net/databases/AutoPagerize/items.json"}]';
            if (!siuff.value) siuff.value = '[{"url": "", "xpath": "", "type": "", "enc": ""}]';
            if (!siuap.value) siuap.value = '[{"url": "", "nextLink": "", "pageElement": ""}]';
            try {
              siteInfo = JSON.parse(rep(siff.value));
              st.siteinfo = JSON.stringify(siteInfo);
              siff.parentNode.style.backgroundColor = null;
            } catch (er2) {
              console.log('init 2', er2);
              problem = true;
              siff.parentNode.style.backgroundColor = '#FCC';
            }
            try {
              siteInfoAP = JSON.parse(rep(siap.value));
              st.siteinfoap = JSON.stringify(siteInfoAP);
              siap.parentNode.style.backgroundColor = null;
            } catch (er3) {
              console.log('init 3', er3);
              problem = true;
              siap.parentNode.style.backgroundColor = '#FCC';
            }
            try {
              userSiteInfo = JSON.parse(rep(siuff.value));
              st.usersiteinfo = JSON.stringify(userSiteInfo);
              siuff.parentNode.style.backgroundColor = null;
            } catch (er4) {
              console.log('init 4', er4);
              problem = true;
              siuff.parentNode.style.backgroundColor = '#FCC';
            }
            try {
              userSiteInfoAP = JSON.parse(rep(siuap.value));
              st.usersiteinfoap = JSON.stringify(userSiteInfoAP);
              siuap.parentNode.style.backgroundColor = null;
            } catch (er5) {
              console.log('init 5', er5);
              problem = true;
              siuap.parentNode.style.backgroundColor = '#FCC';
            }
            const sidu = $ids('siteinfo_disableitem');
            if (sidu) {
              st.disableitem = sidu.value.split('\n').filter(i => {
                if (i) return i;
              });
            }
            st.autoload = $ids('autoload_mode').selectedIndex;
            if (wlList) {
              st.autoloadfeed = wlList.value.split('\n').filter(i => {
                if (i) return i;
              });
            }
            if (blList) {
              st.notloadfeed = blList.value.split('\n').filter(i => {
                if (i) return i;
              });
            }
            wlTitle.value = null;
            blTitle.value = null;
            st.noscripttag = $ids('security_noscripttag').checked;
            st.iframetag = $ids('security_iframetag').checked;
            st.allowiframeurl = $ids('security_allowiframeurl').value;
            st.denynexturl = $ids('security_denynexturl').value;
            st.allownexturl = $ids('security_allownexturl').value;
            st.socialicon = $ids('social_socialicon').checked;
            st.socialhatena = $ids('social_hatena').checked;
            debugLog = $ids('etc_debuglog').checked;
            st.replacefullfeedurl = $ids('etc_replacefullfeedurl').value;
            if (!problem) {
              set.style.display = 'none';
              FullFeed.saveSettings();
            }
          } else if (e.target.id === 'gm_fullfeed_settings-cancel') {
            set.style.display = 'none';
          }
        } else if (e.button === 0 && e.target?.className === 'gm_fullfeed_settings-title') {
          const tab = $ids('tab').childNodes;
          const list = $ids('list').childNodes;
          for (let i = 0; i < tab.length; i++) {
            if (tab[i].classList.contains('selected')) {
              tab[i].classList.remove('selected');
              list[i].style.display = 'none';
            }
            if (tab[i].textContent === e.target.textContent) {
              tab[i].classList.add('selected');
              list[i].style.display = 'block';
            }
          }
        } else if (e.target?.classList.contains('gm_fullfeed_checked_icon')) {
          if ((e.button === 0 && e.ctrlKey) || e.button === 1) FullFeed.viewSettings('autoload');
          else if (e.button === 0) initFullFeed();
        } else if (
          e.button === 0 &&
          e.target?.parentNode &&
          e.target.parentNode.parentNode &&
          e.target.parentNode.parentNode.id === 'gm_fullfeed_settings-siteinfo_disableitemlist' &&
          e.target.nodeName === 'INPUT'
        ) {
          let temp = $ids('siteinfo_disableitem').value.split('\n');
          if (e.target.checked) {
            const chkbox = getElementsByXPath('//input', e.target.parentNode.parentNode.cloneNode(true));
            if (
              chkbox &&
              !chkbox.some(a => {
                if (!a.checked) return true;
              })
            ) {
              e.target.checked = false;
              return;
            }
            temp.push(e.target.nextSibling.textContent);
          } else {
            temp = temp.filter(a => {
              return a !== e.target.nextSibling.textContent;
            });
          }
          temp = temp.filter(a => {
            return !/^\s*$/.test(a);
          });
          $ids('siteinfo_disableitem').value = temp.join('\n');
        } else if (e.button === 0) FullFeed.checkRegister();
      },
      true
    );
    document.addEventListener(
      'scroll',
      () => {
        if (st.autopagerize) FullFeed.checkScroll();
      },
      true
    );
    const pageFX = $id('feedlyPageFX');
    if (pageFX) {
      const callback = (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            if (entry.target.classList.contains('InlineArticle')) {
              entry.target.classList.add('InlineArticle-in-viewport');
            } else {
              const elm = entry.target.closest('.InlineArticle');
              if (elm) {
                elm.classList.add('InlineArticle-in-viewport');
              }
            }
          } else {
            if (entry.target.classList.contains('InlineArticle-in-viewport')) {
              entry.target.classList.remove('InlineArticle-in-viewport');
            } else {
              const elm = entry.target.closest('.InlineArticle-in-viewport');
              if (elm) {
                elm.classList.remove('InlineArticle-in-viewport');
              }
            }
          }
        });
      };
      const observer = new IntersectionObserver(callback, {
        root: null,
        rootMargin: '0px',
        threshold: 0.5,
      });
      const initialArticles = document.querySelectorAll('.InlineArticle--fullArticle');
      initialArticles.forEach(article => {
        observer.observe(article);
      });
      const mutationObserver = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
          mutation.addedNodes.forEach(node => {
            if (node instanceof HTMLElement) {
              const fa = node.querySelector('.InlineArticle--fullArticle');
              if (fa) {
                observer.observe(fa);
              } else if (node.classList.contains('Article--inlined') && node.closest('.presentation-fullArticle')) {
                observer.observe(node);
              }
            }
          });
        });
      });
      mutationObserver.observe(pageFX, { childList: true, subtree: true });
    }
  };

  const initInterval = window.setInterval(() => {
    const e = document.querySelector('#feedlyChrome .Leftnav__wrapper .ProfileBubble');
    if (e && e.hasChildNodes()) {
      window.clearInterval(initInterval);
      window.setTimeout(() => {
        init();
      }, 1000);
    }
  }, 500);

  // == [Utility] =================================
  function removeXSSRisks(htmldoc) {
    const attr = 'allowScriptAccess';
    Array.prototype.forEach.call(htmldoc.getElementsByTagName('embed'), embed => {
      if (embed.hasAttribute(attr)) embed.setAttribute(attr, 'never');
    });
    Array.prototype.forEach.call(htmldoc.getElementsByTagName('param'), param => {
      if (param.getAttribute('name') === attr) param.setAttribute('value', 'never');
    });
  }

  function searchEntry(htmldoc) {
    const arM = ['hentry', 'xfolkentry', 'autopagerize_page_element'];
    const ar1 = [
      'articlebody',
      'articlebox',
      'articlesection',
      'articletable',
      'articlesbody',
      'articlesbox',
      'articlessection',
      'articlestable',
      'blockstory',
      'blogbody',
      'center',
      'entlybody',
      'entlytext',
      'entrybody',
      'entrycontent',
      'entrytable',
      'entrytext',
      'mainarticle',
      'maintext',
      'maintxt',
      'singlecontent',
      'storycontent',
      'yjmt',
    ];
    const ar2 = [
      'blockleaf',
      'boxcontent',
      'contentbody',
      'contentbox',
      'contentinner',
      'contenttext',
      'contentsbody',
      'contentsbox',
      'contentsinner',
      'contentstext',
      'maincontent',
      'mainframe',
      'middlecontent',
      'middlecontainer',
      'newsbody',
      'postbody',
      'postcontent',
      'textbody',
      'kiji',
      'newart',
    ];
    const ar3 = ['article', 'blog', 'body', 'column', 'content', 'entry', 'main', 'middle', 'page', 'post', 'section', 'story', 'text', 'entries'];
    const s1 = '//*[contains(translate(concat(';
    const s2 = '@id, " ", @class';
    const s3 = '), "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_ ", "abcdefghijklmnopqrstuvwxyz"), "';
    const s4 = '")]';
    const nM = arM.length,
      n1 = ar1.length,
      n2 = ar2.length,
      n3 = ar3.length;
    const makeXpath = function (ar, len, sp) {
      const x = [];
      ar.forEach((a, i) => {
        if (sp) x.push(`${s1}" ", ${s2}, " "${s3} ${a} ${s4}`);
        else x.push(s1 + s2 + s3 + a + s4);
        if (len > i + 1) x.push('|');
      });
      return x.join('');
    };
    const xpath = function () {
      const ar = ['date', 'foot', 'head', 'media', 'menu', 'navbar', 'navi', 'side', 'tool', 'widget'];
      const s5 = '[not((.|.//*|ancestor-or-self::*)[contains(translate(concat(@id, " ", @class), "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_", "abcdefghijklmnopqrstuvwxyz"), "';
      const s6 = '")])]';
      const x = ['//*[not(.//head|ancestor-or-self::head)][not(.//link|ancestor-or-self::link)][not(.//style|ancestor-or-self::style)]'];
      ar.forEach(a => {
        x.push(s5 + a + s6);
      });
      return x.join('');
    };

    try {
      let max = 0,
        data;
      let elms = getElementsByXPath(makeXpath(arM, nM, true), htmldoc);
      if (!elms) elms = getElementsByXPath(makeXpath(ar1, n1, true), htmldoc);
      if (!elms) elms = getElementsByXPath(makeXpath(ar1, n1, false), htmldoc);
      if (!elms) elms = getElementsByXPath(makeXpath(ar2, n2, true), htmldoc);
      if (!elms) elms = getElementsByXPath(makeXpath(ar2, n2, false), htmldoc);
      if (!elms) elms = getElementsByXPath(makeXpath(ar3, n3, true), htmldoc);
      if (!elms) elms = getElementsByXPath(makeXpath(ar3, n3, false), htmldoc);
      if (!elms) elms = getElementsByXPath(xpath(), htmldoc);
      if (!elms) return null;
      elms.forEach(e => {
        if (typeof e.textContent !== 'string') return;
        const n = e.textContent.replace(/^\s+|\s+$|(?:\r?\n|\r){2,}/g, '').length;
        if (max < n) {
          max = n;
          data = e;
        }
      });
      return data ? [data] : null;
    } catch (e) {
      console.error('searchEntry', e);
      return null;
    }
  }

  function relativeToAbsolutePath(htmldoc, base) {
    const top = /^https?:\/\/[^/]+/.exec(base)[0],
      current = base.replace(/\/[^/]+$/, '/');
    let url;
    Array.prototype.forEach.call(htmldoc.getElementsByTagName('a'), a => {
      url = a.getAttribute('href');
      if (url) {
        if (url.indexOf(' ') === 0) url = url.replace(/^\s+(.+)/, '$1');
        a.href = _rel2abs(url, top, current, base);
      }
    });
    Array.prototype.forEach.call(htmldoc.getElementsByTagName('img'), img => {
      url = img.getAttribute('src');
      if (url) {
        if (url.indexOf(' ') === 0) url = url.replace(/^\s+(.+)/, '$1');
        img.src = _rel2abs(url, top, current, base);
      }
    });
  }

  function _rel2abs(url, top, current, base) {
    return new RegExp('^https?://').test(url) ? url : url.indexOf('/') === 0 ? top + url : url.indexOf('#') === 0 ? base + url : current + url;
  }

  function message(mes, dur, typ) {
    const box = $id('gm_fullfeed_message');
    if (!box) return;
    const dura = dur < 0 ? 300000 : !dur ? 1500 : dur;
    const type = typ;
    box.innerHTML = mes;
    box.classList.remove('gm_fullfeed_warning');
    if (type) box.classList.add(`gm_fullfeed_${type}`);
    box.classList.remove('gm_fullfeed_hidden');
    window.clearTimeout(messageInterval);
    messageInterval = window.setTimeout(() => {
      box.classList.add('gm_fullfeed_hidden');
      if (type) box.classList.remove(`gm_fullfeed_${type}`);
      box.innerHTML = '';
    }, dura);
  }

  // AutoPagerize (c) id:swdyh
  function createHTMLDocumentByString(str) {
    if (document.documentElement.nodeName !== 'HTML') {
      return new DOMParser().parseFromString(str, 'application/xhtml+xml');
    }
    const html = stripHtmlTag(str);
    let htmlDoc;
    try {
      htmlDoc = document.cloneNode(false);
      htmlDoc.appendChild(htmlDoc.importNode(document.documentElement, false));
    } catch (er1) {
      console.log('createHTMLDocumentByString 2', er1);
      htmlDoc = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
    }
    let fragment = createDocumentFragmentByString(html);
    try {
      fragment = htmlDoc.adoptNode(fragment);
    } catch (er2) {
      console.log('createHTMLDocumentByString 2', er2);
      fragment = htmlDoc.importNode(fragment, true);
    }
    htmlDoc.documentElement.appendChild(fragment);
    return htmlDoc;
  }

  // AutoPagerize (c) id:swdyh
  function getElementsByXPath(xpath, node) {
    const ss = getXPathResult(xpath, node, 7);
    const data = [];
    for (let i = 0; i < ss.snapshotLength; i++) data.push(ss.snapshotItem(i));
    return data.length > 0 ? data : null;
  }

  // AutoPagerize (c) id:swdyh
  function getXPathResult(xpath, nod, resultType) {
    const node = nod || document;
    const doc = node.ownerDocument || node;
    let resolver = doc.createNSResolver(node.documentElement || node);
    let defaultNS = null;
    try {
      if (node.nodeType === node.DOCUMENT_NODE) defaultNS = node.documentElement.lookupNamespaceURI(null);
      else defaultNS = node.lookupNamespaceURI(null);
    } catch (e) { console.log(e); }
    if (defaultNS) {
      const defaultPrefix = '__default__';
      xpath = addDefaultPrefix(xpath, defaultPrefix);
      const defaultResolver = resolver;
      resolver = function (prefix) {
        return prefix === defaultPrefix ? defaultNS : defaultResolver.lookupNamespaceURI(prefix);
      };
    }
    return doc.evaluate(xpath, node, resolver, resultType, null);
  }

  // AutoPagerize (c) id:swdyh
  function addDefaultPrefix(xpath, prefix) {
    const tokenPattern = /([A-Za-z_\u00c0-\ufffd][\w\-.\u00b7-\ufffd]*|\*)\s*(::?|\()?|(".*?"|'.*?'|\d+(?:\.\d*)?|\.(?:\.|\d+)?|[)\]])|(\/\/?|!=|[<>]=?|[([|,=+-])|([@$])/g;
    const TERM = 1,
      OPERATOR = 2,
      MODIFIER = 3;
    let tokenType = OPERATOR;
    prefix += ':';
    function replacer(token, identifier, suffix, term, operator) {
      if (suffix) tokenType = suffix === ':' || (suffix === '::' && (identifier === 'attribute' || identifier === 'namespace')) ? MODIFIER : OPERATOR;
      else if (identifier) {
        if (tokenType === OPERATOR && identifier !== '*') token = prefix + token;
        tokenType = tokenType === TERM ? OPERATOR : TERM;
      } else tokenType = term ? TERM : operator ? OPERATOR : MODIFIER;
      return token;
    }
    return xpath.replace(tokenPattern, replacer);
  }

  // AutoPagerize (c) id:swdyh
  function createDocumentFragmentByString(str) {
    const range = document.createRange();
    range.setStartAfter(document.body);
    return range.createContextualFragment(str);
  }

  // AutoPagerize (c) id:swdyh
  function stripHtmlTag(str) {
    let chunks = str.split(/(<html(?:[ \t\r\n][^>]*)?>)/);
    if (chunks.length >= 3) chunks.splice(0, 2);
    str = chunks.join('');
    chunks = str.split(/(<\/html[ \t\r\n]*>)/);
    if (chunks.length >= 3) chunks.splice(chunks.length - 2);
    return chunks.join('');
  }

  function pathToURL(url, path) {
    const re = path.indexOf('/') === 0 ? /^([a-zA-Z]+:\/\/[^/]+)\/.*$/ : /^(.*\/).*$/;
    return url.replace(re, `$1${path}`);
  }
})();