├── README ├── background.js ├── cloudsave.html ├── cloudsave.js ├── files.js ├── hosts ├── box.js ├── cloudapp.js ├── clouddrive.js ├── cx.js ├── dropbox │ ├── dropbox.js │ ├── modern_dropbox.js │ └── oauth.js ├── droplr.js ├── facebook.js ├── flickr.js ├── google │ ├── chrome_ex_oauth.js │ ├── chrome_ex_oauthsimple.js │ ├── docs.js │ ├── google.js │ └── picasa.js ├── hotfile.js ├── imageshack.js ├── imgly.js ├── imgur.js ├── immio.js ├── js │ ├── jquery-1.4.4.min.js │ ├── jquery-ui.min.js │ ├── md5.js │ └── sha1.js ├── keys.js ├── minus.js ├── posterous.js ├── skydrive.js ├── sugarsync.js ├── twitgoo.js ├── twitpic.js ├── twitrpix.js ├── twitter.js └── webdav │ ├── dav.js │ └── webdav.js ├── icon ├── 1024.png ├── 128-opaque.png ├── 128.png ├── 16.png ├── 22.png ├── 256.png ├── 32.png ├── 48.png ├── 512.png ├── 64 (another copy).png ├── 64.png ├── 64sad.png ├── 64yay.png └── throbber.gif ├── login_popup.html ├── login_popup.js ├── manifest.json ├── popup.html ├── setting.js └── settings.html /README: -------------------------------------------------------------------------------- 1 | ############# Cloud Save 2 | 3 | It's actually really amazing how many people now use Cloud Save. In fact, when I 4 | first built this, I had no idea how anyone would possibly find this extension of 5 | any practical use whatsoever. I still only have a vague idea as to how it could 6 | be used, so this probably implies that I'm not the best maintainer of this project 7 | but at least it's popular and rather awesome. Personally, I find drag2up much more 8 | original, innovative and awesome. 9 | 10 | Cloud Save is still pretty cool though. And it's not a terrible name, actually. Its 11 | probably the one project of mine which actually has a fairly decent name. 12 | 13 | Right now, I'm going to try doing development of cloud save on the cloud via the 14 | cloud9 IDE on a Cr-48. It's going pretty well so far and it's really awesome. 15 | 16 | ############## Design for Cloud Save 1.2 17 | 18 | Version 1.2 will feature tons and tons of magic. Really really magical magic. 19 | 20 | The basis is a moving window-ish persistant stack which contains the names of hosts 21 | used. eg. dropbox, dropbox, picasa, picasa, box.net, google docs, dropbox, google docs, dropbox 22 | 23 | Right now, I figure the ideal value is 10. Why? Because Math.sqrt(10) day is next 24 | week and also incidentally my birthday is on sqrt(10) day. It's also between five 25 | and fifteen, if my third grade arithmetic skills are still intact. 26 | 27 | So this moving window is kept persistantly via localStorage. It gets processed and 28 | sorted into unique elements: dropbox, picasa, google docs, box.net. Then those are 29 | sorted by a magical ranking coefficient. 30 | 31 | The general formula for this ranking index is 32 | number_of_times_used_in_past_10 + most_recent_index / index_dampening_factor 33 | 34 | dropbox was used 4 times in that list - most recent is the last one of the list of so index 8 35 | picasa twice - most recent index is 3 36 | box.net once - most recent index is 4 37 | google docs twice - most recent index is 7 38 | 39 | the index dampening factor should be a big number. the second term in the magical 40 | equation that is used here exists for the sole purpose of breaking ties between 41 | things used an equal number of times to favor those who have been used more recently 42 | (or should it be a smaller number?). A good value is something like the total 43 | size of the moving window multiplied by 10. But to be safe, something like 1000 could 44 | be used. Though any number should theoretically work, for debugging purposes it would 45 | be nice if it was a power of ten. 46 | 47 | The top six most recently used hosts will be shown. Or less than that if the last 48 | ten include less than six items (again, ordered by the rank thing). 49 | 50 | Once a file is uploaded, the list must be recalculated and generated. 51 | 52 | The first level menu will be the same: 53 | 54 | Cloud Save > 55 | 56 | Second level: 57 | 58 | Save As... > 59 | ------------ 60 | HOst1 61 | Host2 62 | Host3 63 | Host5 64 | Host6 65 | Host4 66 | ------------ 67 | More > 68 | 69 | There are two tertiary menus. Save As... however looks exactly like the secondary 70 | level menu but without another save as submenu (obviously). 71 | 72 | So the More.. menu, which is the only interesting tertiary menu will be as follows: 73 | 74 | Add/Remove... 75 | ------------- 76 | HostA 77 | HostB 78 | HostC 79 | HostD 80 | HostE 81 | HostF 82 | HostG 83 | HostH 84 | ... 85 | 86 | As follows. The tertiary list will include all the other hosts in alphabetical order. 87 | 88 | Add/Remove is sort of a misnomer as it will not actually do anything in verison 1.2 89 | instead, it'll be a link to the settings page which will do virtually nothing except 90 | for configuring some very magical variable coefficients and stuff. probably. 91 | 92 | These plans were written at 4:23PM Eastern Standard Time on the Eleventh of March of 93 | the year 2011. 94 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder; 2 | 3 | Blob.prototype.slice = Blob.prototype.slice || function(start, length) { 4 | return this.webkitSlice(start, start + length); 5 | } 6 | 7 | function loginTab(loginurl, string, cb){ 8 | if(typeof chrome != 'undefined'){ 9 | chrome.tabs.create({ 10 | url: loginurl 11 | }, function(tab){ 12 | var poll = function(){ 13 | chrome.tabs.get(tab.id, function(info){ 14 | if(info.url.indexOf(string) != -1){ 15 | cb(info.url); 16 | chrome.tabs.remove(tab.id); 17 | }else{ 18 | setTimeout(poll, 500) 19 | } 20 | }) 21 | }; 22 | poll(); 23 | }) 24 | }else if(typeof tabs != 'undefined'){ 25 | tabs.open({ 26 | url: loginurl, 27 | onOpen: function(tab){ 28 | var poll = function(){ 29 | if(tab.url.indexOf(string) != -1){ 30 | cb(tab.url); 31 | tab.close(); 32 | }else{ 33 | setTimeout(poll, 500) 34 | } 35 | }; 36 | poll(); 37 | } 38 | }) 39 | } 40 | } 41 | 42 | function https(){ 43 | if(localStorage.no_https == 'on'){ 44 | return 'http://'; //idk why someone would want this 45 | } 46 | return 'https://'; 47 | } 48 | 49 | // 1. getURL: pull data from source URL to extension 50 | // 2. sendMultipart: use the pulled data to build file locally in extension (via the append function inside sendMultipart), then send to hosts 51 | function getURL(type, request, callback, sync){ 52 | if(request.data && sync) return request.data; 53 | 54 | if(request.data) return callback(request); //no need reconverting! 55 | 56 | if(/^data:/.test(request.url)){ 57 | console.log('opened via data url'); 58 | var parts = request.url.match(/^data:(.+),/)[1].split(';'); 59 | var mime = parts[0], b64 = parts.indexOf('base64') != -1; 60 | var enc = request.url.substr(request.url.indexOf(',')+1); 61 | var data = b64 ? atob(enc) : unescape(enc); 62 | //data urls dont have any weird encoding issue as far as i can tell 63 | var name = ''; 64 | if(request.name){ 65 | name = request.name; 66 | }else{ 67 | name = enc.substr(enc.length/2 - 6, 6) + '.' + mime.split('/')[1]; 68 | } 69 | if(sync) return data; 70 | callback({ 71 | data: data, 72 | type: mime, 73 | id: request.id, 74 | size: data.length, 75 | name: name, url: request.url 76 | }); 77 | 78 | //callback(new dFile(data, name, mime, id, size) 79 | }else{ 80 | 81 | var xhr = new XMLHttpRequest(); 82 | xhr.addEventListener('progress', function(evt){ 83 | downloadProgress(request.url, evt); 84 | }, false) 85 | 86 | xhr.open('GET', request.url, !sync); 87 | if(type == 'binary' || type == 'raw'){ 88 | xhr.overrideMimeType('text/plain; charset=x-user-defined'); //should i loop through and do that & 0xff? 89 | } 90 | if(type == 'arraybuffer'){ 91 | console.log('Setting Type ArrayBuffer'); 92 | xhr.responseType = 'arraybuffer'; 93 | } 94 | 95 | if(sync){ 96 | xhr.send(); 97 | return xhr.responseText; 98 | } 99 | xhr.onload = function(){ 100 | if(!request.type) request.type = xhr.getResponseHeader("Content-Type"); 101 | 102 | console.log('opened via xhr ', request.url); 103 | var data = ''; 104 | 105 | 106 | if(type == 'binary'){ 107 | //* 108 | if(typeof BlobBuilder == 'undefined'){ 109 | 110 | for(var raw = xhr.responseText, l = raw.length, i = 0, data = ''; i < l; i++) data += String.fromCharCode(raw.charCodeAt(i) & 0xff); 111 | 112 | callback({id: request.id, data: data, type: request.type, size: data.length, name: request.name, url: request.url}); 113 | }else{ 114 | 115 | var bb = new BlobBuilder();//this webworker is totally overkill 116 | bb.append("onmessage = function(e) { for(var raw = e.data, l = raw.length, i = 0, data = ''; i < l; i++) data += String.fromCharCode(raw.charCodeAt(i) & 0xff); postMessage(data) }"); 117 | var url; 118 | if(window.createObjectURL){ 119 | url = window.createObjectURL(bb.getBlob()) 120 | }else if(window.createBlobURL){ 121 | url = window.createBlobURL(bb.getBlob()) 122 | }else if(window.URL && window.URL.createObjectURL){ 123 | url = window.URL.createObjectURL(bb.getBlob()) 124 | }else if(window.webkitURL && window.webkitURL.createObjectURL){ 125 | url = window.webkitURL.createObjectURL(bb.getBlob()) 126 | } 127 | var worker = new Worker(url); 128 | worker.onmessage = function(e) { 129 | var data = e.data; 130 | callback({id: request.id, data: data, type: request.type, size: data.length, name: request.name, url: request.url}); 131 | }; 132 | 133 | worker.postMessage(xhr.responseText); 134 | } 135 | 136 | //*/ 137 | }else if(type == 'raw'){ 138 | var data = xhr.responseText; 139 | callback({id: request.id, data: data, type: request.type, size: data.length, name: request.name, url: request.url}); 140 | }else if(type == 'arraybuffer'){ 141 | var data = xhr.response; 142 | callback({id: request.id, data: data, type: request.type, size: data.length, name: request.name, url: request.url}); 143 | }else{ 144 | var raw = xhr.responseText; 145 | callback({id: request.id, data: raw, type: request.type, size: data.length, name: request.name, url: request.url}); 146 | } 147 | } 148 | xhr.send(); 149 | } 150 | } 151 | 152 | 153 | function getText(request, callback){ 154 | getURL('text', request, callback); 155 | } 156 | 157 | function getRaw(request, callback){ 158 | getURL('raw', request, callback); 159 | } 160 | 161 | function getBinary(request, callback){ 162 | getURL('binary', request, callback); 163 | } 164 | 165 | 166 | function getBuffer(request, callback){ 167 | var tmp = new XMLHttpRequest(); 168 | var abuf = 'responseType' in tmp && 'response' in tmp; 169 | console.log('Testing for array bufs', abuf); 170 | getURL(abuf?'arraybuffer':'raw', request, function(file){ 171 | console.log(abuf, file); 172 | if(abuf){ 173 | callback(file) 174 | }else{ 175 | var bin = file.data 176 | var arr = new Uint8Array(bin.length); 177 | for(var i = 0, l = bin.length; i < l; i++) 178 | arr[i] = bin.charCodeAt(i); 179 | file.data = arr.buffer; 180 | callback(file); 181 | } 182 | }) 183 | } 184 | 185 | var emptyFunc = function(){}; 186 | 187 | function hashToQueryString(hash) { 188 | var params = []; 189 | 190 | for (key in hash) { 191 | if (hash.hasOwnProperty(key)) { 192 | params.push(key + "=" + hash[key]); 193 | } 194 | } 195 | 196 | return params.join('&'); 197 | } -------------------------------------------------------------------------------- /cloudsave.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /cloudsave.js: -------------------------------------------------------------------------------- 1 | var Hosts = {}; 2 | 3 | var root = chrome.contextMenus.create({ 4 | "title" : "Cloud Save", 5 | "contexts" : ["all"] //["page", "image", "link", "selection"] 6 | }); 7 | 8 | var save_as = chrome.contextMenus.create({ 9 | "title": "Save As...", 10 | "contexts": ["all"], 11 | "parentId": root 12 | }); 13 | 14 | chrome.contextMenus.create({ 15 | "type": "separator", 16 | "contexts": ["all"], 17 | "parentId": root 18 | }); 19 | 20 | 21 | //todo: add more 22 | var original = { 23 | "image": { 24 | picasa: 'Picasa', 25 | twitpic: 'TwitPic', 26 | flickr: 'Flickr', 27 | posterous: 'Posterous', 28 | twitrpix: 'Twitrpix', 29 | twitgoo: 'Twitgoo', 30 | facebook: 'Facebook', 31 | imgly: 'Imgly' 32 | }, 33 | "all": { 34 | cx: 'Cx', 35 | box: 'Box.com', 36 | sugarsync: 'SugarSync', 37 | dropbox: 'Dropbox', 38 | gdocs: 'Google Docs/Drive', 39 | minus: 'Minus', 40 | cloudapp: 'CloudApp', 41 | clouddrive: 'Amazon Cloud', 42 | droplr: 'Droplr', 43 | skydrive: 'SkyDrive', 44 | webdav: 'WebDAV' 45 | }, 46 | "link": { 47 | dropdo: 'Dropdo' //this one is peculiar because it works differently from all the other hosts 48 | } 49 | }; 50 | 51 | 52 | var additional = { 53 | "image": { 54 | imageshack: 'Imageshack', 55 | imgur: 'Imgur', 56 | immio: 'Imm.io' 57 | }, 58 | "all": { 59 | hotfile: 'Hotfile' 60 | } 61 | }; 62 | 63 | var classes = clone(original); 64 | 65 | /* 66 | function clone(obj){ //very shallow cloning 67 | var n = {}; 68 | for(var i in obj) n[i] = obj[i]; //we are the knights who say ni! 69 | return n; 70 | } 71 | 72 | function clone_r(obj){ //not so shallow cloning 73 | if(typeof obj != 'object') return obj; 74 | var n = {}; 75 | for(var i in obj) n[i] = clone(obj[i]); //we are the knights who say ni! 76 | return n; 77 | } 78 | */ 79 | 80 | function clone(obj){ 81 | if(obj == null || typeof(obj) != 'object') 82 | return obj; 83 | 84 | var temp = obj.constructor(); // changed 85 | 86 | for(var key in obj) 87 | temp[key] = clone(obj[key]); 88 | 89 | return temp; 90 | } 91 | 92 | 93 | 94 | //an order which shoudl theoretically work, but isnt optimal 95 | //in any stretch of the imagination 96 | /* 97 | general idea: 98 | 1. quantity (2 > 1) 99 | 2. position (end > beginning) 100 | */ 101 | 102 | 103 | try { 104 | recent = JSON.parse(localStorage.cloudsave_recent); 105 | }catch(err){ 106 | recent = [ 107 | 'gdocs', 108 | 'facebook', 109 | 'dropbox', 110 | 'flickr', 111 | 'box', 112 | 'clouddrive', 113 | 'picasa', 114 | 'gdocs', 115 | 'dropbox' 116 | ]; 117 | } 118 | 119 | 120 | function handle_click(info, tab){ 121 | console.log(arguments); 122 | var url = info.srcUrl || info.linkUrl || info.pageUrl; 123 | console.log('Source URL', url); 124 | var name = unescape(decodeURIComponent( 125 | unescape(unescape(unescape(url))) 126 | .replace(/\s/g, '+') 127 | .replace(/^.*\/|\?.*$|\#.*$|\&.*$/g,'') || 128 | url.replace(/.*\/\/|www./g,'') 129 | .replace(/[^\w]+/g,'_') 130 | .replace(/^_*|_*$/g,'')) 131 | ).replace(/\+/g, ' '); 132 | console.log('Processed name', name); 133 | if(info.selectionText){ 134 | url = 'data:text/plain,'+encodeURIComponent(info.selectionText); 135 | } 136 | var host = menu_ids[info.menuItemId]; 137 | if(Hosts[host]){ 138 | if(host == 'dropdo'){ 139 | chrome.tabs.create({ 140 | url: 'http://dropdo.com/upload?link='+url 141 | }) 142 | }else{ 143 | if(host == 'dropbox' && localStorage.folder_prefix){ 144 | name = localStorage.folder_prefix + name; 145 | } 146 | if(info.parentMenuItemId == save_as){ 147 | //woot save as stuff 148 | console.log('save as'); 149 | name = prompt('Save file as...', name); 150 | if(!name) return; 151 | }; 152 | 153 | if(name.indexOf('/') != -1){ 154 | localStorage.folder_prefix = name.replace(/[^\/]+$/,''); 155 | } 156 | 157 | upload(host, url, name); 158 | } 159 | console.log(host, url, name); 160 | recent.push(host); 161 | recent.shift(); 162 | localStorage.cloudsave_recent = JSON.stringify(recent); 163 | updateMenus(); 164 | }else{ 165 | alert("Could not find host "+host); 166 | } 167 | } 168 | 169 | var title_map = {}; 170 | var menu_ids = {}; 171 | 172 | function updateMenus(){ 173 | title_map = {}; 174 | for(var i in classes){ 175 | for(var h in classes[i]){ 176 | title_map[h] = classes[i][h]; //flatten it out 177 | } 178 | } 179 | 180 | Object.keys(menu_ids).reverse().forEach(function(item){ 181 | chrome.contextMenus.remove(parseInt(item)); 182 | }); 183 | menu_ids = {}; 184 | for(var unique = [], freqmap = {}, i = 0; i < recent.length;i++){ 185 | if(title_map[recent[i]]){ 186 | if(!freqmap[recent[i]]){ 187 | freqmap[recent[i]] = 1; 188 | unique.push(recent[i]); 189 | } 190 | freqmap[recent[i]]++; 191 | } 192 | } 193 | var dilation_factor = 100; 194 | function grade(result){ 195 | return freqmap[result] + recent.lastIndexOf(result) / dilation_factor; 196 | } 197 | var sorted = unique.sort(function(a,b){ 198 | return grade(b) - grade(a); 199 | }); 200 | console.log(recent); 201 | console.log(unique.map(function(a){ 202 | return a + ' ' + grade(a) 203 | })) 204 | for(var i = 0; i < sorted.length; i++){ 205 | var prop = { 206 | "title": title_map[sorted[i]], 207 | "onclick": handle_click 208 | }; 209 | prop.contexts = classes.image[sorted[i]] ? 210 | ['image'] : 211 | (classes.link[sorted[i]]? 212 | ['image', 'link', 'selection']: ['all'/*'page', 'link', 'image', 'selection'*/]); 213 | prop.parentId = root; 214 | menu_ids[chrome.contextMenus.create(clone(prop))] = sorted[i]; 215 | prop.parentId = save_as; 216 | menu_ids[chrome.contextMenus.create(clone(prop))] = sorted[i]; 217 | } 218 | var others = Object.keys(title_map).sort().filter(function(x){ 219 | return unique.indexOf(x) == -1; 220 | }); 221 | /* 222 | menu_ids[chrome.contextMenus.create({ 223 | "type": "separator", 224 | "contexts": ["all"], 225 | "parentId": root 226 | })] = 42; 227 | menu_ids[chrome.contextMenus.create({ 228 | "type": "separator", 229 | "contexts": ["all"], 230 | "parentId": save_as 231 | })] = 42; 232 | //*/ 233 | var save_as_more = chrome.contextMenus.create({ 234 | "title": "More", 235 | "parentId": save_as, 236 | "contexts": ["all"] 237 | }); 238 | menu_ids[save_as_more] = 'save_as_more'; 239 | var root_more = chrome.contextMenus.create({ 240 | "title": "More", 241 | "parentId": root, 242 | "contexts": ["all"] 243 | }); 244 | menu_ids[root_more] = 'root_more'; 245 | function add_more(host){ 246 | var prop = { 247 | "title": title_map[host], 248 | "onclick": handle_click 249 | }; 250 | prop.contexts = classes.image[host] ? 251 | ['image'] : 252 | (classes.link[host]? 253 | ['image', 'link']: ['all'/*'page', 'link', 'image'*/]); 254 | prop.parentId = root_more; 255 | menu_ids[chrome.contextMenus.create(clone(prop))] = host; 256 | prop.parentId = save_as_more; 257 | menu_ids[chrome.contextMenus.create(clone(prop))] = host; 258 | } 259 | 260 | for(var i = 0; i < others.length; i++){ 261 | if(classes.image[others[i]]){ 262 | add_more(others[i]) 263 | } 264 | } 265 | menu_ids[chrome.contextMenus.create({ 266 | "type": "separator", 267 | "contexts": ["image"], 268 | "parentId": root_more 269 | })] = 42; 270 | menu_ids[chrome.contextMenus.create({ 271 | "type": "separator", 272 | "contexts": ["image"], 273 | "parentId": save_as_more 274 | })] = 42; 275 | for(var i = 0; i < others.length; i++){ 276 | if(!classes.image[others[i]]){ 277 | add_more(others[i]) 278 | } 279 | } 280 | //* 281 | menu_ids[chrome.contextMenus.create({ 282 | "type": "separator", 283 | "contexts": ["all"], 284 | "parentId": root_more 285 | })] = 42; 286 | menu_ids[chrome.contextMenus.create({ 287 | "title": "Add/Remove", 288 | "contexts": ["all"], 289 | "parentId": root_more, 290 | "onclick": open_settings 291 | })] = 'add_remove'; //*/ 292 | } 293 | 294 | 295 | function open_settings(){ 296 | chrome.tabs.create({url: "settings.html"}) 297 | } 298 | 299 | //shamelessly stolen from john resig. 300 | function wbr(str, num) { 301 | return str.replace(RegExp("(\\w{" + num + "})(\\w)", "g"), function(all,text,char){ 302 | return text + "" + char; 303 | }); 304 | } 305 | 306 | var INDETERMINATE = {}; 307 | 308 | 309 | function updateNotification(id,opt){ 310 | chrome.notifications.update(id, opt,function(wasUpdated){ 311 | if(!wasUpdated){ 312 | console.log('Error! Could not locate notification', id,opt.title, opt.message); 313 | } 314 | }); 315 | } 316 | 317 | 318 | var urlid = { 319 | 'todo_fix_this': 42 320 | //this is a sort of hack. it uses the file download urls 321 | //as a sort of state callback whatnot stuff. 322 | }; 323 | 324 | function uploadProgress(url, evt){ 325 | var pr=Math.round(evt.loaded * 100 / evt.total); 326 | var upload={ 327 | type: 'progress', 328 | title:'Upload', 329 | message:'Uploading...', 330 | iconUrl:'icon/48.png', 331 | progress:pr 332 | } 333 | updateNotification(urlid[url],upload); 334 | } 335 | 336 | function downloadProgress(url, evt){ 337 | var pr=Math.round(evt.loaded * 100 / evt.total); 338 | var download={ 339 | type: 'progress', 340 | title:'Download', 341 | message:'Downloading...', 342 | iconUrl:'icon/48.png', 343 | progress:pr 344 | } 345 | updateNotification(urlid[url],download); 346 | } 347 | 348 | 349 | function upload(host, url, name){ 350 | var id = Math.random().toString(36).substr(3); 351 | var opt = { 352 | type: "basic", 353 | title: "Saving", 354 | message: 'The file is being saved to '+title_map[host], 355 | iconUrl: "icon/throbber.gif" 356 | } 357 | chrome.notifications.create(id, opt,function(nid){ 358 | console.log("id="+nid); 359 | }) 360 | 361 | var has_uploaded = false; 362 | var upload_callback = function(){}; 363 | 364 | chrome.notifications.onClicked.addListener(function(notificationId){ 365 | if(has_uploaded){ 366 | openFile(); 367 | clearnotification(notificationId); 368 | }else{ 369 | upload_callback = openFile; 370 | } 371 | }) 372 | 373 | chrome.notifications.onClosed.addListener(function(notificationId,byUser){ 374 | delete urlid[url]; 375 | }) 376 | 377 | function clearnotification(id){ 378 | chrome.notifications.clear(id, function(wasCleared){ 379 | if(wasCleared!=true) 380 | console.log('clear notification failed'); 381 | }) 382 | } 383 | 384 | function openFile(){ 385 | chrome.tabs.create({url: has_uploaded}) 386 | } 387 | urlid[url] = id; 388 | Hosts[host]({ 389 | url: url, 390 | name: name 391 | }, function(e){ 392 | has_uploaded = e && e.url; 393 | var ntitle,nmsg,nicon; 394 | setTimeout(upload_callback, 200); 395 | console.log('uploaded file yay', e); 396 | if(e && typeof e == "string" && e.indexOf('error:') != -1){ 397 | ntitle='Error'; 398 | nmsg="Could not be uploaded to "+title_map[host]; 399 | console.log('e.error'+e.substr(6)); 400 | nicon='icon/64sad.png'; 401 | }else{ 402 | ntitle="Success"; 403 | nmsg="The file has been uploaded to "+title_map[host]+", click me to view."; 404 | nicon="icon/64.png"; 405 | } 406 | 407 | var opt = { 408 | type: 'basic', 409 | title: ntitle, 410 | message: nmsg, 411 | iconUrl: nicon 412 | }; 413 | updateNotification(id, opt); 414 | }) 415 | } 416 | 417 | 418 | function install_additional(state){ 419 | if(state){ 420 | for(var i in additional){ 421 | for(var ii in additional[i]) 422 | classes[i][ii] = additional[i][ii]; 423 | } 424 | }else{ 425 | classes = clone(original); 426 | } 427 | updateMenus(); 428 | } 429 | 430 | if(localStorage.additional == 'yes'){ 431 | install_additional(true); 432 | }else{ 433 | updateMenus(); 434 | } 435 | -------------------------------------------------------------------------------- /files.js: -------------------------------------------------------------------------------- 1 | //stolen from mozilla http://demos.hacks.mozilla.org/openweb/imageUploader/js/extends/xhr.js 2 | //http://code.google.com/p/chromium/issues/detail?id=35705#c6 3 | //http://efreedom.com/Question/1-3743047/Uploading-Binary-String-WebKit-Chrome-Using-XHR-Equivalent-Firefoxs-SendAsBinary 4 | //this is a mutilated sendMultipart function. BEWARE! 5 | 6 | if(typeof btoa == 'undefined'){ 7 | 8 | btoa = function (input) { 9 | var output = "", i = 0, l = input.length, 10 | key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 11 | chr1, chr2, chr3, enc1, enc2, enc3, enc4; 12 | while (i < l) { 13 | chr1 = input.charCodeAt(i++); 14 | chr2 = input.charCodeAt(i++); 15 | chr3 = input.charCodeAt(i++); 16 | enc1 = chr1 >> 2; 17 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 18 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 19 | enc4 = chr3 & 63; 20 | if (isNaN(chr2)) enc3 = enc4 = 64; 21 | else if (isNaN(chr3)) enc4 = 64; 22 | output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4); 23 | } 24 | return output; 25 | } 26 | 27 | atob = function(input){ 28 | var output = "", i = 0, l = input.length, 29 | key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 30 | chr1, chr2, chr3, enc1, enc2, enc3, enc4; 31 | while (i < l) { 32 | enc1 = key.indexOf(input.charAt(i++)); 33 | enc2 = key.indexOf(input.charAt(i++)); 34 | enc3 = key.indexOf(input.charAt(i++)); 35 | enc4 = key.indexOf(input.charAt(i++)); 36 | chr1 = (enc1 << 2) | (enc2 >> 4); 37 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 38 | chr3 = ((enc3 & 3) << 6) | enc4; 39 | output = output + String.fromCharCode(chr1); 40 | if (enc3 != 64) output = output + String.fromCharCode(chr2); 41 | if (enc4 != 64) output = output + String.fromCharCode(chr3); 42 | } 43 | return output; 44 | } 45 | 46 | } 47 | 48 | XMLHttpRequest.prototype.sendMultipart = function(params) { 49 | var BOUNDARY = "---------------------------1966284435497298061834782736"; 50 | var rn = "\r\n"; 51 | console.log(params) 52 | 53 | var tmp = new XMLHttpRequest(); 54 | var abuf = 'responseType' in tmp && 'response' in tmp; 55 | 56 | var binxhr = !!this.sendAsBinary; 57 | if(binxhr){ 58 | var req = '', append = function(data){req += data} 59 | }else{ 60 | var req = [], append = function(data){req.push(data)} 61 | } 62 | 63 | append("--" + BOUNDARY); 64 | 65 | var file_param = -1; 66 | var xhr = this; 67 | 68 | 69 | 70 | 71 | for (var i in params) { 72 | if (typeof params[i] == "object") { 73 | file_param = i; 74 | } else { 75 | append(rn + "Content-Disposition: form-data; name=\"" + i + "\""); 76 | append(rn + rn + params[i] + rn + "--" + BOUNDARY); 77 | } 78 | } 79 | 80 | var i = file_param; 81 | 82 | append(rn + "Content-Disposition: form-data; name=\"" + i + "\""); 83 | 84 | getURL(abuf?'arraybuffer':(binxhr?'binary':'raw'),params[i], function(file){ 85 | //Uint8 does clamping, but sendAsBinary doesn't 86 | console.log('actual data entity', file); 87 | 88 | xhr.upload.addEventListener('progress', function(evt){ 89 | uploadProgress(file.url, evt); 90 | }, false) 91 | 92 | append("; filename=\""+file.name+"\"" + rn + "Content-type: "+file.type); 93 | 94 | append(rn + rn); 95 | 96 | if(binxhr){ 97 | append(file.data); 98 | }else if(abuf){ 99 | append(file.data); 100 | }else{ 101 | var bin = file.data 102 | var arr = new Uint8Array(bin.length); 103 | for(var i = 0, l = bin.length; i < l; i++) 104 | arr[i] = bin.charCodeAt(i); 105 | 106 | append(arr.buffer) 107 | } 108 | append(rn + "--" + BOUNDARY); 109 | 110 | append("--"); 111 | 112 | 113 | xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); 114 | 115 | if(binxhr){ 116 | xhr.sendAsBinary(req); 117 | }else{ 118 | superblob = new Blob(req); 119 | xhr.send(superblob); 120 | } 121 | }); 122 | }; 123 | -------------------------------------------------------------------------------- /hosts/box.js: -------------------------------------------------------------------------------- 1 | //box.net 2 | 3 | Hosts.box = function uploadBox(file, callback){ 4 | function create_folder(){ 5 | var xhr = new XMLHttpRequest(); 6 | var fname = 'cloudsave'; 7 | xhr.open('GET', 'https://www.box.net/api/1.0/rest?action=create_folder&api_key='+Keys.box+'&auth_token='+localStorage.box_auth+'&parent_id=0&share=0&name='+fname, true); 8 | xhr.send(); 9 | xhr.onload = function(){ 10 | if(xhr.responseText.indexOf('not_logged_in') != -1){ 11 | login(function(){ 12 | //function inside a function (passed to another function inside a function inside a function) inside a function inside a function 13 | create_folder(); 14 | }); 15 | }else{ 16 | var fid = xhr.responseXML.getElementsByTagName('folder_id')[0].firstChild.nodeValue; 17 | console.log('folder ID', fid); 18 | upload(fid); 19 | } 20 | } 21 | } 22 | 23 | 24 | function upload(folder){ 25 | var xhr = new XMLHttpRequest(); 26 | xhr.open('POST', 'https://upload.box.net/api/1.0/upload/'+localStorage.box_auth+'/'+folder+'?new_copy=1'); 27 | xhr.onload = function(){ 28 | callback({ 29 | url: "http://box.net/" 30 | }); 31 | } 32 | xhr.sendMultipart({ 33 | share: 1, 34 | file: file 35 | }) 36 | } 37 | 38 | function login(stopforward){ //sort of opposite vaguely of callback 39 | 40 | function auth_token(url){ 41 | var auth = url.match(/auth_token=([^\&]+)/)[1]; 42 | localStorage.box_auth = auth; 43 | console.log(localStorage.box_auth, localStorage.box_ticket); 44 | stopforward(); 45 | } 46 | 47 | var xhr = new XMLHttpRequest(); 48 | xhr.open('GET', https()+'www.box.net/api/1.0/rest?action=get_ticket&api_key='+Keys.box, true); 49 | xhr.send(); 50 | xhr.onload = function(){ 51 | var ticket = xhr.responseXML.getElementsByTagName('ticket')[0].firstChild.nodeValue; 52 | localStorage.box_ticket = ticket; 53 | var redirect = https()+"www.box.net/api/1.0/auth/"+ticket; 54 | 55 | loginTab(redirect, 'auth_token', auth_token); 56 | } 57 | } 58 | 59 | create_folder() 60 | } 61 | -------------------------------------------------------------------------------- /hosts/cloudapp.js: -------------------------------------------------------------------------------- 1 | //uses multipart helper function. 2 | //does not support https 3 | Hosts.cloudapp = function uploadCloudApp(file, callback){ 4 | var xhr = new XMLHttpRequest(); 5 | xhr.open('GET', 'http://my.cl.ly/items/new'); 6 | xhr.setRequestHeader('Accept', 'application/json'); 7 | xhr.onload = function(){ 8 | if(xhr.status == 401){ 9 | //i can haz login 10 | 11 | 12 | if(typeof chrome != 'undefined'){ 13 | chrome.tabs.create({ 14 | url: "http://my.cl.ly/login" 15 | }, function(tab){ 16 | var poll = function(){ 17 | chrome.tabs.get(tab.id, function(info){ 18 | if('http://my.cl.ly/' == (info.url)){ 19 | chrome.tabs.remove(tab.id); 20 | uploadCloudApp(file, callback); 21 | }else{ 22 | setTimeout(poll, 100) 23 | } 24 | }) 25 | }; 26 | poll(); 27 | }) 28 | }else if(typeof tabs != 'undefined'){ 29 | tabs.open({ 30 | url: "http://my.cl.ly/login", 31 | onOpen: function(tab){ 32 | var poll = function(){ 33 | if('http://my.cl.ly/' == (tab.url)){ 34 | tab.close() 35 | uploadCloudApp(file, callback); 36 | }else{ 37 | setTimeout(poll, 100) 38 | } 39 | }; 40 | poll(); 41 | } 42 | }) 43 | } 44 | 45 | 46 | }else{ 47 | var json = JSON.parse(xhr.responseText); 48 | var xhr2 = new XMLHttpRequest(); 49 | xhr2.open('POST', json.url); 50 | if(json.uploads_remaining == 0){ 51 | return callback('error: You have exceeded your maximum number of uploads today for CloudApp. You may need to upgrade your account.'); 52 | } 53 | json.params.key = json.params.key.replace('${filename}', file.name); 54 | json.params.file = file; 55 | xhr2.sendMultipart(json.params); 56 | xhr2.onload = function(){ 57 | //since ajax cant add the Accept: header to the redirects, heres a hack 58 | var xhr3 = new XMLHttpRequest(); 59 | xhr3.open('GET', 'http://my.cl.ly/items'); 60 | xhr3.setRequestHeader('Accept', 'application/json'); 61 | xhr3.onload = function(){ 62 | var j3 = JSON.parse(xhr3.responseText)[0]; 63 | callback({ 64 | direct: j3.remote_url, 65 | url: j3.url 66 | }) 67 | } 68 | xhr3.send() 69 | } 70 | } 71 | } 72 | xhr.send() 73 | } 74 | -------------------------------------------------------------------------------- /hosts/clouddrive.js: -------------------------------------------------------------------------------- 1 | Hosts.clouddrive = function uploadclouddrive(file, callback){ 2 | var cid = ''; 3 | var sessid = '' 4 | 5 | function apiRequest(p, callback){ 6 | var xhr = new XMLHttpRequest(); 7 | p._ = +new Date; 8 | p.ContentType = 'JSON'; 9 | p.customerId = cid; 10 | xhr.open('GET', 'https://www.amazon.com/clouddrive/api/?'+urlencode(p), true); 11 | xhr.setRequestHeader('x-amzn-SessionId', sessid); 12 | xhr.onload = function(){ 13 | var json = JSON.parse(xhr.responseText); 14 | callback(json); 15 | } 16 | xhr.send(null); 17 | } 18 | 19 | function urlencode(p){ 20 | var params = []; 21 | for(var i in p){ 22 | params.push(i+'='+encodeURIComponent(p[i])); 23 | } 24 | return params.join('&'); 25 | } 26 | 27 | 28 | function login(){ 29 | var loginurl = 'https://www.amazon.com/ap/signin?_encoding=UTF8&openid.assoc_handle=usflex&openid.return_to=https%3A%2F%2Fwww.amazon.com%2Fclouddrive%2F%3F_encoding%3DUTF8%26path%3D%252Fclouddrive%252F%26ref_%3Dpd_irl_gw_r%26signIn%3D1%26useRedirectOnSuccess%3D1%26action%3Dsign-out&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.pape.max_auth_age=0&openid.ns.pape=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select'; 30 | loginTab(loginurl, '/clouddrive', getKeys); 31 | } 32 | 33 | 34 | function getKeys(){ 35 | var xhr = new XMLHttpRequest(); 36 | xhr.open('GET', 'https://www.amazon.com/clouddrive', true); 37 | xhr.send(null); 38 | xhr.onload = function(){ 39 | var match = xhr.responseText.match(//); 40 | var match2 = xhr.responseText.match(//); 41 | if(!match || !match2){ 42 | //todo: redirect to amazon lgoin 43 | console.log('Error: not logged in'); 44 | login(); 45 | }else{ 46 | cid = match[1]; 47 | sessid = match2[1]; 48 | createFile(); 49 | } 50 | } 51 | } 52 | 53 | 54 | 55 | function createFile(){ 56 | apiRequest({ 57 | Operation: 'createByPath', 58 | type: 'FILE', 59 | path: '/', 60 | name: file.name, 61 | conflictResolution: 'RENAME', 62 | overwrite: true, 63 | autoparent: true 64 | }, function(json){ 65 | getUpload(json.createByPathResponse.createByPathResult.objectId); 66 | }) 67 | } 68 | 69 | 70 | 71 | 72 | function completeFile(objectid, storagekey){ 73 | apiRequest({ 74 | Operation: 'completeFileUploadById', 75 | objectId: objectid, 76 | storageKey: storagekey 77 | }, function(json){ 78 | callback({ 79 | url: 'https://www.amazon.com/clouddrive' 80 | }) 81 | }) 82 | } 83 | 84 | function getUpload(objectid){ 85 | apiRequest({ 86 | Operation: 'getUploadUrlById', 87 | objectId: objectid, 88 | size: 42, //d.data.byteLength, 89 | method: 'POST' 90 | }, function(json){ 91 | var h = json.getUploadUrlByIdResponse.getUploadUrlByIdResult.httpRequest; 92 | console.log('got http stuff', h); 93 | var storageKey = json.getUploadUrlByIdResponse.getUploadUrlByIdResult.storageKey; 94 | doUpload(h, function(){ 95 | completeFile(objectid, storageKey); 96 | }); 97 | }) 98 | } 99 | 100 | function doUpload(h, cb){ 101 | var xhr = new XMLHttpRequest(); 102 | var params = JSON.parse(JSON.stringify(h.parameters)); 103 | params.Filename = file.name; 104 | params.file = file; 105 | console.log(params); 106 | xhr.open('POST', h.endpoint, true); 107 | xhr.onload = function(){ 108 | if(cb) cb(); 109 | } 110 | xhr.sendMultipart(params) 111 | } 112 | 113 | getKeys(); 114 | 115 | } 116 | -------------------------------------------------------------------------------- /hosts/cx.js: -------------------------------------------------------------------------------- 1 | // Cx 2 | 3 | Hosts.cx = function uploadCx(file, callback) { 4 | var login_shown = false; 5 | var poll = function(){ 6 | var xhr = new XMLHttpRequest(); 7 | xhr.open("GET", "https://www.cx.com/0/userInfo/viewProfile"); 8 | xhr.send(); 9 | xhr.onload = function(){ 10 | if (xhr.status==200) { // Logged in 11 | 12 | var tmp = new XMLHttpRequest(); 13 | var abuf = 'responseType' in tmp && 'response' in tmp; 14 | var binxhr = !!this.sendAsBinary; 15 | 16 | // Amazon needs file size to upload 17 | getURL(abuf?'arraybuffer':(binxhr?'binary':'raw'),file, function(file){ 18 | 19 | var xhr2 = new XMLHttpRequest(); 20 | xhr2.open("POST", "https://www.cx.com/0/filedata/upload"); 21 | xhr2.onload = function(){ 22 | callback({url:"http://www.cx.com/mycx/files"}); 23 | } 24 | xhr2.sendMultipart({ 25 | fileName: file.name, 26 | fileSize: file.data.byteLength, 27 | file: file 28 | }); 29 | }); 30 | 31 | } else { 32 | if (!login_shown) { 33 | chrome.tabs.create({url:"http://www.cx.com/mycx/sign_in"}); 34 | login_shown = true; 35 | } 36 | setTimeout(poll, 2000); 37 | } 38 | } 39 | } 40 | poll(); 41 | } -------------------------------------------------------------------------------- /hosts/dropbox/dropbox.js: -------------------------------------------------------------------------------- 1 | //uses multipart helper function. 2 | 3 | 4 | Hosts.dropbox = function uploadDropbox(file, callback){ 5 | var dropbox = new ModernDropbox(Keys.dropbox.key, Keys.dropbox.secret) 6 | 7 | var poll = function(){ 8 | if(dropbox.isAccessGranted()){ 9 | var fname = file.name; 10 | var folder = '' 11 | 12 | dropbox.getAccountInfo(function(user){ 13 | 14 | 15 | dropbox.getDirectoryMetadata(folder + encodeURIComponent(file.name), function(json){ 16 | if(json.error && json.error.indexOf('not found') != -1){ 17 | //yay plop it on the top 18 | }else if(fname.indexOf('/') == -1){ 19 | fname = Math.random().toString(36).substr(2,4) + '_' + fname; 20 | }else{ 21 | //no idea. TODO: do something 22 | } 23 | 24 | 25 | dropbox.putFileContents(folder + fname, file, 26 | function(){ 27 | console.log('done uploading'); 28 | //yay done. hopefully 29 | console.log('got stuffs now'); 30 | callback({ 31 | url: 'https://www.dropbox.com/' 32 | }) 33 | }); 34 | }) 35 | 36 | }) 37 | 38 | }else{ 39 | setTimeout(poll, 300); 40 | } 41 | }; 42 | poll(); 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /hosts/dropbox/modern_dropbox.js: -------------------------------------------------------------------------------- 1 | var ModernDropbox = function(consumerKey, consumerSecret) { 2 | // Constructor / Private 3 | var _consumerKey = consumerKey; 4 | var _consumerSecret = consumerSecret; 5 | 6 | var _tokens = {}; 7 | var _storagePrefix = "moderndropbox_"; 8 | var _isSandbox = false; 9 | var _cache = true; 10 | var _authCallback = "http://drag2up.appspot.com/static/tpilb.html"; 11 | var _fileListLimit = 10000; 12 | var _cookieTimeOut = 3650; 13 | var _dropboxApiVersion = 0; 14 | var _xhr = new XMLHttpRequest(); 15 | 16 | var _ajaxSendFileContents = function(message, filename, content, callback) { 17 | _xhr.open("POST", message.action, true); 18 | 19 | var params = {}; 20 | 21 | for (i in message.parameters) { 22 | params[message.parameters[i][0]] = message.parameters[i][1]; 23 | } 24 | 25 | content.name = filename; 26 | params.file = content; 27 | 28 | _xhr.onreadystatechange = function() { 29 | //console.log(this); 30 | if(_xhr.status == 200 && _xhr.readyState == 4){ 31 | callback(_xhr); 32 | } 33 | } 34 | console.log(params); 35 | _xhr.sendMultipart(params); 36 | }; 37 | 38 | var _setAuthCallback = function(callback) { 39 | _authCallback = callback; 40 | }; 41 | 42 | var _setupAuthStorage = function() { 43 | keys = ["requestToken", "requestTokenSecret", "accessToken", "accessTokenSecret"]; 44 | 45 | for (i in keys) { 46 | var key = keys[i]; 47 | value = localStorage[_storagePrefix + key]; 48 | if (value) { 49 | _tokens[key] = value; 50 | } 51 | } 52 | }; 53 | 54 | var _clearAuthStorage = function() { 55 | keys = ["requestToken", "requestTokenSecret", "accessToken", "accessTokenSecret"]; 56 | 57 | for (i in keys) { 58 | var key = keys[i]; 59 | localStorage.removeItem(_storagePrefix + key); 60 | } 61 | }; 62 | 63 | var _storeAuth = function(valueMap) { 64 | keys = ["requestToken", "requestTokenSecret", "accessToken", "accessTokenSecret"]; 65 | 66 | for (i in keys) { 67 | var key = keys[i]; 68 | 69 | if (valueMap[key] !== undefined) { 70 | 71 | localStorage[_storagePrefix + key] = valueMap[key]; 72 | _tokens[key] = valueMap[key]; 73 | } 74 | } 75 | }; 76 | 77 | var _isAccessGranted = function() { 78 | return (_tokens["accessToken"] != null) && (_tokens["accessTokenSecret"] != null); 79 | }; 80 | 81 | var _isAuthorized = function() { 82 | return (_tokens["requestToken"] != null) && (_tokens["requestTokenSecret"] != null); 83 | }; 84 | 85 | var _createOauthRequest = function(url, options) { 86 | if (!options) { 87 | options = []; 88 | } 89 | 90 | // Outline the message 91 | var message = { 92 | action: url, 93 | method: "GET", 94 | parameters: [ 95 | ["oauth_consumer_key", _consumerKey], 96 | ["oauth_signature_method", "HMAC-SHA1"] 97 | ] 98 | }; 99 | 100 | // Define the accessor 101 | var accessor = { 102 | consumerSecret: _consumerSecret, 103 | }; 104 | 105 | if (!options.token) { 106 | message.parameters.push(["oauth_token", _tokens["accessToken"]]); 107 | } else { 108 | message.parameters.push(["oauth_token", options.token]); 109 | delete options.token; 110 | } 111 | 112 | if (!options.tokenSecret) { 113 | accessor.tokenSecret = _tokens["accessTokenSecret"]; 114 | } else { 115 | accessor.tokenSecret = options.tokenSecret; 116 | delete options.tokenSecret; 117 | } 118 | 119 | if (options.method) { 120 | message.method = options.method; 121 | delete options.method; 122 | } 123 | 124 | for (key in options) { 125 | message.parameters.push([key, options[key]]); 126 | } 127 | 128 | OAuth.setTimestampAndNonce(message); 129 | OAuth.SignatureMethod.sign(message, accessor); 130 | 131 | return message; 132 | }; 133 | 134 | var _sendOauthRequest = function(message, options) { 135 | if (!options) { 136 | options = []; 137 | } 138 | 139 | if (!options.success) { 140 | options.success = function() {}; 141 | } 142 | 143 | if (!options.error) { 144 | options.error = function() {}; 145 | } 146 | 147 | if (!options.type) { 148 | options.type = "json"; 149 | } 150 | 151 | if (options.multipart) { 152 | _ajaxSendFileContents( 153 | message, 154 | options.filename, 155 | options.content, 156 | options.success 157 | ); 158 | } else { 159 | var xhr = new XMLHttpRequest(); 160 | function params(obj){ 161 | var str = []; 162 | for(var i in obj) str.push(i+'='+encodeURIComponent(obj[i])); 163 | return str.join('&'); 164 | } 165 | 166 | 167 | var data = params(OAuth.getParameterMap(message.parameters)); 168 | 169 | 170 | if(message.method.toLowerCase() == 'post'){ 171 | xhr.open(message.method, message.action, true); 172 | xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded'); 173 | xhr.send(data); 174 | }else{ 175 | xhr.open(message.method, message.action+'?'+data, true); 176 | xhr.send(null); 177 | } 178 | xhr.onload = function(){ 179 | var res = xhr.responseText; 180 | 181 | if(options.type == 'json') res = JSON.parse(res); 182 | 183 | options.success(res); 184 | } 185 | } 186 | }; 187 | 188 | // Public 189 | return ({ 190 | initialize: function() { 191 | _setupAuthStorage(); 192 | 193 | if (!_isAccessGranted()) { 194 | if (!_isAuthorized()) { 195 | var message = _createOauthRequest("https://www.dropbox.com/" + _dropboxApiVersion + "/oauth/request_token"); 196 | 197 | _sendOauthRequest(message, { 198 | type: "text", 199 | success: (function(data) { 200 | if (!data) { 201 | data = ""; 202 | } 203 | 204 | var tokenPairStrings = data.split("&"); 205 | var parsedTokenPairs = []; 206 | 207 | for (i in tokenPairStrings) { 208 | var tokenPairs = tokenPairStrings[i].split("="); 209 | parsedTokenPairs[tokenPairs[0]] = tokenPairs[1]; 210 | } 211 | 212 | var authTokens = {}; 213 | authTokens["requestToken"] = parsedTokenPairs["oauth_token"]; 214 | authTokens["requestTokenSecret"] = parsedTokenPairs["oauth_token_secret"]; 215 | 216 | _storeAuth(authTokens); 217 | var init = this.initialize; 218 | var url = "https://www.dropbox.com/" + _dropboxApiVersion + "/oauth/authorize?oauth_token=" 219 | + authTokens["requestToken"] 220 | + "&oauth_callback=" 221 | + _authCallback; 222 | 223 | loginTab(url, 'uid=', init); 224 | }).bind(this) 225 | }); 226 | } else { 227 | var message = _createOauthRequest("https://www.dropbox.com/" + _dropboxApiVersion + "/oauth/access_token", { 228 | token: _tokens["requestToken"], 229 | tokenSecret: _tokens["requestTokenSecret"] 230 | }); 231 | 232 | _sendOauthRequest(message, { 233 | type: "text", 234 | success: (function(data) { 235 | if (!data) { 236 | data = ""; 237 | } 238 | 239 | var tokenPairStrings = data.split("&"); 240 | var parsedTokenPairs = []; 241 | 242 | for (i in tokenPairStrings) { 243 | var tokenPairs = tokenPairStrings[i].split("="); 244 | parsedTokenPairs[tokenPairs[0]] = tokenPairs[1]; 245 | } 246 | 247 | var authTokens = {}; 248 | authTokens["accessToken"] = parsedTokenPairs["oauth_token"]; 249 | authTokens["accessTokenSecret"] = parsedTokenPairs["oauth_token_secret"]; 250 | 251 | _storeAuth(authTokens); 252 | }).bind(this), 253 | error: (function(data){ 254 | _storeAuth({ 255 | requestToken: '', 256 | requestTokenSecret: '' 257 | }) 258 | this.initialize(); 259 | }).bind(this) 260 | }); 261 | } 262 | } 263 | 264 | return this; 265 | }, 266 | isAccessGranted: function(){ 267 | return _isAccessGranted() 268 | }, 269 | getAccountInfo: function(callback) { 270 | var url = "https://www.dropbox.com/" + _dropboxApiVersion + "/account/info"; 271 | var message = _createOauthRequest(url); 272 | _sendOauthRequest(message, { 273 | type: "json", 274 | success: (function(data) { callback(data); }).bind(this) 275 | }); 276 | }, 277 | 278 | getDirectoryContents: function(path, callback) { 279 | var url = "https://www.dropbox.com/" + _dropboxApiVersion + "/metadata/dropbox/" + path; 280 | var message = _createOauthRequest(url, { 281 | file_limit: _fileListLimit, 282 | list: "true" 283 | }); 284 | 285 | _sendOauthRequest(message, { 286 | type: "json", 287 | success: (function(data) { callback(data); }).bind(this) 288 | }); 289 | }, 290 | 291 | getDirectoryMetadata: function(path, callback) { 292 | var url = "https://www.dropbox.com/" + _dropboxApiVersion + "/metadata/dropbox/" + path; 293 | var message = _createOauthRequest(url, { 294 | list: "false" 295 | }); 296 | 297 | _sendOauthRequest(message, { 298 | type: "json", 299 | success: (function(data) { callback(data); }).bind(this) 300 | }); 301 | }, 302 | 303 | getFileContents: function(path, callback) { 304 | var url = "https://api-content.dropbox.com/" + _dropboxApiVersion + "/files/dropbox/" + path; 305 | var message = _createOauthRequest(url); 306 | 307 | _sendOauthRequest(message, { 308 | type: "text", 309 | success: (function(data) { callback(data); }).bind(this) 310 | }); 311 | }, 312 | 313 | putFileContents: function(path, file, callback) { 314 | var filename = path.match(/([^\\\/]+)$/)[1]; 315 | var file_path = path.match(/^(.*?)[^\\\/]+$/)[1]; 316 | var url = "https://api-content.dropbox.com/" + _dropboxApiVersion + "/files/dropbox/" + file_path + "?file=" + filename; 317 | var message = _createOauthRequest(url, { method: "POST" }); 318 | 319 | _sendOauthRequest(message, { 320 | multipart: true, 321 | content: file, 322 | filename: filename, 323 | success: (function(data) { callback(data); }).bind(this) 324 | }); 325 | }, 326 | 327 | logOutDropbox: function() { 328 | _clearAuthStorage(); 329 | } 330 | }).initialize(); 331 | }; 332 | -------------------------------------------------------------------------------- /hosts/dropbox/oauth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* Here's some JavaScript software for implementing OAuth. 18 | 19 | This isn't as useful as you might hope. OAuth is based around 20 | allowing tools and websites to talk to each other. However, 21 | JavaScript running in web browsers is hampered by security 22 | restrictions that prevent code running on one website from 23 | accessing data stored or served on another. 24 | 25 | Before you start hacking, make sure you understand the limitations 26 | posed by cross-domain XMLHttpRequest. 27 | 28 | On the bright side, some platforms use JavaScript as their 29 | language, but enable the programmer to access other web sites. 30 | Examples include Google Gadgets, and Microsoft Vista Sidebar. 31 | For those platforms, this library should come in handy. 32 | */ 33 | 34 | // The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by 35 | // http://pajhome.org.uk/crypt/md5/sha1.js 36 | 37 | /* An OAuth message is represented as an object like this: 38 | {method: "GET", action: "http://server.com/path", parameters: ...} 39 | 40 | The parameters may be either a map {name: value, name2: value2} 41 | or an Array of name-value pairs [[name, value], [name2, value2]]. 42 | The latter representation is more powerful: it supports parameters 43 | in a specific sequence, or several parameters with the same name; 44 | for example [["a", 1], ["b", 2], ["a", 3]]. 45 | 46 | Parameter names and values are NOT percent-encoded in an object. 47 | They must be encoded before transmission and decoded after reception. 48 | For example, this message object: 49 | {method: "GET", action: "http://server/path", parameters: {p: "x y"}} 50 | ... can be transmitted as an HTTP request that begins: 51 | GET /path?p=x%20y HTTP/1.0 52 | (This isn't a valid OAuth request, since it lacks a signature etc.) 53 | Note that the object "x y" is transmitted as x%20y. To encode 54 | parameters, you can call OAuth.addToURL, OAuth.formEncode or 55 | OAuth.getAuthorization. 56 | 57 | This message object model harmonizes with the browser object model for 58 | input elements of an form, whose value property isn't percent encoded. 59 | The browser encodes each value before transmitting it. For example, 60 | see consumer.setInputs in example/consumer.js. 61 | */ 62 | 63 | /* This script needs to know what time it is. By default, it uses the local 64 | clock (new Date), which is apt to be inaccurate in browsers. To do 65 | better, you can load this script from a URL whose query string contains 66 | an oauth_timestamp parameter, whose value is a current Unix timestamp. 67 | For example, when generating the enclosing document using PHP: 68 | 69 | -------------------------------------------------------------------------------- /login_popup.js: -------------------------------------------------------------------------------- 1 | function usrpwd(){ 2 | var up = {}; 3 | up.usr = document.login_form.usr.value.trim(); 4 | up.pwd = document.login_form.pwd.value.trim(); 5 | up.keep = document.login_form.loginkeeping.checked; 6 | chrome.tabs.getCurrent(function(tab){ 7 | up.tid=tab.id; 8 | chrome.extension.sendRequest(up); 9 | }); 10 | } 11 | 12 | document.addEventListener('DOMContentLoaded', function () { 13 | document.login_form.addEventListener('submit',usrpwd); 14 | document.login_form.cancel.addEventListener('click',function(){window.close.call(window)}); 15 | }); -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Cloud Save", 4 | "version": "1.4.5", 5 | "description": "Save to cloud.", 6 | "background": { 7 | "page": "cloudsave.html" 8 | }, 9 | "permissions": [ 10 | "*://*/", 11 | "tabs", 12 | "contextMenus", 13 | "unlimitedStorage", 14 | "notifications" 15 | ], 16 | "options_page": "settings.html", 17 | "icons": { 18 | "16": "icon/16.png", 19 | "32": "icon/32.png", 20 | "48": "icon/48.png", 21 | "64": "icon/64.png", 22 | "128": "icon/128.png", 23 | "512": "icon/512.png" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 27 | 28 | 29 |

