zenbackのスクリプトを解析してみた(3/3)


前回の続きです。

cssとhtmlには特に見るべき所が無さそうなので、javascriptだけチェックしていきます。

zb_jq("div.zenback-twitterbtn > a:first").click(function() {
  this.disabled = true;
  var title = (document.title || "");
  if (title) {
    title += " → ";
  }
  window.top.location = "http://widget.zenback.jp/_t/twitter_post_redirect/?url=http%3A//nanoappli.com/blog/&title="+encodeURIComponent(title);
}); //end click




div.zenback-twitterbtnがクリックされたらページ遷移するスクリプトですが、zenback-twitterbtnの定義自体がhtmlに無かったので既に使われてないコードっぽいです。
実際に上記URLにアクセスしてみても、以下のようなエラーでした。



ちなみに余談ですが、このエラーページの右下にあるbluebridge、以下の企業でした。
http://bluebridge.jp/


zb_escape_html()


var zb_escape_html = function (str) {
  str = str.replace("&","&");
  str = str.replace("\"",""");
  str = str.replace("'","'");
  str = str.replace("<","&lt;");
  str = str.replace(">","&gt;");
  return str;
};


HTMLエンコードの処理です。
これとまったく同じ処理。→JavaScript/jQuery HTML Encoding
どこからも呼ばれていない模様なので過去の遺産かも。



zb_load_script()


var zb_load_script = function (options) {
  var script = document.createElement('script');
  script.src = options.url;
  script.type = 'text/javascript';
  script.charset = 'utf-8';
  if (options.async) {
    script.async = true;
  }
  if (window.ActiveXObject) { // for IE
    script.onreadystatechange = function () {
      if (this.readyState === 'onloaded' || this.readyState === 'complete') {
        options.onload && options.onload();
      }
    };
  } else { // for other
    script.onloaddone = false; // for opera
    script.onload = function () {
      if (!this.onloaddone) {
        options.onload && options.onload();
        this.onloaddone = true;
      }
    };
  }
  if (options.element) {
    var s = options.element;
    s.appendChild(script);
  } else { // default insert
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(script, s);
  }
};


引数で指定されたoptions.urlのスクリプトを実行する処理。
options.urlにはjavascriptのurlが指定される。
また、options.asyncがtrueの時は非同期で処理がコールされ、JavaScriptのロードが完了したタイミングでoptions.onloadをコールバックしてくれる。
ブラウザ毎の非互換性の吸収とかしてますね。

この辺の処理は汎用性が有りそう。



_zb_print_error()


var _zb_print_error = function (msg) {
  window.console && console.error(msg);
};


デバッグログ的な関数。
console(window.console)は、クライアントよっては存在しない可能性があるので、チェックしてますね。



zb_load_jsonp()


var zb_load_jsonp = function (params, callback) {
  if (!params || !callback) {
    throw new Error('Invalid Parameter');
  }
  try {
    var cb_key = params.callbackKey || 'callback';
    var cb_value = params.callbackValue || 'callback';
    var url = (params.url + (params.url.match(/\?/) ? '&' : '?'));
    url += (cb_key + '=' + cb_value);
    var frame = document.createElement("iframe");
    frame.style.display = "none";
    document.body.appendChild(frame);
    var doc = frame.contentWindow.document;
    var count = 0; // for Opera
    frame[frame.readyState/*IE*/ ? "onreadystatechange" : "onload"] = function () {
      if (this.readyState && this.readyState !== 'complete' || count++) {
        return;
      }
      if (doc['__zb_jsonp__']) {
        callback(null, doc['__zb_jsonp__']);
      } else if (params.retry && params.retry >= 1) {
        var timeout = params.timeout || 1000;
        setTimeout(function () {
          zb_load_jsonp({
            url: params.url
            ,callbackKey: cb_key
            ,callbackValue: cb_value
            ,retry: params.retry - 1
            ,timeout: timeout 
          }, callback)}, timeout);
      } else {
        callback({
          message: 'Failed load jsonp'
        });
      }
      setTimeout(function () {
        try {
          frame && frame.parentNode && frame.parentNode.removeChild(frame);
        } catch (e) {
          _zb_print_error(e.message);
        }
      }, 0);
    };
    doc.open();
    doc.write('<' + 'script type="text/javascript">'
        + 'function ' + cb_value + ' (v) { document["__zb_jsonp__"] = v };'
        + '</' + 'script>'
        + '<' + 'script type="text/javascript" src="' + url + '"></' + 'script>');
    doc.close();
    return frame;
  } catch (e) {
    callback(e);
  }
};


なんだろう…
雰囲気的にはiframeを作って、jsonpの呼び出しの感じだけど、ちょっと複雑なので後回し。
zb_load_twitter_favorites()からコールされている。


zb_abort_jsonp()


var zb_abort_jsonp = function (frame) {
  try {
    frame && frame.parentNode && frame.parentNode.removeChild(frame);
  } catch (e) {
    _zb_print_error(e.message);
  }
};


jsonpコールを中断したとき、一旦作ったiframeを消しに走っている。
残念ながら誰からも呼ばれてない。



script_container()


var script_container = document.getElementById('zenback-script-container');
 
var zb_ga_track_settings = function () {
  _gaq.push(['zb._setAccount', 'UA-17145123-2']);
  _gaq.push(['zb._trackPageview']);
  _gaq.push(['zb._trackEvent', 'widgetNSID', '353091eea229c2955b2271a0c215dd8bcb210a67']);
  _gaq.push(['zb_ads._setAccount', 'UA-17145123-5']);
};


google analysticsの下準備。


zb_ga_track_XXXX()


var zb_ga_track_entries = function () {
  zb_jq('.zenback-entries .zenback-list a').click(function () {
    _gaq.push(['zb._trackEvent', 'kanren_entries', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_links = function () {
  zb_jq('.zenback-links .zenback-list a').click(function () {
    _gaq.push(['zb._trackEvent', 'kanren_links', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_keywords = function () {
  zb_jq('.zenback-keywords .zenback-list a').click(function () {
    _gaq.push(['zb._trackEvent', 'kanren_keywords', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_newsitem = function () {
  zb_jq('.zenback-newsitem a').click(function () {
    _gaq.push(['zb._trackEvent', 'zenback_news', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_twitter_widget = function () {
  zb_jq('.zenback-twitter .zenback-list a').click(function() {
    _gaq.push(['zb._trackEvent', 'twitter', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_mixi = function () {
  zb_jq('.zenback-socialbar .zenback-socialbar-mixicheck a').click(function () {
    _gaq.push(['zb._trackEvent', 'mixi', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_evernote = function () {
  zb_jq('.zenback-socialbar .zenback-socialbar-evernote a').click(function () {
    _gaq.push(['zb._trackEvent', 'evernote', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};


アクセスログを取る際に、どのボタンが押されたかクリックログを仕込んでいる。
ボタンクリックのイベントハンドラを登録し、クリックのタイミングでどのボタンが押されたかgoogle analysticsのトラッキング用変数に値をセットしてる。
_gaq.pushの第二引数が違うだけなんだから共通関数にしてください!! な所。


zb_ga_track_XXXX()再び


var zb_ga_track_hatena_bookmark = function () {
  _gaq.push(['zb._trackEvent', 'hatena_bookmark', encodeURI(this.href), 'http://nanoappli.com/blog/']);
};
 
var zb_ga_track_googleplusone = function (obj) {
  _gaq.push(['zb._trackSocial', 'google+1', obj.state, obj.href]);
};
 
var zb_ga_track_twitter_tweet = function (url) {
  _gaq.push(['zb._trackSocial', 'twitter', 'tweet', url]);
};
 
var zb_ga_track_twitter_follow = function () {
  _gaq.push(['zb._trackSocial', 'twitter', 'follow']);
};
 
var zb_ga_track_fb_comments = function (comment) {
  _gaq.push(['zb._trackSocial', 'facebook', 'comment', comment.href]);
};
 
var zb_ga_track_fb_uncomments = function (comment) {
  _gaq.push(['zb._trackSocial', 'facebook', 'uncomment', comment.href]);
};


さっきと同じ。
今回はまったく同じでは無いけど、もう少し共通関数できると思うんだけど。



google analsticsのロード処理


zb_load_script({
  url: ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'
  ,async: true
  ,onload: function () {
    zb_ga_track_settings();
    zb_ga_track_entries();
    zb_ga_track_links();
    zb_ga_track_newsitem();
    zb_ga_track_keywords();
    zb_ga_track_twitter_widget();
    zb_ga_track_mixi();
    zb_ga_track_evernote();
  }
});


google analsticsのロード処理を,zb_load_script()経由でコールしている。
zb_load_script()の中身は既に前述済み。
gaのロードが完了したタイミングで、前述のクリックログの仕込みに入っている。



tweetボタン関係のロード処理


zb_load_script({
  url: 'http://platform.twitter.com/widgets.js'
  ,element: script_container
  ,async: true
  ,onload: function () {
    var zb_extract_param_from_uri = function (uri, paramName) {
      if (!uri) {
        return '';
      }
      var uri = uri.split('#')[0];  // Remove anchor.
      var parts = uri.split('?');  // Check for query params.
      if (parts.length == 1) {
        return '';
      }
      var query = decodeURI(parts[1]);
 
      // Find url param.
      paramName += '=';
      var params = query.split('&');
      for (var i = 0, param; param = params[i]; ++i) {
        if (param.indexOf(paramName) === 0) {
          return unescape(param.split('=')[1]);
        }
      }
    };
    if (window.twttr) {
      window.twttr.events.bind('tweet', function (event) {
        if (event) {
          var targetUrl;
          if (event.target && event.target.nodeName == 'IFRAME') {
            targetUrl = zb_extract_param_from_uri(event.target.src, 'url');
          }
          zb_ga_track_twitter_tweet(targetUrl);
        }
      });
      window.twttr.events.bind('follow', function (event) {
        event && zb_ga_track_twitter_follow();
      });
    }
  }
});


twitter公式のtweetボタン関係のライブラリをロードしてる。
ライブラリAPIは下記のページに説明があります。
https://dev.twitter.com/docs/tweet-button



hatenaボタン関係のロード処理


zb_load_script({
  url: 'http://b.st-hatena.com/js/bookmark_button.js'
  ,element: script_container
  ,async: true
  ,onload: function () {
  }
});


はてなのボタン。



google+1ボタン関係のロード処理


window.___gcfg = {
  lang: 'ja'
  ,parsetags: 'explicit'
};
zb_load_script({
  url: 'https://apis.google.com/js/plusone.js'
  ,element: script_container
  ,async: true
  ,onload: function () {
    gapi.plusone.render('zenback-google-plusone', {
      'size': 'medium'
      ,'count': 'true'
      ,'callback': 'zb_ga_track_googleplusone'
    });
  }
});


googleのボタン
APIはこちら→ +1 Button


zb_render_ad_view()


var zb_render_ad_view = function (params) {
  var result = false;
  try {
    var $zb_ads_body = zb_jq('#zenback-ads span.tweet_body');
    var $zb_ads_text_a = zb_jq('<a></a>')
      .attr('href', params.link_url)
      .attr('target', '_blank')
      .addClass('zenback-ads-ad')
      .click(params.ga_click_tracking_code);
    var $zb_ads_link_span = zb_jq('<span></span>')
      .addClass('link');
    $zb_ads_link_span.append($zb_ads_text_a.clone().text(params.view_url));
    $zb_ads_text_a.text(params.ad_text);
    var $zb_ads_metadata_span = zb_jq('<span></span>')
      .addClass('metadata');
    var $zb_ads_author_span = zb_jq('<span></span>')
      .addClass('author');
    var $zb_ads_twitter_a = zb_jq('<a></a>')
      .attr('href', params.twitter_url)
      .attr('target', '_blank')
      .addClass('zenback-ads-twitter')
      .click(params.ga_click_tracking_code);
    var $zb_ads_twitter_a_clone = $zb_ads_twitter_a.clone()
      .text('@' + params.twtiter_screen_name);
    var $zb_ads_img = zb_jq('<img />')
      .attr('src', params.twitter_icon_url)
      .attr('alt', params.twtiter_screen_name);
    $zb_ads_twitter_a.append($zb_ads_img);
    var $zb_ads_strong = zb_jq('<strong></strong>')
      .append($zb_ads_twitter_a_clone);
    $zb_ads_author_span.append($zb_ads_twitter_a)
      .append($zb_ads_strong)
      .append(zb_jq('<br />'))
      .append(zb_jq('<span></span>').text(params.twtiter_screen_name));
    $zb_ads_metadata_span.append($zb_ads_author_span);
    $zb_ads_body.append($zb_ads_text_a)
      .append(zb_jq('<br />'))
      .append($zb_ads_link_span)
      .append($zb_ads_metadata_span)
      .show();
    result = true;
  } catch (e) {
    _zb_print_error(e.message);
  }
  return result;
};


zenbackの広告のエリア。
なぜ全部動的に生成する必要があるんだろう??
jQuery使ってるなら変数部だけ差し込めばいい気もするんだけど。



zb_show_default_ad_view()


var zb_show_default_ad_view = function (ga_category) {
  var ga_category = ga_category || 'default';
  var link_url = 'http://zenback.jp/?s=ads_fl01';
  var view_url = 'http://zenback.jp';
  var result = zb_render_ad_view({
    link_url: link_url
    ,view_url: view_url
    ,ad_text: 'zenbackは自分のブログの記事と、過去の自分の記事、他のブログ記事、TwitterやFacebookなどソーシャルメディアをつなげるウィジェットです。多くのブログサービスに対応。設置はコードをコピペするだけ、5分で完了します。ご利用は無料です。'
    ,twitter_url: 'http://twitter.com/zenback'
    ,twitter_icon_url: 'http://a1.twimg.com/profile_images/1185414381/zenback_icon_normal.png'
    ,twtiter_screen_name: 'zenback'
    ,ga_click_tracking_code: function () {
      _gaq.push(['zb_ads._trackEvent', ga_category, 'click', link_url]);
    }
  });
  if (result) {
    _gaq.push(['zb_ads._trackPageview', ga_category, link_url]);
  } else {
    _zb_print_error('Failed the default ad rendering.');
  }
};


諸々のエラーでロードが出来なかったときに、デフォルトで表示される広告の情報を作ってる。



zb_load_twitter_favorites(), zb_select_ad_tweet(), zb_show_ad_view()


var zb_load_twitter_favorites = function (twitter_id, load_count, callback) {
  try {
    var retry = 4;
    var favorites_api_base_url = 'https://api.twitter.com/1/favorites.json';
    var favorites_api_call_url = favorites_api_base_url + '?id=' + twitter_id + '&count=' + load_count;
    zb_load_jsonp({ url: favorites_api_call_url, retry: retry }, function (err, jsonp) {
      if (err) {
        callback(err);
        return;
      }
      callback(null, jsonp);
    });
  } catch (e) {
    callback(e);
  }
};
var zb_select_ad_tweet = function (free_tweets, filler_tweets, callback) {
  try {
    if (!free_tweets || !filler_tweets) {
      callback({
        message: 'Invalid parameter'
      }, null);
      return;
    }
    var timestamp = (+new Date());
    var select_index = timestamp % 40;
    if (select_index < free_tweets.length) {
      callback(null, {
        ad: free_tweets[select_index]
        ,ga_category: 'zbcf'
      });
    } else {
      if (filler_tweets.length === 0) {
        callback({
          message: 'Nothing filler'
        }, null);
      } else {
        callback(null, {
          ad: filler_tweets[timestamp % filler_tweets.length]
          ,ga_category: 'zbcf2'
        });
      }
    }
  } catch (e) {
    _zb_print_error(e.message);
    callback(e);
  }
};
var zb_show_ad_view = function (tweet, ga_category) {
  if (!tweet) {
    return;
  }
  var link_url = 'https://twitter.com/#!/' + tweet.user.screen_name + '/status/' + tweet.id_str;
  var view_url = link_url;
  var url_regex = /((?:https?):\/\/[!-~]+)/m;
  if (tweet.text.match(url_regex)) {
    link_url = RegExp.$1;
    view_url = RegExp.$1;
  }
  var twitter_url = 'http://twitter.com/' + tweet.user.screen_name;
  var result = zb_render_ad_view({
    link_url: link_url
    ,view_url: view_url
    ,ad_text: tweet.text
    ,twitter_url: twitter_url
    ,twitter_icon_url: tweet.user.profile_image_url
    ,twtiter_screen_name: tweet.user.screen_name
    ,ga_click_tracking_code: function () {
      _gaq.push(['zb_ads._trackEvent', ga_category, 'click', tweet.id_str]);
    }
  });
  if (result) {
    _gaq.push(['zb_ads._trackPageview', ga_category, tweet.id_str]);
  } else {
    _zb_print_error('Failed the free ad rendering.');
  }
};


読むのがめんどくさくなってきたので省略…
twitterのお気に入りとか広告の表示処理っぽい。



広告のロード処理


try {
  var ads_free_id = 'zbcf';
  var ads_free_load_number = 40;
  zb_load_twitter_favorites(ads_free_id, ads_free_load_number, function (err, data) {
    if (err) {
      _zb_print_error(err.message);
      zb_show_default_ad_view();
      return;
    }
    var ads_free_tweets = data; 
    var ads_filler_id = 'zbcf2';
    var ads_filler_load_number = 40;
    zb_load_twitter_favorites(ads_filler_id, ads_filler_load_number, function (err, data) {
      if (err) {
        _zb_print_error(err.message);
        zb_show_default_ad_view();
        return;
      }
      var ads_filler_tweets = data;
      zb_select_ad_tweet(ads_free_tweets, ads_filler_tweets, function (err, data) {
        if (err) {
          _zb_print_error(err.message);
          zb_show_default_ad_view();
          return;
        }
        zb_show_ad_view(data.ad, data.ga_category);
      });
    });
  });
 
} catch (e) {
  _zb_print_error(e.message);
  zb_show_default_ad_view();
}
  })();


さっき省略した広告等のロード処理。
twitterのIDが@zbcfか@zbcf2のツイート内容を出力している?
ロードに失敗したときでも、zb_show_default_ad_view()でデフォルトの広告を出そうとしている所は良い感じ(作り手側の視点として)。




…と、ざっくり全処理を見てみました。
個人的に、気になった事や、後でもう少し勉強しておきたい箇所を纏めておきます。

  • zb_load_script()の非同期処理(コールバックあり)は汎用性がありそう
  • 各ボタンの、クリックログをとる仕組み
  • google analysticsの、_gaq.pushに関する仕様を押さえておく
  • _zb_print_error()は、console.logではなくってajaxでサーバサイドに投げておくと便利かも。
  • zb_ga_track_xxx()関数郡のつくりは明らかにおかしいよね??(共通関数化して欲しい)

あと、ソーシャルブックマーク系ボタンの処理を調べてるときに見つけたサイトで、以下のページが勉強になりそうでした。
はてなダイアリーにいいねボタンを置く方法
【図解】iGoogleガジェットをブログパーツとして活用する方法。


関連記事

コメントを残す

メールアドレスが公開されることはありません。