Skip to content

Instantly share code, notes, and snippets.

@saitamanodoruji
Last active April 26, 2024 10:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saitamanodoruji/6419059 to your computer and use it in GitHub Desktop.
Save saitamanodoruji/6419059 to your computer and use it in GitHub Desktop.
Google search + twitter search mashup
// ==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);
@originalpete
Copy link

Does this still work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment