// ==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; }