Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

executable file 1376 lines (1251 sloc) 40.653 kB
<html>
<head></head>
<body>
<script type="text/javascript" src="lib/3rdparty/jquery.js"></script>
<script type="text/javascript" src="lib/3rdparty/oauth.js"></script>
<script type="text/javascript" src="lib/3rdparty/sha1.js"></script>
<script type="text/javascript" src="lib/twitter_lib.js"></script>
<script type="text/javascript" src="lib/icon_creator.js"></script>
<script type="text/javascript" src="lib/expander_lib.js"></script>
<script>
urlExpander = new Expander();
OptionsBackend = {
defaultOptions: {
home_refresh_interval: 90 * 1000,
mentions_refresh_interval: 150 * 1000,
dms_refresh_interval: 150 * 1000,
lists_refresh_interval: 150 * 1000,
favorites_refresh_interval: 600 * 1000,
tweets_per_page: 10,
max_cached_tweets: 30,
url_shortener: 'bitly',
shortener_acct: false,
shortener_login: '',
shortener_key: '',
name_attribute: 'name',
request_timeout: 6000,
compose_position: 'top',
hover_timeout: 1000,
base_url: 'http://twitter.com/',
base_signing_url: 'http://twitter.com/',
base_authorize_url: 'https://twitter.com/oauth/authorize?oauth_token=',
use_desktop_notifications: false,
home_on_page: false,
home_icon: true,
mentions_on_page: true,
mentions_icon: true,
dms_on_page: true,
dms_icon: true,
favorites_on_page: false,
favorites_icon: false,
lists_on_page: true,
lists_icon: true,
home_visible: true,
mentions_visible: true,
dms_visible: true,
favorites_visible: true,
lists_visible: true,
unified_visible: true,
home_include_unified: true,
mentions_include_unified: true,
dms_include_unified: true,
favorites_include_unified: false,
lists_include_unified: false,
reply_all: false,
show_expanded_urls: true,
lists_count: 1,
idle_color: '#4880a6',
home_color: '#9c2e2e',
mentions_color: '#7f870b',
dms_color: '#7f870b',
favorites_color: '#7f870b',
lists_color: '#7f870b',
tweets_color_only_unified: false,
home_tweets_color: 'rgba(0, 72, 255, 0.15)',
mentions_tweets_color: 'rgba(255, 255, 0, 0.15)',
dms_tweets_color: 'rgba(0, 255, 0, 0.15)',
lists_tweets_color: 'rgba(255, 0, 0, 0.15)',
favorites_tweets_color: 'rgba(0, 0, 0, 0)',
notification_fade_timeout: 6000,
theme: 'css/chromified.css,css/chromified-theme/jquery-ui-1.7.2.custom.css',
font_family: 'Helvetica, Arial, sans-serif',
font_size: '1.0em'
},
cachedOptions: null,
save: function(optionsMap) {
localStorage['options'] = JSON.stringify(optionsMap);
this.cachedOptions = this.load();
},
load: function(forceDefault) {
var map = $.extend(true, {}, this.defaultOptions);
if(forceDefault) {
return map;
}
try {
var parsedMap = JSON.parse(localStorage['options']);
if(parsedMap)
$.extend(true, map, parsedMap);
} catch(e) { /* ignored */ }
return map;
},
get: function(option) {
if(this.cachedOptions == null) {
this.cachedOptions = this.load();
}
return this.cachedOptions[option];
}
};
Utils = {
pushTweet: function(list, tweet) {
var baseTime = Date.parse(tweet.created_at);
var i = 0;
for(; i < list.length; ++i) {
var tweetTime = Date.parse(list[i].created_at);
if(baseTime >= tweetTime) {
break;
}
}
if(i == list.length || list[i].id != tweet.id)
list.splice(i, 0, tweet);
},
makeJoinableCallback: function(joinCount, eachCallback, joinCallback) {
return function() {
joinCount--;
eachCallback.apply(this, arguments);
if(joinCount == 0) {
joinCallback();
}
};
}
};
function UnifiedTweetsTimeline(timelines) {
this.timelines = timelines;
this.timelineId = 'unified';
this.tweetsPerPage = OptionsBackend.get('tweets_per_page');
this.visibleTweetsList = [];
this.currentPage = 0;
this.currentScroll = 0;
}
UnifiedTweetsTimeline.prototype = {
killTimeline: function() {
/*noop*/
},
stopTimer: function() {
/*noop*/
},
eachTimeline: function(callback) {
for(var tId in this.timelines) {
var timeline = this.timelines[tId];
if(timeline.includeInUnified) {
callback.call(this, timeline);
}
}
},
countTimelines: function() {
var count = 0;
this.eachTimeline(function(timeline) {
if(timeline.includeInUnified) {
++count;
}
});
return count;
},
giveMeTweets: function(callback, syncNew, cacheOnly) {
// We shouldn't receive syncNew requests so they'll be ignored.
if(this.currentCallback) {
//Handling multiple calls to giveMeTweets, just update the registered
//callback and let the first request finish.
this.currentCallback = callback;
return;
}
this.currentCallback = callback;
if(cacheOnly) {
if(this.currentScroll == 0) {
this.currentPage = 0;
}
} else {
this.currentPage += 1;
}
var _this = this;
var errorAbort = false;
var tweetsCache = {};
var requiredTweets = (this.currentPage + 1) * this.tweetsPerPage;
var resultTweetsList = [];
var usedTweetsMap = {};
var findMaxBefore = function(maxDate) {
var maxBeforeDate = null;
var maxBeforeTweet = null;
var checkMaxTweet = function(timelineId) {
var tweetsInTimeline = tweetsCache[timelineId];
for(var i = 0, len = tweetsInTimeline.length; i < len; ++i) {
var tweet = tweetsInTimeline[i];
if(usedTweetsMap[tweet.id]) {
continue;
}
var date = Date.parse(tweet.created_at);
if(maxDate && date > maxDate) {
continue;
}
if(!maxBeforeDate || date > maxBeforeDate) {
maxBeforeDate = date;
maxBeforeTweet = tweet;
maxBeforeTweet.timelineId = timelineId;
}
}
};
var checkFavorite = false;
for(var timelineId in tweetsCache) {
if(timelineId == 'favorites') {
//HACK: postpone
checkFavorite = true;
continue;
}
checkMaxTweet(timelineId);
}
if(checkFavorite) {
checkMaxTweet('favorites');
}
usedTweetsMap[maxBeforeTweet.id] = true;
return maxBeforeTweet;
}
var joinFunction = function() {
if(errorAbort) {
return;
}
/* 2nd step: Let's cherry pick tweets until we get enough of them
to compose our unified timeline.
*/
while(resultTweetsList.length < requiredTweets) {
var lastTweet = resultTweetsList[resultTweetsList.length - 1];
var maxDate = null;
if(lastTweet) {
maxDate = Date.parse(lastTweet.created_at);
}
var nextTweet = findMaxBefore(maxDate);
if(!nextTweet) {
break;
}
resultTweetsList.push(nextTweet);
if(resultTweetsList.length == requiredTweets) {
break;
}
var timelineTweetsCache = tweetsCache[nextTweet.timelineId];
var isLastInTimelineCache = timelineTweetsCache[timelineTweetsCache.length - 1].id == nextTweet.id
if(isLastInTimelineCache) {
/* 3rd step: Some timeline went empty because all other tweets are already in
the unified timeline, now we need to get more tweets from this timeline.
*/
var newJoinableCallback = Utils.makeJoinableCallback(1, eachFunction, joinFunction);
_this.timelines[nextTweet.timelineId].giveMeTweets(newJoinableCallback, false, false, true,
(requiredTweets - resultTweetsList.length) + 1);
return;
}
}
/* 4th step now we're ready to return our unified timeline. */
try {
_this.visibleTweetsList = resultTweetsList;
_this.currentCallback(resultTweetsList, _this.timelineId);
} catch(e) {
/* ignoring, popup dead? */
}
_this.currentCallback = null;
};
var eachFunction = function (tweets, timelineId) {
if(errorAbort) {
return;
}
if(!tweets) {
errorAbort = true;
try {
_this.currentCallback(null, _this.timelineId);
} catch(e) {
/* ignoring, popup dead? */
}
_this.currentCallback = null;
return;
}
var currentTweets = tweetsCache[timelineId];
tweetsCache[timelineId] = tweets;
}
/* 1st step: Let's get cached results from each timeline and call
joinFunction when we get all the results.
*/
var joinCount = this.countTimelines();
var joinableCallback = Utils.makeJoinableCallback(joinCount, eachFunction, joinFunction);
this.eachTimeline(function(timeline) {
timeline.giveMeTweets(joinableCallback, false, true, true);
});
},
updateNewTweets: function() {
this.eachTimeline(function(timeline) {
timeline.updateNewTweets();
});
},
newTweetsCount: function() {
var ret = [0, 0];
this.eachTimeline(function(timeline) {
var timelineCount = timeline.newTweetsCount();
ret[0] += timelineCount[0];
ret[1] += timelineCount[1];
});
return ret;
},
getNewUnreadTweets: function() {
return [];
},
getNewUnreadIds: function() {
return [];
},
removeFromCache: function(id) {
this.eachTimeline(function(timeline) {
timeline.removeFromCache(id);
});
},
findTweet: function(id) {
return null;
},
pushTweet: function(tweet) {
return;
},
getNewTweetsCache: function() {
return [];
},
getTweetsCache: function() {
return this.visibleTweetsList;
}
}
function TweetsTimeline(timelineId, manager, recheckTime) {
this.timelineId = timelineId;
this.manager = manager;
this.recheckTime = recheckTime;
this.tweetsCache = [];
this.newTweetsCache = [];
this.unreadNotified = [];
this.timerId = null;
this.currentError = null;
this.currentCallback = null;
this.currentScroll = 0;
this.firstRun = true;
this.timelinePath = null;
this.usingPages = false;
this.timelineId = timelineId;
this.listId = null;
this.timelineStopped = false;
switch(timelineId) {
case 'home':
this.timelinePath = 'statuses/home_timeline';
break;
case 'mentions':
this.timelinePath = 'statuses/mentions';
break;
case 'dms':
this.timelinePath = 'direct_messages';
break;
case 'favorites':
this.timelinePath = 'favorites';
this.usingPages = true;
break;
}
}
TweetsTimeline.prototype = {
setError: function(status) {
this.currentError = status;
},
setTimelinePath: function(path) {
this.timelinePath = path;
},
killTimeline: function() {
this.timelineStopped = true;
this.stopTimer();
},
stopTimer: function() {
if(this.timerId) {
clearTimeout(this.timerId);
this.timerId = null;
}
},
onFetchNew: function(success, tweets, status, context) {
if(this.timelineStopped) {
return;
}
var _this = this;
if(!success) {
this.setError(status);
if(context.onFinish)
context.onFinish(0);
this.timerId = setTimeout(function() { _this.fetchNewTweets.call(_this); }, this.recheckTime);
return;
}
this.setError(null);
var unreadLength = 0;
if(this.usingPages) {
for(var i = 0; i < tweets.length; ++i) {
var j = 0;
for(; j < this.tweetsCache.length; ++j) {
if(tweets[i].id == this.tweetsCache[j].id) {
break;
}
}
if(j != this.tweetsCache.length) {
break;
}
this.newTweetsCache.push(tweets[i]);
}
} else {
for(var i = tweets.length - 1; i >= 0; --i) {
this.newTweetsCache.unshift(tweets[i]);
var tid = tweets[i].id;
if(context.onFinish) {
this.manager.readTweet(tid);
} else if(!this.manager.readTweets[tid]) {
++unreadLength;
this.manager.unreadTweets[tid] = true;
}
}
}
if(tweets.length > 0) {
this.manager.notifyNewTweets();
}
if(context.onFinish)
context.onFinish(tweets.length);
this.timerId = setTimeout(function() { _this.fetchNewTweets.call(_this); }, this.recheckTime);
},
fetchNewTweets: function(onFinishCallback) {
this.stopTimer();
var lastId = null;
if(this.newTweetsCache.length > 0) {
lastId = this.newTweetsCache[0].id;
} else if(this.tweetsCache.length > 0) {
lastId = this.tweetsCache[0].id;
}
var _this = this;
var context = { onFinish: onFinishCallback };
var params = {};
if(this.usingPages) {
//No params
} else {
if(lastId) {
params.since_id = lastId;
}
}
this.manager.twitterBackend.timeline(this.timelinePath, function(success, tweets, status, context) {
_this.onFetchNew.call(_this, success, tweets, status, context);
}, context, params);
},
onFetch: function(success, tweets, status, context) {
if(this.timelineStopped) {
return;
}
if(!success) {
this.setError(status);
try {
this.currentCallback(null);
} catch(e) {
/* ignoring exception, popup might be closed */
}
this.setError(null);
this.currentCallback = null;
return;
}
this.setError(null);
if(this.usingPages) {
for(var i = 0; i < tweets.length; ++i) {
this.pushTweet(tweets[i]);
}
} else {
var i = 0;
if(context.usingMaxId) {
// If we're fetching using maxId, ignore the first one (we already have it)
i = 1;
}
for(; i < tweets.length; ++i) {
this.tweetsCache.push(tweets[i]);
}
}
// Let's clean this.currentCallback before calling it as
// there might be unexpected errors and this should not block
// the extension.
var callback = this.currentCallback;
this.currentCallback = null;
try {
callback(this.tweetsCache, this.timelineId);
} catch(e) {
/* ignoring exception, popup might be closed */
}
if(this.firstRun) {
this.firstRun = false;
var _this = this;
this.timerId = setTimeout(function() { _this.fetchNewTweets.call(_this); }, this.recheckTime);
}
},
giveMeTweets: function(callback, syncNew, cacheOnly, keepCache, suggestedCount) {
if(!this.timelinePath) {
callback([], this.timelineId);
return;
}
if(this.currentCallback) {
//Handling multiple calls to giveMeTweets, just update the registered
//callback and let the first request finish.
this.currentCallback = callback;
return;
}
if(syncNew) {
//We should fetch new tweets, update the cache and then return the
//cached results.
var oldNewTweetsCallback = this.manager.newTweetsCallback;
var _this = this;
this.currentCallback = callback;
this.manager.newTweetsCallback = null;
var onFinishCallback = function() {
var tweetsCallback = _this.currentCallback;
_this.currentCallback = null;
_this.updateNewTweets();
_this.manager.updateAlert();
_this.giveMeTweets(tweetsCallback, false, true);
_this.manager.newTweetsCallback = oldNewTweetsCallback;
}
this.fetchNewTweets(onFinishCallback);
return;
}
if(cacheOnly && !this.firstRun) {
//Only return cached results if this is not the first run.
if(!keepCache && this.currentScroll == 0) {
this.cleanUpCache();
}
try {
callback(this.tweetsCache, this.timelineId);
} catch(e) {
/* ignoring exception, popup might be closed */
}
return;
}
//If we didn't return yet it's because we want to fetch old tweets
//from twitter's API.
this.currentCallback = callback;
var _this = this;
var params, context;
if(this.usingPages) {
params = {page: (this.tweetsCache.length / 20) + 1};
} else {
var maxId = null;
if(this.tweetsCache.length > 0) {
maxId = this.tweetsCache[this.tweetsCache.length - 1].id;
}
context = {
usingMaxId: !!maxId
}
if(!suggestedCount) {
suggestedCount = OptionsBackend.get('tweets_per_page');
}
params = {count: suggestedCount, per_page: suggestedCount};
if(maxId) {
params.max_id = maxId;
}
}
this.manager.twitterBackend.timeline(this.timelinePath, function(success, tweets, status, context) {
_this.onFetch.call(_this, success, tweets, status, context);
}, context, params);
},
cleanUpCache: function() {
var len = this.tweetsCache.length;
if(len <= OptionsBackend.get('max_cached_tweets'))
return;
this.tweetsCache = this.tweetsCache.slice(0, OptionsBackend.get('max_cached_tweets') + 1);
},
updateNewTweets: function() {
this.tweetsCache = this.newTweetsCache.concat(this.tweetsCache);
this.newTweetsCache = [];
this.unreadNotified = [];
},
newTweetsCount: function() {
var unreadCount = 0;
for(var i = this.newTweetsCache.length - 1; i >= 0; --i) {
if(!this.manager.isTweetRead(this.newTweetsCache[i].id)) {
++unreadCount;
}
}
return [this.newTweetsCache.length, unreadCount];
},
getNewUnreadTweets: function() {
var unreadNewList = [];
for(var i = 0; i < this.newTweetsCache.length; ++i) {
if(!this.manager.isTweetRead(this.newTweetsCache[i].id) && !this.unreadNotified[this.newTweetsCache[i].id]) {
unreadNewList.push(this.newTweetsCache[i]);
this.unreadNotified[this.newTweetsCache[i].id] = true;
}
}
return unreadNewList;
},
getNewUnreadIds: function() {
var unreadNewIds = [];
for(var i = 0; i < this.newTweetsCache.length; ++i) {
if(!this.manager.isTweetRead(this.newTweetsCache[i].id)) {
unreadNewIds.push(this.newTweetsCache[i].id);
}
}
return unreadNewIds;
},
removeFromCache: function(id) {
var i = 0;
for(; i < this.tweetsCache.length; ++i) {
if(this.tweetsCache[i].id == id)
break;
}
if(i != this.tweetsCache.length) {
this.tweetsCache.splice(i, 1);
}
},
findTweet: function(id) {
var i = 0;
for(; i < this.tweetsCache.length; ++i) {
if(this.tweetsCache[i].id == id)
return this.tweetsCache[i];
}
return null;
},
pushTweet: function(tweet) {
Utils.pushTweet(this.tweetsCache, tweet);
},
getNewTweetsCache: function() {
return this.newTweetsCache;
},
getTweetsCache: function() {
return this.tweetsCache;
}
}
function ComposerData() {
this.saveMessage = '';
this.urlShortener = '';
this.isComposing = false;
this.replyId = null;
this.replyUser = null;
}
function TweetManager() {
this.unreadTweets = {};
this.readTweets = {};
this.retweets = {};
this.injectTweets = null;
this.newTweetsCallback = null;
this.composerData = new ComposerData();
this.timelines = {};
this.iconImg = null;
this.listsCache = null;
this.listsTabCount = OptionsBackend.get('lists_count');
this.unifiedVisible = OptionsBackend.get('unified_visible');
if(this.unifiedVisible) {
this.timelines['unified'] = new UnifiedTweetsTimeline(this.timelines);
this.timelines['unified'].visible = true;
this.currentTimeline = 'unified';
}
var timelineNames = ["home", "mentions", "dms", "favorites", "lists"];
for(var i = 0; i < timelineNames.length; ++i) {
var refreshInterval = OptionsBackend.get(timelineNames[i] + '_refresh_interval');
var visible = OptionsBackend.get(timelineNames[i] + '_visible');
var includeInUnified = false;
if(this.unifiedVisible) {
includeInUnified = OptionsBackend.get(timelineNames[i] + '_include_unified');
}
if(!visible && !includeInUnified) {
continue;
}
if(timelineNames[i] == "lists") {
for(var j = 0; j < this.listsTabCount; ++j) {
var listTimeline = new TweetsTimeline("lists_" + j, this, refreshInterval);
listTimeline.includeInUnified = includeInUnified;
listTimeline.visible = visible;
this.timelines["lists_" + j] = listTimeline;
}
} else {
var timeline = new TweetsTimeline(timelineNames[i], this, refreshInterval);
timeline.includeInUnified = includeInUnified;
timeline.visible = visible;
this.timelines[timelineNames[i]] = timeline;
}
if(!this.currentTimeline) {
this.currentTimeline = (timelineNames[i] == "lists") ? "lists_0" : timelineNames[i];
}
}
this.lastAvailableHits = null;
this.lastAvailableHitsTime = null;
this.warningsCallback = null;
this.warningMessage = null;
this.autoClearWarning = false;
this.tooManyHitsCount = 0;
this.initIconAndAlerts();
var _this = this;
this.twitterBackend = new TwitterLib(OptionsBackend.get('request_timeout'),
function onAuthenticated() {
for(var i = 0; i < timelineNames.length; ++i) {
var visible = OptionsBackend.get(timelineNames[i] + '_visible');
var includeInUnified = false;
if(_this.unifiedVisible) {
includeInUnified = OptionsBackend.get(timelineNames[i] + '_include_unified');
}
if(!visible && !includeInUnified) {
continue;
}
if(timelineNames[i] == "lists") {
_this.lists();
var selectedLists = _this.getSelectedLists();
for(var timelineId in selectedLists) {
var timeline = _this.changeList(timelineId, selectedLists[timelineId]);
if(timeline) {
timeline.giveMeTweets(function() {});
}
}
} else {
_this.giveMeTweets.call(_this, timelineNames[i], function() {});
}
}
},
function(remainingHits, nextHitsReset, hourlyLimit) {
_this.onHitsUpdated.call(_this, remainingHits, nextHitsReset, hourlyLimit);
},
OptionsBackend.get('base_url'),
OptionsBackend.get('base_authorize_url'),
OptionsBackend.get('base_signing_url')
);
}
TweetManager.prototype = {
setWarning: function(msg) {
this.warningMessage = msg;
try {
if(this.warningCallback) {
this.warningCallback(msg);
}
} catch(e) {
/* ignoring, the popup window might be closed. */
}
},
clearWarning: function() {
this.warningMessage = null;
},
registerWarningsCallback: function(callback) {
this.warningsCallback = callback;
if(this.warningMessage && this.warningsCallback) {
this.warningsCallback(this.warningMessage);
}
},
getSelectedLists: function() {
var selectedListsStr = localStorage['selected_lists'];
if(selectedListsStr) {
return JSON.parse(selectedListsStr);
}
return {};
},
registerSelectedList: function(timelineId, listId) {
var selectedLists = this.getSelectedLists();
selectedLists[timelineId] = listId;
localStorage['selected_lists'] = JSON.stringify(selectedLists);
},
eachTimeline: function(callback) {
for(var tId in this.timelines) {
callback.call(tId, this.timelines[tId]);
}
},
orderedEachTimeline: function(callback) {
var retList = [];
for(var tId in this.timelines) {
var orderedPos = this.getTimelinePosition(tId);
if(orderedPos == -1) {
orderedPos = retList.length;
}
if(retList[orderedPos]) {
retList.splice(orderedPos, 0, tId);
} else {
retList[orderedPos] = tId;
}
}
for(var i = 0; i < retList.length; ++i) {
var tId = retList[i];
if(tId) {
callback.call(tId, this.timelines[tId]);
}
}
},
getTimelinePosition: function(timelineId) {
if(!this.timelineOrderCache) {
var storedOrder = localStorage['timeline_order'];
if(storedOrder) {
this.timelineOrderCache = JSON.parse(storedOrder);
} else {
this.timelineOrderCache = [];
}
}
for(var i = 0; i < this.timelineOrderCache.length; ++i) {
if(timelineId == this.timelineOrderCache[i]) {
return i;
}
}
return -1;
},
setTimelineOrder: function(sortedTimelinesArray) {
this.timelineOrderCache = sortedTimelinesArray;
localStorage['timeline_order'] = JSON.stringify(sortedTimelinesArray);
},
initIconAndAlerts: function() {
var $icon = $('<img>').attr('src', 'img/icon19.png');
this.iconImg = $icon[0];
var _this = this;
$icon.load(function() {
_this.updateAlert();
});
},
unique: function(srcList) {
var newList = [];
for(var i = 0; i < srcList.length; ++i) {
if($.inArray(srcList[i], newList) == -1) {
newList[newList.length] = srcList[i];
}
}
return newList;
},
updateAlert: function() {
var colors = [];
var unreadNewTweets = [];
var totalUnreadNewIds = [];
this.eachTimeline(function(timeline) {
var timelineId = this;
var unreadNewIds = timeline.getNewUnreadIds();
totalUnreadNewIds = totalUnreadNewIds.concat(unreadNewIds);
if(unreadNewIds.length > 0) {
if(timelineId.match(/^lists/)) {
timelineId = 'lists';
}
if(OptionsBackend.get(timelineId + '_icon')) {
colors.push(OptionsBackend.get(timelineId + '_color'));
}
if(OptionsBackend.get(timelineId + '_on_page')) {
unreadNewTweets = unreadNewTweets.concat(timeline.getNewUnreadTweets());
}
}
});
var totalUnreadNewCount = this.unique(totalUnreadNewIds).length;
if(colors.length == 0) {
chrome.browserAction.setTitle({title: "Chromed Bird"});
chrome.browserAction.setIcon({imageData: IconCreator.paintIcon(this.iconImg, OptionsBackend.get('idle_color'))});
chrome.browserAction.setBadgeText({text: ''});
} else {
var title = totalUnreadNewCount + " new tweet";
if(totalUnreadNewCount > 1)
title += "s";
chrome.browserAction.setTitle({title: title});
chrome.browserAction.setIcon({imageData: IconCreator.paintIcon(this.iconImg, colors)});
chrome.browserAction.setBadgeBackgroundColor({color: [255, 0, 0, 0]});
chrome.browserAction.setBadgeText({text: '' + totalUnreadNewCount});
}
if(unreadNewTweets.length > 0) {
this.showTweetsNotifications(unreadNewTweets);
}
},
showTweetsNotifications: function(tweetsToNotify, forceOnPage) {
this.injectTweets = tweetsToNotify;
if(!forceOnPage && OptionsBackend.get('use_desktop_notifications')) {
try {
var notificationCenter = window.notifications || window.webkitNotifications;
var notification = notificationCenter.createHTMLNotification(
chrome.extension.getURL('tweets_notifier.html')
);
notification.show();
} catch(e) {
// Fallback to 'on page' notifications
this.showTweetsNotifications(tweetsToNotify, true);
}
} else {
var injectHelper = function(action, file, allFrames, callback) {
var method;
if(action == 'script') {
method = chrome.tabs.executeScript;
} else if(action == 'css') {
method = chrome.tabs.insertCSS;
} else {
return;
}
var params = {file: file};
if(allFrames) {
params.allFrames = true;
}
try {
method.call(chrome.tabs, null, params, callback);
} catch(e) {
// Maybe this exception is due to allFrames = true, let's try without it
if(allFrames) {
try {
method.call(chrome.tabs, null, {file: file}, callback);
} catch(e) {
// This time something really bad happened, logging and ignoring
console.log(e);
}
} else {
// We don't know the motive, logging and ignoring
console.log(e);
}
}
};
injectHelper('script', 'lib/3rdparty/jquery.js', true, function() {
injectHelper('css', 'css/injectedTweets.css', true, function() {
injectHelper('script', 'lib/tweets_assembler.js', true);
});
});
}
},
registerNewTweetsCallback: function(callback) {
this.newTweetsCallback = callback;
},
readTweet: function(id) {
this.readTweets[id] = true;
delete this.unreadTweets[id];
this.notifyNewTweets();
},
isTweetRead: function(id) {
return !this.unreadTweets[id];
},
isRetweet: function(id) {
return this.retweets[id];
},
notifyNewTweets: function() {
if(this.newTweetsCallback) {
var _this = this;
this.eachTimeline(function(timeline) {
var newTweets = timeline.newTweetsCount();
try {
// The callback might be invalid (popup not active), so let's ignore errors for now.
_this.newTweetsCallback(newTweets[0], newTweets[1], timeline.timelineId);
} catch(e) { /* ignoring */ }
});
}
this.updateAlert();
},
postTweet: function(callback, msg, replyId) {
return this.twitterBackend.tweet(callback, msg, replyId);
},
postRetweet: function(callback, id) {
var _this = this;
return this.twitterBackend.retweet(function(success, data, status) {
if(success) {
_this.retweets[id] = true;
}
callback(success, data, status);
}, id);
},
destroy: function(callback, tweetTimelineId, id) {
var _this = this;
var firstCallback = function(success, data, status) {
if(success) {
_this.eachTimeline(function(timeline) {
timeline.removeFromCache(id);
});
}
callback(success, data, status);
};
if(tweetTimelineId == "dms") {
return this.twitterBackend.destroyDM(firstCallback, id);
} else {
return this.twitterBackend.destroy(firstCallback, id);
}
},
favorite: function(callback, id) {
var _this = this;
var firstCallback = function(success, data, status) {
if(success) {
var favTimeline = _this.timelines["favorites"];
if(favTimeline) {
favTimeline.pushTweet(data);
}
_this.eachTimeline(function(timeline) {
var tweet = timeline.findTweet(id);
if(tweet)
tweet.favorited = true;
});
}
callback(success, data, status);
};
return this.twitterBackend.favorite(firstCallback, id);
},
unFavorite: function(callback, id) {
var _this = this;
var firstCallback = function(success, data, status) {
if(success) {
var favTimeline = _this.timelines["favorites"];
if(favTimeline) {
favTimeline.removeFromCache(id);
}
_this.eachTimeline(function(timeline) {
var tweet = timeline.findTweet(id);
if(tweet)
tweet.favorited = false;
});
}
callback(success, data, status);
};
return this.twitterBackend.unFavorite(firstCallback, id);
},
lists: function(force, callback) {
if(force) {
this.listsCache = null;
}
if(!this.listsCache) {
var _this = this;
this.twitterBackend.lists(function(success, data, status) {
if(success) {
_this.listsCache = data.lists;
}
if(callback) {
callback(success, _this.listsCache, status);
}
});
return;
}
if(callback) {
callback(true, this.listsCache, null);
}
},
changeList: function(timelineName, listId) {
var timeline = this.timelines[timelineName];
if(!timeline) {
return null;
}
if(timeline.listId != listId) {
this.registerSelectedList(timelineName, listId);
timeline.stopTimer();
delete this.timelines[timelineName];
timeline = this.timelines[timelineName] = new TweetsTimeline(timelineName, this, OptionsBackend.get('lists_refresh_interval'));
}
timeline.includeInUnified = this.unifiedVisible && OptionsBackend.get('lists_include_unified');
timeline.visible = OptionsBackend.get('lists_visible');
timeline.listId = listId;
timeline.setTimelinePath(this.twitterBackend.username() + '/lists/' + listId + '/statuses');
return timeline;
},
getListId: function(timelineName) {
if(!timelineName) {
timelineName = this.currentTimeline;
}
var timeline = this.timelines[timelineName];
if(!timeline) {
return null;
}
var listId = timeline.listId;
if(listId && this.listsCache) {
for(var i = 0; i < this.listsCache.length; ++i) {
if(this.listsCache[i].slug == listId) {
return listId;
}
}
}
return null;
},
/* Delegates */
giveMeTweets: function(timelineId, callback, syncNew, cacheOnly) {
var timeline = this.timelines[timelineId];
if(!timeline) {
callback([], timelineId);
return;
}
if(syncNew && this.unifiedVisible) {
var timelineBaseId = timelineId.replace(/_.*$/, '');
if(OptionsBackend.get(timelineBaseId + '_include_unified')) {
var originalCallback = callback;
var _this = this;
callback = function(tweets, timelineId) {
originalCallback(tweets, timelineId);
_this.timelines['unified'].giveMeTweets(originalCallback, false, true);
};
}
}
return timeline.giveMeTweets(callback, syncNew, cacheOnly);
},
newTweetsCount: function(timelineId) {
return this.timelines[timelineId].newTweetsCount();
},
updateNewTweets: function() {
if(this.currentTimeline == 'favorites') {
var newTweets = this.timelines[this.currentTimeline].getNewTweetsCache();
for(var i = 0, len = newTweets.length; i < len; ++i) {
var id = newTweets[i].id;
this.eachTimeline(function(timeline) {
var tweet = timeline.findTweet(id);
if(tweet)
tweet.favorited = true;
});
}
}
this.timelines[this.currentTimeline].updateNewTweets();
this.updateAlert();
},
getCurrentTimeline: function() {
return this.timelines[this.currentTimeline];
},
currentError: function() {
return this.timelines[this.currentTimeline].currentError;
},
stopAll: function() {
this.eachTimeline(function(timeline) {
timeline.killTimeline();
delete timeline;
});
},
signout: function() {
localStorage.removeItem('oauth_token_data');
localStorage.removeItem('selected_lists');
this.stopAll();
TweetManager.instance = new TweetManager();
},
markTimelineAsRead: function() {
var tweetsToRead = this.getCurrentTimeline().getTweetsCache();
for(var i = 0; i < tweetsToRead.length; ++i) {
this.readTweet(tweetsToRead[i].id);
}
},
onHitsUpdated: function(remainingHits, nextHitsReset, hourlyLimit) {
var nextResetDate = new Date();
nextResetDate.setTime(parseInt(nextHitsReset) * 1000);
if(remainingHits == 0) {
this.autoClearWarning = true;
this.setWarning("You've exceeded API hits limit. " +
"Please review your refresh interval settings in the <a href='javascript:chrome.tabs.create({url:\"" + chrome.extension.getURL('options.html') + "\"})'>options page</a>. " +
"Chromed Bird won't update until " + nextResetDate.toLocaleDateString() + " " + nextResetDate.toLocaleTimeString() + ".");
return;
}
if(this.autoClearWarning) {
this.autoClearWarning = false;
this.clearWarning();
}
if(this.lastAvailableHitsTime == null) {
this.lastAvailableHitsTime = new Date();
this.lastAvailableHits = remainingHits;
}
var timeDiff = new Date() - this.lastAvailableHitsTime;
if(timeDiff >= 5 * 60 * 1000) {
var realHitsPerHour = ((this.lastAvailableHits - remainingHits) / timeDiff) * 60 * 60 * 1000;
if(realHitsPerHour > hourlyLimit) {
// If we keep this same speed we're gonna exhaust our hits limit.
this.tooManyHitsCount += 1;
if(this.tooManyHitsCount >= 2) {
// We've exceeded the limit twice in a row, let's warn users.
this.setWarning("Hey! Your remaining API hits are ending too fast.<br>" +
"Please review your refresh interval settings in the <a href='javascript:chrome.tabs.create({url:\"" + chrome.extension.getURL('options.html') + "\"})'>options page</a>. " +
"Take special care if you're running multiple Twitter clients, as they all share the same limit.<br>" +
"Available hits left: " + remainingHits + " - Next hits reset: " + nextResetDate.toLocaleDateString() + " " + nextResetDate.toLocaleTimeString());
this.tooManyHitsCount = 0;
}
} else {
// We only wanna warn users if we exceed hits limit more than once
// in a row, so let's just reset the counter.
this.tooManyHitsCount = 0;
}
this.lastAvailableHitsTime = new Date();
this.lastAvailableHits = remainingHits;
}
}
}
/* Clean up old versions mess */
localStorage.removeItem('password');
localStorage.removeItem('logged');
localStorage.removeItem('username');
localStorage.removeItem('remember');
localStorage.removeItem('currentTheme');
function compareVersions(v1, v2) {
if(!v1) return -1;
if(!v2) return 1;
var maxLen = Math.max(v1.length, v2.length);
for(var i = 0; i < maxLen; ++i) {
if(!v1[i]) return -1;
if(!v2[i]) return 1;
if(v1[i] > v2[i]) return 1;
if(v1[i] < v2[i]) return -1;
}
return 0;
}
function checkVersionChanges(manifest) {
try {
var currentVersion = manifest.version.split('.');
var storageVersion = localStorage['version'];
if(storageVersion) {
storageVersion = JSON.parse(storageVersion);
}
if(compareVersions(currentVersion, storageVersion) != 0) {
// if last version is < 1.3.0
if(compareVersions(storageVersion, [1, 3, 0]) < 0) {
//Old default refresh interval, let's update it for the new version
if(OptionsBackend.get('home_refresh_interval') == 60000) {
var options = OptionsBackend.load();
options['home_refresh_interval'] = OptionsBackend.defaultOptions['home_refresh_interval'];
OptionsBackend.save(options);
}
}
if(compareVersions(storageVersion, [1, 5, 1]) <= 0) {
var storedOrder = localStorage['timeline_order'];
storedOrder = JSON.parse(storedOrder);
storedOrder.unshift('unified');
localStorage['timeline_order'] = JSON.stringify(storedOrder);
}
//TODO: Maybe restart tweetManager
localStorage['version'] = JSON.stringify(currentVersion);
}
} catch(e) {
/* experimental code, something can go wrong */
console.log(e);
}
}
initializeExtension();
$.ajax({
type: 'GET',
url: 'manifest.json',
data: {},
dataType: "json",
timeout: 5000,
success: function(data, status) {
checkVersionChanges(data);
},
error: function (request, status, error) {
try {
// Ugly hack, but currently Chrome is returning an error
checkVersionChanges(JSON.parse(request.responseText));
} catch(e) {
/* Something can go wrong, ignoring... */
}
}
});
function initializeExtension() {
TweetManager.instance = new TweetManager();
var waitingFirstRequest = true;
var selectedResponse = null;
var biggestArea = -1;
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if(request.cb_requesting_tweets_immediate) {
sendResponse({
tweets: TweetManager.instance.injectTweets,
nameAttribute: OptionsBackend.get('name_attribute'),
extensionId: location.host,
fadeTimeout: OptionsBackend.get('notification_fade_timeout')
});
TweetManager.instance.injectTweets = null;
} else if(request.cb_requesting_tweets) {
if(waitingFirstRequest) {
waitingFirstRequest = false;
setTimeout(function() {
if(selectedResponse) {
selectedResponse({
tweets: TweetManager.instance.injectTweets,
nameAttribute: OptionsBackend.get('name_attribute'),
extensionId: location.host,
fadeTimeout: OptionsBackend.get('notification_fade_timeout')
});
}
waitingFirstRequest = true;
TweetManager.instance.injectTweets = null;
biggestArea = -1;
}, 200);
}
var area = request.frame_area;
if(area >= biggestArea) {
biggestArea = area;
selectedResponse = sendResponse;
}
}
});
}
</script>
</body>
</html>
Jump to Line
Something went wrong with that request. Please try again.