30 | Starting to save file... 31 |

32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /setting.js: -------------------------------------------------------------------------------- 1 | function toggle_additional(state){ 2 | localStorage.additional = state ? 'yes': ''; 3 | chrome.extension.getBackgroundPage().install_additional(state); 4 | } 5 | document.getElementById('moar').checked = localStorage.additional=='yes' 6 | 7 | function upload(files){ 8 | for(var i = 0; i < files.length; i++){ 9 | var url, file = files[i]; 10 | if(window.createObjectURL){ 11 | url = window.createObjectURL(file) 12 | }else if(window.createBlobURL){ 13 | url = window.createBlobURL(file) 14 | }else if(window.URL && window.URL.createObjectURL){ 15 | url = window.URL.createObjectURL(file) 16 | }else if(window.webkitURL && window.webkitURL.createObjectURL){ 17 | url = window.webkitURL.createObjectURL(file) 18 | } 19 | chrome.extension.getBackgroundPage().upload(document.getElementById('hostselect').value, url, file.name); 20 | } 21 | } 22 | 23 | document.addEventListener('DOMContentLoaded', function (){ 24 | var titles = chrome.extension.getBackgroundPage().title_map; 25 | for(var host in titles){ 26 | var opt = document.createElement('option'); 27 | opt.innerHTML = titles[host]; 28 | opt.value = host; 29 | document.getElementById('hostselect').appendChild(opt); 30 | } 31 | 32 | document.querySelector("#moar").addEventListener('change', function(){toggle_additional(this.checked)}); 33 | document.querySelector("#file").addEventListener('change', function(){upload(this.files)}); 34 | }); -------------------------------------------------------------------------------- /settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cloud Save Settings 5 | 6 | 15 | 16 | 17 |

Cloud Save Settings

18 |

19 | Please excuse the fact that this settings page is mostly empty. 20 |

21 |

22 | 23 |

24 |

25 | Local File Upload (Beta): 26 |

27 |

28 | 29 | 30 |

31 | 32 |
33 | 34 |
35 | Part of the Cloud Save project, 36 | and check here for the latest updates. 37 | Authors: @antimatter15 and Chao. 38 | Email comments and concerns to mister.huangchao@gmail.com. 39 |
40 | 41 | 42 | 43 | 44 | 45 | --------------------------------------------------------------------------------