// ==UserScript==
// @name Side Ustream
// @namespace http://so-kukan.com/
// @description Twitter の左サイドに Ustream のチャンネルを表示
// @include https://twitter.com/*
// @include http://twitter.com/*
// ==/UserScript==
// UPDATE INFO http://trac.so-kukan.com/tools/wiki/SideUstream
//
// 動作環境
// * Safari + GreaseKit
// * Firefox + Greasemonkey
// * Mac OS 10.5 でのみ確認
//
// 更新履歴
// [2009-07-26] 0.9.1 for (〜 in 〜)の挙動が変わったことによる影響に対応(Safariでのみ発生)
// [2009-06-26] 0.9 最初の公開バージョン
// Side Ustream に関する情報
var sb_info = {
version: '0.9.1', // バージョン番号
title: 'Ustream', // サイドバーに表示するタイトル
id: 'ustream', // id
short_id: 'ust', // ショートid
url: 'http://ustchannelid.appspot.com/?channel=$(channel)&callback=$(callback)',
// Ustream API(JSONP) でチャンネルIDを取得する
sizes: { // リサイズできる表示サイズ
'200x160': {width:200, height:160},
'400x320': {width:400, height:320},
},
styles: [
'#ust-container { position:fixed; top:75px; left:10px; margine:10px; }',
'.ust_form { padding:2px 18px 2px 14px; text-align:right; }',
'.ust_input { display:block; width:164px; margin:4px 0; }',
'.ust_action { margin:0 0 0 4px; }',
'.ust_resize { margin:4px 0 4px 0; text-align:left; }',
'.ust_resize .ust_action { margin:2px 4px 2px 0; }',
'.ust_resize .ust_selected { background-color:#EE4; border-color:#EE0; }',
],
// 設定のデフォルト値(これは修正しない。user_config のほうを修正すること)
sidebar_inserts: ['pb-group', 'following'], // サイドバーの挿入位置
currSize: '200x160',
};
// ユーザ設定
var user_config = {
// sidebar_inserts: ['following'], // サイドバーの挿入位置
// currSize: '400x320',
};
if (document.getElementById('container'))
{ // id 'container' がある場合のみ実行
// デフォルト値とユーザ設定をマージする
var config = hash_marge(sb_info, user_config);
if (! sb_config_init(config))
{ // サイドバーの挿入位置がみつからない
return;
}
// Ustream の表示エリアを作成する
var b = document.getElementsByTagName('body')[0];
b.appendChild(div(null, {id:'ust-container'}));
// Ustream のための表示エリアを左サイドに表示
side_ustream(config);
}
// Ustream のための表示エリアを左サイドに表示
function side_ustream(config)
{
var menu = sb_get_menu(config);
var list = menu.getElementsByTagName('ul')[0];
var f = form(null, {'onSubmit':'return false', 'class':'ust_form'});
var inputField = element('input', null, {'class':'ust_input'});
var old;
function onsubmit(e)
{
old = ust_on(inputField.value, old, config);
}
function offsubmit(e)
{
ust_off(config);
}
function channel_select(json)
{
ust_select(json, config);
}
// グローバルに定義する
set_global('ust_channel_select', channel_select);
// 前に見ていたチャンネルをフィールドにセットする
inputField.value = get_local_storage('ust_channel');
// フォームを作成する
f.addEventListener('submit', onsubmit, false);
f.appendChild(inputField);
f.appendChild(button('OFF', {'class':'ust_action'}, offsubmit));
f.appendChild(button('ON', {'class':'ust_action'}, onsubmit));
list.appendChild(f);
}
function ust_select(json, config)
{
var liveShow = 'Video clips at Ustream'
var ust_container = document.getElementById('ust-container');
var channelId = json['results'];
if (! channelId)
{
ust_off(config, json['msg']);
return;
}
// 古いUstreamオブジェクトを削除
remove_by_class_name(ust_container, 'ust_object')
var attr = hash_marge(config.sizes[config.currSize], {channelid:escape(channelId)});
var o = str_expand(liveShow, attr);
var e = div(o, {'class':'ust_object'});
// リサイズ・ボタンの追加
var resize_attr = {'class':'ust_action'};
var resize_btns = [];
for (var key in config.sizes)
{
resize_btns.push(button(key, resize_attr,
function(e) {
ust_resize(config.sizes[this.value]);
ust_update_button(resize_btns, this.value);
}));
}
e.appendChild(div(resize_btns, {'class':'ust_resize'}));
// ボタンの更新
ust_update_button(resize_btns, config.currSize);
// スタイルシートのの変更
ust_update_style(attr);
// Ustreamオブジェクトを追加
ust_container.appendChild(e);
// ロード終了
var menu = sb_get_menu(config);
remove_class(menu, 'loading');
}
// チャンネル ON
function ust_on(channel, old, config)
{
set_local_storage('ust_channel', channel);
if (channel)
{
var menu = sb_get_menu(config);
sb_show_error('', config);
add_class(menu, 'loading');
channel = encodeURIComponent(channel);
var url = str_expand(config.url, {channel:channel, callback:'ust_channel_select'});
old = jsonp(url, old)
}
else
{
offsubmit(config);
}
return old;
}
// チャンネル OFF
function ust_off(config, msg)
{
var ust_container = document.getElementById('ust-container');
var h = document.getElementsByTagName('head')[0];
var menu = sb_get_menu(config);
sb_show_error(msg, config);
remove_class(menu, 'loading');
remove_by_class_name(ust_container, 'ust_object');
remove_by_class_name(h, 'ust-style');
}
function ust_resize(attr)
{
var ust = document.getElementById('ust-container');
if (! ust) return;
var objElmt = ust.getElementsByTagName('object')[0];
if (objElmt.getAttribute('width') == attr['width'] &&
objElmt.getAttribute('height') == attr['height'])
{ // 変更の必要がない
return;
}
objElmt.setAttribute('width', attr['width']);
objElmt.setAttribute('height', attr['height']);
var embElmt = objElmt.getElementsByTagName('embed')[0];
embElmt.setAttribute('width', attr['width']);
embElmt.setAttribute('height', attr['height']);
var a = ust.getElementsByTagName('a')[0];
a.setAttribute('width', attr['width']);
// スタイルシートのの変更
ust_update_style(attr);
}
function ust_update_button(btns, key)
{
var klass = 'ust_selected';
for (var i in btns)
{
if (isNaN(i)) continue;
var e = btns[i];
if (e.value == key)
add_class(e, klass);
else
remove_class(e, klass);
}
}
// スタイルシートのの変更
function ust_update_style(attr)
{
var h = document.getElementsByTagName('head')[0];
remove_by_class_name(h, 'ust-style');
if (attr)
{
var attr2 = hash_marge(attr, {'margin_left':attr['width'] + 20});
var s = str_expand('#container { margin-left:$(margin_left)px; }', attr2);
h.appendChild(style(s, {'class':'ust-style'}));
}
}
//--------------------
// ローカルストレージからデータを取出す
function get_local_storage(name)
{
if (typeof localStorage != 'undefined')
return localStorage.getItem(name);
else if (typeof GM_getValue != 'undefined')
return GM_getValue(name, null);
else
return null;
}
// ローカルストレージにデータを格納する
function set_local_storage(name, value)
{
if (typeof localStorage != 'undefined')
localStorage.setItem(name, value);
else if (typeof GM_getValue != 'undefined')
GM_setValue(name, value);
}
// グローバルに定義する
function set_global(name, value)
{
if (typeof unsafeWindow != 'undefined')
{ // Greasemonkey の場合
unsafeWindow[name] = value;
}
else
{
window[name] = value;
}
}
// 複数の連想配列をマージする
function hash_marge()
{
var h = {};
for (var i = 0; i < arguments.length; i++)
{
var a = arguments[i];
for (var key in a) h[key] = a[key];
}
return h;
}
// 文字列に含まれる $VAR or $(VAR) を連想配列の値で展開する
function str_expand(str, vars)
{
return str.replace(/\$(([A-z_]+)|\(([A-z_]+)\))/g,
function(m, m1, m2, m3)
{
var key = m2?m2:m3;
var v = vars[key];
if (typeof v == 'undefined')
return m;
else
return v;
}
);
}
// 名前からコンテントを取得
function get_content_by_name(name)
{
var e = document.getElementsByName(name);
if (0 < e.length)
return e[0].content;
else
return null;
}
// クラス名で指定された要素を削除する
function remove_by_class_name(parent, className)
{
var elements = parent.getElementsByClassName(className);
for (var i = 0; i < elements.length; i++)
{
parent.removeChild(elements[i]);
}
}
// クラスをトグルさせる
function toggle_class(e, className)
{
var klass = e.getAttribute('class');
var c = klass?klass.split(' '):[];
var i = c.indexOf(className);
if (i == -1)
c.push(className);
else
c.splice(i, 1);
e.setAttribute('class', c.join(' '));
return (i == -1);
}
// クラスを追加する
function add_class(e, className)
{
var klass = e.getAttribute('class');
var c = klass?klass.split(' '):[];
var i = c.indexOf(className);
if (i == -1)
{
c.push(className);
e.setAttribute('class', c.join(' '));
}
}
// クラスを削除する
function remove_class(e, className)
{
var klass = e.getAttribute('class');
var c = klass?klass.split(' '):[];
var i = c.indexOf(className);
if (i != -1)
c.splice(i, 1);
e.setAttribute('class', c.join(' '));
}
// 要素を作成する
function element(tag, obj, attr)
{
var e = document.createElement(tag);
var a;
if ((typeof obj == 'object') && (obj instanceof Array))
a = obj;
else
a = [obj];
for (var i in a)
{
if (isNaN(i)) continue;
var o = a[i];
if (o)
{
// if (typeof o == 'object' && o instanceof HTMLElement)
if (typeof o == 'object' && ! (o instanceof String))
e.appendChild(o);
else
e.innerHTML += o;
}
}
if (attr)
{
for (var key in attr) e.setAttribute(key, attr[key]);
}
return e;
}
// div要素を作成する
function div(obj, attr)
{
return element('div', obj, attr);
}
// form要素を作成する
function form(obj, attr)
{
return element('form', obj, attr);
}
// span要素を作成する
function span(obj, attr)
{
return element('span', obj, attr);
}
// style要素を作成する
function style(obj, attr)
{
var e = document.createElement('style');
e.setAttribute('type', 'text/css');
if (attr)
{
for (var key in attr) e.setAttribute(key, attr[key]);
}
if ((typeof obj == 'object') && (obj instanceof Array))
obj = obj.join(' ');
try {
// Firefox の場合
e.innerHTML = obj;
} catch(err) {
// Safari の場合
e.innerText = obj;
}
return e;
}
// ボタンを作成する
function button(text, attr, listener)
{
var e = element('input', null, attr);
e.type = 'button';
e.value = text;
if (listener)
e.addEventListener('click', listener, false);
return e;
}
// JSONP をヘッダに追加する
function jsonp(url, old, onerror)
{
var h = document.getElementsByTagName('head')[0];
var s = document.createElement('script');
s.setAttribute('type', 'text/javascript');
s.setAttribute('src', url);
s.setAttribute('charset', 'UTF-8');
if (onerror) s.onerror = onerror;
// ヘッダに追加する
h.appendChild(s);
try {
// 古いのは削除しておく
if (old) h.removeChild(old);
} catch(e) {
}
return s;
}
//--------------------
// デバッグ用にエラー内容を表示する
function sb_show_error(e, config)
{
var menu = sb_get_menu(config);
var klass = 'sb-message';
remove_class(menu, 'loading');
remove_by_class_name(menu, klass);
if (e)
{
var msg = span(e, {'class':klass});
var hr = menu.getElementsByTagName('hr');
if (0 < hr.length && menu.firstChild != hr[0])
menu.insertBefore(msg, hr[0]);
else
menu.appendChild(msg);
}
}
// config の初期化
function sb_config_init(config)
{
for (var i in config.sidebar_inserts)
{
var id = config.sidebar_inserts[i];
if (document.getElementById(id))
{
config.sidebar_insert_pos = id;
break;
}
}
if (! config.sidebar_insert_pos)
{ // サイドバーの挿入位置がみつからない
return false;
}
if (! config.lang)
{
try {
config.lang = navigator.language.substr(0, 2);
} catch(e) {
config.lang = 'ja'; // デフォルト言語は 'ja'
}
}
return true;
}
// メニューを返す
function sb_get_menu(config)
{
var m = document.getElementById(config.id);
if (m)
{ // 既にメニューがつくられている場合はそれを返す
return m;
}
// メニューを作成してサイドバーに挿入する
m = sb_insert_side(config.sidebar_insert_pos, sb_create_menu(config));
return m;
}
// メニューをサイドバーに挿入する
function sb_insert_side(insert_pos, menu)
{
var side = document.getElementById('side');
var nextElement = document.getElementById(insert_pos).nextSibling;
while ((typeof nextElement.tagName == 'undefined')
|| (nextElement.tagName == 'hr'))
{
nextElement = nextElement.nextSibling;
}
if (nextElement.getAttribute('id') == 'rssfeed')
menu.insertBefore(document.createElement('hr'), menu.firstChild);
else
menu.appendChild(document.createElement('hr'));
side.insertBefore(menu, nextElement);
return menu;
}
// メニューのタイトル要素を作成する
function sb_create_title(config, listener)
{
var title = document.createElement('h2');
var linkover = false;
title.setAttribute('class', 'sidebar-title');
title.setAttribute('id', config.short_id + '_menu');
title.appendChild(span(config.title));
if (listener)
{ // アンカータグの上でクリックされていないときのみ実行する
title.addEventListener('click', function() { if (! linkover) listener() }, false);
}
if (config.link)
{
title.appendChild(span(' '));
var link = document.createElement('a');
link.setAttribute('href', config.link);
link.setAttribute('target', '_blank');
link.appendChild(span('go'));
// アンカータグの上でクリックされたときに他の動作が実行されないようにするためにフラグをセットする
link.addEventListener('mouseover', function() { linkover = true }, false);
link.addEventListener('mouseout', function() { linkover = false }, false);
title.appendChild(link);
}
return title;
}
// リスト要素を作成する
function sb_create_list(config)
{
var list = document.createElement('ul');
var classes = ['sidebar-menu'];
classes.push(config.id + '-links');
list.setAttribute('class', classes.join(' '));
return list;
}
// メニュー要素を作成する
function sb_create_menu(config, collapsed)
{
var menu = document.createElement('div');
var classes = ['collapsible'];
if (collapsed) classes.push('collapsed');
if (config.style == 'cloud')
{ // 表示スタイルがクラウドの場合
classes.push('cloud');
// style要素の追加
var s = [
'.cloud ul.sidebar-menu { padding-left:10px; }',
'.cloud ul.sidebar-menu li, .cloud ul.sidebar-menu li a { display:inline; width:auto; padding:2px; }',
'.cloud ul.sidebar-menu li a span { width:auto; }',
];
menu.appendChild(style(s));
}
menu.appendChild(style(str_expand('#$(id) span.sb-message { display:inline-block; padding-left:14px; }', config)));
if (config.styles)
menu.appendChild(style(config.styles));
menu.setAttribute('class', classes.join(' '));
menu.setAttribute('id', config.id);
// 表示・非表示をトグルさせる
function toggleCollapse()
{
toggle_class(menu, 'collapsed');
}
menu.appendChild(sb_create_title(config, toggleCollapse));
menu.appendChild(sb_create_list(config));
// menu.appendChild(document.createElement('hr'));
return menu;
}