Last active
April 26, 2024 10:15
-
-
Save saitamanodoruji/6419059 to your computer and use it in GitHub Desktop.
Google search + twitter search mashup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name SearchTweetsOnGoogle | |
// @namespace saitamanodoruji | |
// @description Google search + twitter search mashup | |
// @include http://*.google.*/search?* | |
// @include https://*.google.*/search?* | |
// @version 0.0.2.5 | |
// @update 2013-09-10 | |
// @author saitamanodoruji | |
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js | |
// @require https://raw.github.com/dankogai/js-base64/master/base64.js | |
// ==/UserScript== | |
// origin: http://llcheesell.com/post/85753786/install-this-greasemoneky-google-twitter-mashup | |
// http://files.vilvo.net/googletwittermashup.user.js | |
// 使う前に必要な準備: Twitter に app-only auth して access token をもらう | |
// (1) https://dev.twitter.com/apps から app を登録する | |
// (2) consumer key と consumer secret が発行される | |
// (3) Twitter からサインアウトした状態で Greasemonkey ユーザーコマンドの get token を実行して | |
// consumer key と consumer secret を入力する | |
// (4) access token が発行される (GM_setValue で Preference に保存される) | |
// http://d.hatena.ne.jp/saitamanodoruji/20130906/1378455908 | |
(function executeSearchTweetsOnGoogle(doc) { | |
// ================ Setting ================ | |
const NUM_OF_TWEETS = 100; | |
// ================ User Commands ================ | |
// app-only auth | |
// サインアウトした状態で実行しないとエラーになる | |
GM_registerMenuCommand('SearchTweetsOnGoogle - get token', function() { | |
console.log('SearchTweetsOnGoogle - get token'); | |
var consumerKey = prompt('SearchTweetsOnGoogle - get token:\nEnter consumer key'); | |
var consumerSecret = prompt('SearchTweetsOnGoogle - get token:\n' | |
+ 'consumer key you entered is ' + consumerKey + '\nEnter consumer secret'); | |
var base64EncodedBearerTokenCredentials = Base64.toBase64([consumerKey, consumerSecret].join(':')); | |
consumerSecret = null; | |
GM_xmlhttpRequest({ | |
method: 'POST', | |
url: 'https://api.twitter.com/oauth2/token', | |
data: 'grant_type=client_credentials', | |
headers: { | |
"User-Agent": "SearchTweetsOnGoogle", | |
"Authorization": "Basic " + base64EncodedBearerTokenCredentials, | |
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", | |
}, | |
onload: function(res) { | |
if (res.status === 200) { | |
var returnedObject = JSON.parse(res.responseText); | |
console.log('token_type = ' + returnedObject.token_type); | |
console.log('access_token = ' + returnedObject.access_token); | |
GM_setValue('accessToken', returnedObject.access_token); | |
console.log('Access token was saved to Preference as "accessToken".'); | |
alert('OAuth completed successfully. Access token was saved.\n' | |
+ 'access token:\n' + returnedObject.access_token); | |
} else { | |
alert('OAuth failed.'); | |
} | |
} | |
}); | |
}); | |
// access token の破棄 | |
GM_registerMenuCommand('SearchTweetsOnGoogle - invalidate token', function() { | |
console.log('invalidate token'); | |
var consumerKey = prompt('SearchTweetsOnGoogle - invalidate token:\nEnter consumer key'); | |
var consumerSecret = prompt('SearchTweetsOnGoogle - invalidate token:\n' | |
+ 'consumer key you entered is ' + consumerKey + '\nEnter consumer secret'); | |
var base64EncodedBearerTokenCredentials = Base64.toBase64([consumerKey, consumerSecret].join(':')); | |
consumerSecret = null; | |
GM_xmlhttpRequest({ | |
method: 'POST', | |
url: 'https://api.twitter.com/oauth2/invalidate_token', | |
data: 'access_token=' + GM_getValue('accessToken'), | |
headers: { | |
"User-Agent": "SearchTweetsOnGoogle", | |
"Authorization": "Basic " + base64EncodedBearerTokenCredentials, | |
"Content-Type": "application/x-www-form-urlencoded", | |
}, | |
onload: function(res) { | |
if (res.status === 200) { | |
console.log(res.responseText); | |
var returnedObject = JSON.parse(res.responseText); | |
console.log('access_token = ' + returnedObject.access_token); | |
GM_setValue('accessToken', ''); | |
console.log('Access token was invalidated.'); | |
alert('The invalidation completed successfully.\n' | |
+ 'invalidated access token:\n' + returnedObject.access_token); | |
} else { | |
alert('OAuth failed.'); | |
} | |
} | |
}); | |
}) | |
// access token の確認 | |
GM_registerMenuCommand('SearchTweetsOnGoogle - check saved access token', function() { | |
alert(GM_getValue('accessToken')); | |
console.log(GM_getValue('accessToken')); | |
}) | |
// rate limit status の確認 | |
GM_registerMenuCommand('SearchTweetsOnGoogle - check rate limit status', function() { | |
GM_xmlhttpRequest({ | |
method: 'GET', | |
url: 'https://api.twitter.com/1.1/application/rate_limit_status.json', | |
headers: { | |
Authorization: 'Bearer ' + GM_getValue('accessToken'), | |
}, | |
onload: function(res) { | |
if (res.status === 200) { | |
console.log(res.responseText); | |
var returnedObject = JSON.parse(res.responseText); | |
var limits = returnedObject.resources.search['/search/tweets']; | |
var timeToReset = new Date(limits.reset * 1000).toTimeString(); | |
alert('Remaining: ' + limits.remaining + '\nReset: ' + timeToReset); | |
} else { | |
alert('failed.'); | |
} | |
}, | |
}); | |
}); | |
// ================ Main Process ================ | |
// --- 検索結果の表示エリアを準備 --- | |
var rules = [ | |
"#tsr {", | |
"border: 1px solid #EEEEEE;", | |
"margin: 0 40px 10px 0;", | |
"box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);", | |
"min-width: 268px;", | |
"max-width: 456px;", | |
"}", | |
"#tsr .tsr-title {", | |
"margin: 0 10px -15px 10px;", | |
"position: relative;", | |
"top: -15px;", | |
"font-size: 20px;", | |
"}", | |
"#tsr .tsr-title span {", | |
"background: none repeat scroll 0 0 #FFFFFF;", | |
"padding: 0 5px;", | |
"}", | |
"#tsr .tsr-title a {", | |
"color: #1F98C7;", | |
"text-decoration: none;", | |
"}", | |
"#tsr .tsr-title a:hover {", | |
"color: #1F98C7;", | |
"text-decoration: underline;", | |
"}", | |
"#tsr .results {", | |
"padding-top: 5px;", | |
"}", | |
"#tsr .result {", | |
"padding: 5px 15px 10px;", | |
"}", | |
"#tsr .result .account-group {", | |
"text-decoration: none;", | |
"}", | |
"#tsr .result .account-group .avatar {", | |
"float: left;", | |
"margin-right: 10px;", | |
"-moz-force-broken-image-icon: 1;", | |
"border-radius: 5px 5px 5px 5px;", | |
"}", | |
"#tsr .result .account-group .fullname {", | |
"color: #333333;", | |
"}", | |
"#tsr .result .account-group .username {", | |
"color: #AAAAAA;", | |
"}", | |
"#tsr .result .account-group:hover .fullname {", | |
"color: #1F98C7;", | |
"text-decoration: underline;", | |
"}", | |
"#tsr .result .tweet-text, #tsr .timestamp {", | |
"margin-left: 58px;", | |
"}", | |
"#tsr .result .tweet-text a {", | |
"color: #1F98C7;", | |
"text-decoration: none;", | |
"}", | |
"#tsr .result .timestamp a {", | |
"color: #AAAAAA;", | |
"text-decoration: none;", | |
"}", | |
"#tsr .result .tweet-text a:hover, #tsr .timestamp a:hover {", | |
"color: #1F98C7;", | |
"text-decoration: underline;", | |
"}", | |
"#tsr .clear {", | |
"clear: both;", | |
"}", | |
].join(''); | |
GM_addStyle(rules); | |
// if #mbEnd already exists then remove it | |
var mbEnd = doc.getElementById("mbEnd"); | |
if (mbEnd) mbEnd.parentNode.removeChild(mbEnd); | |
// if #rhs doesn't exists then create it | |
if (!doc.getElementById("rhs")) { | |
var rhs = doc.createElement("div"); | |
rhs.id = "rhs"; | |
doc.getElementById("rhscol").appendChild(rhs); | |
} | |
// Create new div for twitter search results and append into #rhs | |
var tsr = doc.createElement("div"); | |
tsr.id = "tsr"; | |
var spinner = [ | |
"<div id='spinner'>", | |
"<h2>", | |
"<img src='http://img413.imageshack.us/img413/8999/ajaxloaderbs5.gif' /> searching Twitter ", | |
"</h2>", | |
"</div>" | |
].join(''); | |
tsr.innerHTML = "<ol class='results'>"+spinner+"</ol>"; | |
rhs = doc.getElementById("rhs"); | |
rhs.appendChild(tsr); | |
// --- get the JSON and manipulate the results --- | |
// grab the search query from the input element on the page | |
// tokenise it and join with + for twitter | |
var q = $("input[name=q]")[0].value; | |
var esc_q = q.split(' ').join('+').replace('#','%23'); | |
// get twitter search results and add it to ol.results | |
GM_xmlhttpRequest({ | |
method: 'GET', | |
url: [ | |
'https://api.twitter.com/1.1/search/tweets.json?', | |
'q=', esc_q, | |
'&count=', NUM_OF_TWEETS].join(''), | |
headers: { | |
Authorization: 'Bearer ' + GM_getValue('accessToken'), | |
}, | |
onload: function(res) { | |
var r; | |
var result = JSON.parse(res.responseText) | |
$("#spinner").hide(); | |
$("#tsr").prepend([ | |
"<div class='tsr-title'><span>", | |
"<a target='_blank' href='https://twitter.com/search?q=" + esc_q.split('+').join('%20') + "'>", | |
"Twitter search for '" + $("input[name=q]").text(q).html() + "'", | |
"</a>", | |
"</span></div>" | |
].join('')); | |
$.each(result.statuses, function(i, stat) { | |
r = [ | |
"<li class='result'>", | |
"<a class='account-group' target='_none' href='http://twitter.com/" + stat.user.screen_name + "'>", | |
"<img class='avatar' src='" + stat.user.profile_image_url + "'>", | |
"<strong class='fullname'>" + stat.user.name + "</strong>", | |
" <span class='username'>@" + stat.user.screen_name + "</span>", | |
"</a>", | |
"<div class='tweet-text'>" + stat.text.replace(/(http\:\/\/t\.co\/[a-zA-Z0-9]{10})/g, "<a target='_none' href='$1'>$1</a>") + "</div>", | |
"<div class='timestamp'>", | |
"<a target='_none' href='http://twitter.com/" + stat.user.screen_name + "/status/" + stat.id_str + "'>" + new Date(stat.created_at).toLocaleFormat('%Y-%m-%d %T %Z') + "</a>", | |
"</div>", | |
"<div class='clear'></div>", | |
"</li>", | |
].join(''); | |
$("#tsr > ol.results").append(r); | |
if (i == NUM_OF_TWEETS) return false; | |
}); | |
}, | |
}); | |
})(document); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Does this still work?