酷安 Feed 阅读

利用酷安的客户端 API,在无须扫码打开的情况下预览酷安网 Feed 的信息

// ==UserScript==
// @name         酷安 Feed 阅读
// @namespace    https://coolapk.com/
// @version      0.1.1
// @description  利用酷安的客户端 API,在无须扫码打开的情况下预览酷安网 Feed 的信息
// @author       duangsuse
// @include      /http(s?):\/\/www.coolapk.com\/feed\/(\d+)/
// @match        https://www.coolapk.com/feed/*
// @grant        none
// ==/UserScript==

function currentTimeTms() { return new Date().getTime(); }

function checkFunDep(value, message) {
  if (typeof value !='function')
    throw Error(message);
}

function findFirstParent(elem, predicate) {
  var eln = elem.parentNode;
  do {
    if (predicate(eln))
      return eln;

    eln = eln.parentNode;
  } while (eln != null);
}

// from https://jsfiddle.net/briguy37/2MVFd/
function uuidgen() {
  var tms = currentTimeTms();
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = (tms + Math.random() * 16) %16 | 0;
    tms = Math.floor(tms /16);
    return (c=='x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
}

// Begin coolToken

function hex(x) {return '0x' + x.toString(16);}

function timestampMd5(ts) {
  checkFunDep(digest, 'MD5 digest algorithm not found');

  var repr = String(ts);
  return digest(repr);
}

function coolToken(_base, appId, uuid, time) {
  checkFunDep(digest, 'MD5 digest algorithm not found');

  var base = _base || 'token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?';
  var aid = appId || 'com.coolapk.market';
  var uuidstr = uuid || '00000000-0000-0000-0000-000000000000';
  var tms = time || Math.floor(currentTimeTms() /1000);

  var salt = base + timestampMd5(tms) + "$" + uuidstr + "&" + aid;

  var saltbase64 = btoa(salt);
  var saltb64md5 = digest(saltbase64);

  var hextime = hex(tms);

  return saltb64md5 + uuidstr + hextime;
}

// End coolToken
function xmlhttp(url, asyncb, method) {
  var req = new XMLHttpRequest();
  req.open(method || 'GET', url, asyncb !=null);

  req.onreadystatechange = function (st) {
    if (req.readyState == XMLHttpRequest.DONE)
      asyncb(req);
  };
  return req;
}
function setheaders(xmlreq, hdrs) {
  hdrs.forEach(function (kv) {
    xmlreq.setRequestHeader(kv[0], kv[1]);
  });
}

function to_a(dom) {
  var result = [];
  for (var xi in Object.keys(dom))
    result.push(dom[xi]);
  return result;
}

cool = {
  detail: function(id) {return 'https://api.coolapk.com/v6/feed/detail?id='+id;}
};

function concat(xs, sep) {
  var seprator = sep || '';
  var result = [];
  xs.forEach(function (x) { result.push(x); });
  return result.join(seprator);
}

/**
 * CoolApk Feed render
 * [title](abbr: info + entityId + length)(href: shareUrl) [share_num]{!=0} [signature] [isXXX]
 * [userInfo]
 * <img>[message_cover]</img>
 * Keywords: [message_keywords]
 * [tags]
 * <quote>[message_brief]</quote>
 * [message]
 * [picArr]
 * [dateline] [lastupdate]
 * [device_title] (abbr: device_name) [device_rom] [fromname](abbr: fromid)
 * 
 * [likenum] / [burynum] / [rank_score]
 * [recent_like_list]
 * [userLikeList]
 * 
 * [recent_hot_reply_ids] [commentnum](abbr: replynum, comment_block_num)
 * [hotReplyRows]
 * 
 * ~> [recent_reply_ids]
 */
var _ = ' ';
function feedRender(model) {
  //console.log(model.message);
  var mesg = document.createElement('p');
  mesg.id = model.entityId;
  mesg.innerHTML = (model.message || '未知消息');

  var title = document.createElement('h2');
  var titleabbr = document.createElement('abbr');
  titleabbr.innerText = model.title || '未知标题';
  titleabbr.title = concat([model.info, _, model.entityId, _, '长度=', model.message_length, ' @次数=', model.at_count]);
  title.appendChild(titleabbr);

  var share = document.createElement('a');
  share.href = model.shareUrl;
  share.innerText = model.share_num ==0? '分享' : model.share_num+' 次外链';

  var widget = document.createElement('div');

  [title, share, mesg].forEach(function (x) {widget.appendChild(x);});
  //console.log(widget);
  return widget;
}

/**
 * CoolApk User model render
 * | [displayUsername]{username != displayUsername} '('[username]')' (href: url) (abbr: level + groupid + uid + status) [admintype]
 * [userAvatar] (src: userAvatar, href: userBigAvatar) | []
 * | [logintime] [regdate]
 */
function userRender(model) {}

function render(trans, destview) {
  if (trans.status !=200) {
    alert(":( Sorry, request to CoolApk API failed with response code " + trans.status + " and body " + trans.response);
  }
  var model = JSON.parse(trans.response);
  var data = model.data;
  console.log(data);
  //console.log(destview);
  destview.appendChild(feedRender(data));
}

function rewrites(container) {
  //alert('rewrite!');

  try {
    to_a(container.children).forEach(function (child) {container.removeChild(child);});
    var currenttok = coolToken();
    //console.log(currenttok);

    var feedid = /coolapk.com\/feed\/(\d+)$/.exec(document.location)[1];
    //console.log(feedid);
    var req = xmlhttp(cool.detail(feedid), function(t){render(t, container);});
    
    var headers = [
      ['X-Requested-With', XMLHttpRequest.name],
      ['X-Sdk-Int', 25],
      ['X-Sdk-Locale', 'zh-CN'],
      ['X-App-Id', 'com.coolapk.market'],
      ['X-App-Version', '9.0.2'],
      ['X-App-Code', 1902151],
      ['X-App-Token', coolToken()]
    ];

    setheaders(req, headers);
    req.send(null);

  } catch (e) { container.appendChild(document.createTextNode(e +'@'+ e.lineNumber)); }
}

function main() {
  var dbtn = document.querySelector('.download_button');
  //dbtn.innerHTML = 'GO';

  var container = findFirstParent(dbtn, function (e) {return /*e.style.textAlign ==='center'*/ e.classList.contains('msg_box');});
  var dbtnc = findFirstParent(dbtn, function (e) {return e.tagName ==='DIV';});

  var btntext = document.createTextNode('Preview Feed');
  var rewrite = document.createElement('a');
  rewrite.classList.add('header-developer');
  
  var btx = document.createElement('span');
  btx.appendChild(btntext);
  rewrite.appendChild(btx);

  container.appendChild(document.createElement('br'));
  container.appendChild(rewrite);

  rewrite.onclick = function(){rewrites(container);};
}

//document.addEventListener('DOMContentLoaded', main);
main();

/// Dependency https://github.com/solderjs/jsMD5

function digestn(M) {
  var originalLength, i, j, k, l, A, B, C, D, AA, BB, CC, DD, X, rval;

  function F(x, y, z) {
    return (x & y) | (~x & z);
  }

  function G(x, y, z) {
    return (x & z) | (y & ~z);
  }

  function H(x, y, z) {
    return x ^ y ^ z;
  }

  function I(x, y, z) {
    return y ^ (x | ~z);
  }

  function to4bytes(n) {
    return [n & 0xff, (n >>> 8) & 0xff, (n >>> 16) & 0xff, (n >>> 24) & 0xff];
  }

  originalLength = M.length; // for Step.2

  // 3.1 Step 1. Append Padding Bits
  M.push(0x80);
  l = (56 - M.length) & 0x3f;
  for (i = 0; i < l; i++)
    M.push(0);

  // 3.2 Step 2. Append Length
  to4bytes(8 * originalLength).forEach(function (e) {
    M.push(e);
  });
  [0, 0, 0, 0].forEach(function (e) {
    M.push(e);
  });

  // 3.3 Step 3. Initialize MD Buffer
  A = [0x67452301];
  B = [0xefcdab89];
  C = [0x98badcfe];
  D = [0x10325476];

  // 3.4 Step 4. Process Message in 16-Word Blocks
  function rounds(a, b, c, d, k, s, t, f) {
    a[0] += f(b[0], c[0], d[0]) + X[k] + t;
    a[0] = ((a[0] << s) | (a[0] >>> (32 - s)));
    a[0] += b[0];
  }

  for (i = 0; i < M.length; i += 64) {
    X = [];
    for (j = 0; j < 64; j += 4) {
      k = i + j;
      X.push(M[k] | (M[k + 1] << 8) | (M[k + 2] << 16) | (M[k + 3] << 24));
    }
    AA = A[0];
    BB = B[0];
    CC = C[0];
    DD = D[0];

    // Round 1.
    rounds(A, B, C, D, 0, 7, 0xd76aa478, F);
    rounds(D, A, B, C, 1, 12, 0xe8c7b756, F);
    rounds(C, D, A, B, 2, 17, 0x242070db, F);
    rounds(B, C, D, A, 3, 22, 0xc1bdceee, F);
    rounds(A, B, C, D, 4, 7, 0xf57c0faf, F);
    rounds(D, A, B, C, 5, 12, 0x4787c62a, F);
    rounds(C, D, A, B, 6, 17, 0xa8304613, F);
    rounds(B, C, D, A, 7, 22, 0xfd469501, F);
    rounds(A, B, C, D, 8, 7, 0x698098d8, F);
    rounds(D, A, B, C, 9, 12, 0x8b44f7af, F);
    rounds(C, D, A, B, 10, 17, 0xffff5bb1, F);
    rounds(B, C, D, A, 11, 22, 0x895cd7be, F);
    rounds(A, B, C, D, 12, 7, 0x6b901122, F);
    rounds(D, A, B, C, 13, 12, 0xfd987193, F);
    rounds(C, D, A, B, 14, 17, 0xa679438e, F);
    rounds(B, C, D, A, 15, 22, 0x49b40821, F);

    // Round 2.
    rounds(A, B, C, D, 1, 5, 0xf61e2562, G);
    rounds(D, A, B, C, 6, 9, 0xc040b340, G);
    rounds(C, D, A, B, 11, 14, 0x265e5a51, G);
    rounds(B, C, D, A, 0, 20, 0xe9b6c7aa, G);
    rounds(A, B, C, D, 5, 5, 0xd62f105d, G);
    rounds(D, A, B, C, 10, 9, 0x02441453, G);
    rounds(C, D, A, B, 15, 14, 0xd8a1e681, G);
    rounds(B, C, D, A, 4, 20, 0xe7d3fbc8, G);
    rounds(A, B, C, D, 9, 5, 0x21e1cde6, G);
    rounds(D, A, B, C, 14, 9, 0xc33707d6, G);
    rounds(C, D, A, B, 3, 14, 0xf4d50d87, G);
    rounds(B, C, D, A, 8, 20, 0x455a14ed, G);
    rounds(A, B, C, D, 13, 5, 0xa9e3e905, G);
    rounds(D, A, B, C, 2, 9, 0xfcefa3f8, G);
    rounds(C, D, A, B, 7, 14, 0x676f02d9, G);
    rounds(B, C, D, A, 12, 20, 0x8d2a4c8a, G);

    // Round 3.
    rounds(A, B, C, D, 5, 4, 0xfffa3942, H);
    rounds(D, A, B, C, 8, 11, 0x8771f681, H);
    rounds(C, D, A, B, 11, 16, 0x6d9d6122, H);
    rounds(B, C, D, A, 14, 23, 0xfde5380c, H);
    rounds(A, B, C, D, 1, 4, 0xa4beea44, H);
    rounds(D, A, B, C, 4, 11, 0x4bdecfa9, H);
    rounds(C, D, A, B, 7, 16, 0xf6bb4b60, H);
    rounds(B, C, D, A, 10, 23, 0xbebfbc70, H);
    rounds(A, B, C, D, 13, 4, 0x289b7ec6, H);
    rounds(D, A, B, C, 0, 11, 0xeaa127fa, H);
    rounds(C, D, A, B, 3, 16, 0xd4ef3085, H);
    rounds(B, C, D, A, 6, 23, 0x04881d05, H);
    rounds(A, B, C, D, 9, 4, 0xd9d4d039, H);
    rounds(D, A, B, C, 12, 11, 0xe6db99e5, H);
    rounds(C, D, A, B, 15, 16, 0x1fa27cf8, H);
    rounds(B, C, D, A, 2, 23, 0xc4ac5665, H);

    // Round 4.
    rounds(A, B, C, D, 0, 6, 0xf4292244, I);
    rounds(D, A, B, C, 7, 10, 0x432aff97, I);
    rounds(C, D, A, B, 14, 15, 0xab9423a7, I);
    rounds(B, C, D, A, 5, 21, 0xfc93a039, I);
    rounds(A, B, C, D, 12, 6, 0x655b59c3, I);
    rounds(D, A, B, C, 3, 10, 0x8f0ccc92, I);
    rounds(C, D, A, B, 10, 15, 0xffeff47d, I);
    rounds(B, C, D, A, 1, 21, 0x85845dd1, I);
    rounds(A, B, C, D, 8, 6, 0x6fa87e4f, I);
    rounds(D, A, B, C, 15, 10, 0xfe2ce6e0, I);
    rounds(C, D, A, B, 6, 15, 0xa3014314, I);
    rounds(B, C, D, A, 13, 21, 0x4e0811a1, I);
    rounds(A, B, C, D, 4, 6, 0xf7537e82, I);
    rounds(D, A, B, C, 11, 10, 0xbd3af235, I);
    rounds(C, D, A, B, 2, 15, 0x2ad7d2bb, I);
    rounds(B, C, D, A, 9, 21, 0xeb86d391, I);

    A[0] += AA;
    B[0] += BB;
    C[0] += CC;
    D[0] += DD;
  }

  rval = [];
  to4bytes(A[0]).forEach(function (e) {
    rval.push(e);
  });
  to4bytes(B[0]).forEach(function (e) {
    rval.push(e);
  });
  to4bytes(C[0]).forEach(function (e) {
    rval.push(e);
  });
  to4bytes(D[0]).forEach(function (e) {
    rval.push(e);
  });

  return rval;
}
function digest(s) {
  var M = [],
    i, d, rstr, s;

  for (i = 0; i < s.length; i++)
    M.push(s.charCodeAt(i));

  d = digestn(M);
  rstr = '';

  d.forEach(function (e) {
    s = e.toString(16);
    while (s.length < 2)
      s = '0' + s;
    rstr += s;
  });

  return rstr;
}