:don: - TLジャンプしようとしてたやつ Fork

    @@ -9,8 +9,8 @@ // BUG: TIMELINE_REFRESH_SUCCESS always *concat* to cached timeline... - (( - store = (() => { + (() => { + const store = (() => { // >= v16: to descendant let current_node = document.querySelector('#mastodon')._reactRootContainer.current.child; while ('function' === typeof current_node.type) {
    @@ -18,19 +18,83 @@ if (store) return store; current_node = current_node.child; } - })(), - now = new Date(), - now_array = [0, now.getFullYear(), now.getMonth() + 1, now.getDate(), now.getHours(), now.getMinutes()], - query = String(prompt('Enter max_id:\n [[yyyy/]mm/dd ]hh:mm or status_id\nLeave empty to load latest timeline.')), - query_date = query && query.match(/(?:(?:(\d{4})\D+)?(\d+)\D+(\d+)\D+)?(\d+):(\d+)/).map((e, i) => e || now_array[i]), - max_id = query_date ? new Date(query_date[1], query_date[2] - 1, query_date[3], query_date[4], query_date[5], 0).getTime() * 2 ** 16 : query - ) => - fetch(`/api/v1/timelines/public?local=true${ max_id ? '&max_id=' + max_id : ''}`) + })(); + + const target = (() => { + // path match + const path_segments = location.pathname.split('/').slice(1); + if (path_segments[0] === 'accounts') { + const [, id, type] = path_segments; + const build = (label = '', append_key = '', append_path = '') => + ({ label: label || `account timeline`, key: `accounts:${id}${append_key}`, path: `accounts/${id}${append_path}` }); + switch (type) { + case 'media': return build('account media timeline', ':media', '?only_media=true'); + case 'with_replies': return build('account timeline (w/ replies)', ':with_replies', '?exclude_replies=false'); + default: return build('account timeline', '', 'exclude_replies=true'}; + } + } else if (path_segments[0] === 'timelines') { + const [, type, id] = path_segments; + switch (type) { + case 'local': return { label: 'local', key: 'community', path: 'timelines/public?local=true'}; + case 'public': return { label: 'federated', key: 'public', path: 'timelines/public'}; + case 'tag': return { label: `#${id}`, key: `hashtag:${id}`, path: 'timelines/tag/${id}'}; + } + } + // use pinned columns + if (document.querySelector('.fa-users.column-header__icon')) { + return { label: 'local', key: 'community', path: 'timelines/public?local=true'}; + } + })(); + + if (!target) { + alert('No supported timeline detected.\nThis script finds:\n- account/local timeline on right-most column\n- pinned local timeline'); + return; + } + + const query = prompt(`Load statuses to ${target.label} timeline.\n\nEnter <max_id> | [[yyyy/]mm/dd ]hh:mm\nor leave empty to load latest timeline.`); + if (query === null) return; + + const parseDateQuery = s => { + const now_array = ((d = new Date()) => [0, d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes()])(); + const match = query.match(/(?:(?:(\d{4})\D+)?(\d+)\D+(\d+)\D+)?(\d+):(\d+)/).map((e, i) => e || now_array[i]); + return match && new Date(query_date[1], query_date[2] - 1, query_date[3], query_date[4], query_date[5], 0).getTime() * 2 ** 16; + } + const parseMaxIdQuery = s => (s.match(/^\d+$/) || {})[0]; + + const max_id = parseDateQuery(query) || parseMaxIdQuery(query); + + if (!max_id) { + alert('Invalid query: ' + query); + return; + } + + + fetch(`/api/v1/${target.path}${target.path.includes('?') ? '&' : '?'}limit=40` + (max_id ? `&max_id=${max_id}` : '')) .then(x => x.json()) - .then(x => store.dispatch({ + .then(statuses => { - type: 'TIMELINE_REFRESH_SUCCESS', + // TIMELINE_UPDATE trims timeline if the timeline has 40 statuses already - timeline: 'community', + const scrollTop = store.getState().getIn(['timelines', target.key, 'top']); - statuses: x, + store.dispatch({ - partial: false + type: 'TIMELINE_SCROLL_TOP', - })) + timeline: target.key, - )();+ top: true + }); + store.dispatch({ + type: 'TIMELINE_REFRESH_SUCCESS', + timeline: target.key, + statuses: statuses.slice(1), + partial: false + }); + store.dispatch({ + type: 'TIMELINE_UPDATE', + timeline: target.key, + status: statuses[0], + references: [] + }); + store.dispatch({ + type: 'TIMELINE_SCROLL_TOP', + timeline: target.key, + top: scrollTop + }); + }); + })();
  • /*
     * @title bookmarklet
     * @description my bookmarklet
     * @include http://*
     * @license MIT License
     * @require 
     * @private
     */
    
    // BUG: TIMELINE_REFRESH_SUCCESS always *concat* to cached timeline...
    
    (() => {
      const store = (() => {
        // >= v16: to descendant
        let current_node = document.querySelector('#mastodon')._reactRootContainer.current.child;
        while ('function' === typeof current_node.type) {
          const store = current_node.memoizedProps.store;
          if (store) return store;
          current_node = current_node.child;
        }
      })();
      
      const target = (() => {
        // path match
        const path_segments = location.pathname.split('/').slice(1);
        if (path_segments[0] === 'accounts') {
          const [, id, type] = path_segments;
          const build = (label = '', append_key = '', append_path = '') =>
            ({ label: label || `account timeline`, key: `accounts:${id}${append_key}`, path: `accounts/${id}${append_path}` });
          switch (type) {
            case 'media': return build('account media timeline', ':media', '?only_media=true');
            case 'with_replies': return build('account timeline (w/ replies)', ':with_replies', '?exclude_replies=false');
            default: return build('account timeline', '', 'exclude_replies=true'};
          }
        } else if (path_segments[0] === 'timelines') {
          const [, type, id] = path_segments;
          switch (type) {
            case 'local': return { label: 'local', key: 'community', path: 'timelines/public?local=true'};
            case 'public': return { label: 'federated', key: 'public', path: 'timelines/public'};
            case 'tag': return { label: `#${id}`, key: `hashtag:${id}`, path: 'timelines/tag/${id}'};
          }
        }
        // use pinned columns
        if (document.querySelector('.fa-users.column-header__icon')) {
          return { label: 'local', key: 'community', path: 'timelines/public?local=true'};
        }
      })();
      
      if (!target) {
        alert('No supported timeline detected.\nThis script finds:\n- account/local timeline on right-most column\n- pinned local timeline');
        return;
      }
      
      const query = prompt(`Load statuses to ${target.label} timeline.\n\nEnter <max_id> | [[yyyy/]mm/dd ]hh:mm\nor leave empty to load latest timeline.`);
      if (query === null) return;
      
      const parseDateQuery = s => {
        const now_array = ((d = new Date()) => [0, d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes()])();
        const match = query.match(/(?:(?:(\d{4})\D+)?(\d+)\D+(\d+)\D+)?(\d+):(\d+)/).map((e, i) => e || now_array[i]);
        return match && new Date(query_date[1], query_date[2] - 1, query_date[3], query_date[4], query_date[5], 0).getTime() * 2 ** 16;
      }
      const parseMaxIdQuery = s => (s.match(/^\d+$/) || {})[0];
      
      const max_id = parseDateQuery(query) || parseMaxIdQuery(query);
      
      if (!max_id) {
        alert('Invalid query: ' + query);
        return;
      }
      
    
     fetch(`/api/v1/${target.path}${target.path.includes('?') ? '&' : '?'}limit=40` + (max_id ? `&max_id=${max_id}` : ''))
     .then(x => x.json())
     .then(statuses => {
       // TIMELINE_UPDATE trims timeline if the timeline has 40 statuses already
       const scrollTop = store.getState().getIn(['timelines', target.key, 'top']);
       store.dispatch({
         type: 'TIMELINE_SCROLL_TOP',
         timeline: target.key,
         top: true
       });
       store.dispatch({
        type: 'TIMELINE_REFRESH_SUCCESS',
        timeline: target.key,
        statuses: statuses.slice(1),
        partial: false
       });
       store.dispatch({
         type: 'TIMELINE_UPDATE',
         timeline: target.key,
         status: statuses[0],
         references: []
       });
       store.dispatch({
         type: 'TIMELINE_SCROLL_TOP',
         timeline: target.key,
         top: scrollTop
       });
     });
    })();
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。