Skip to content

Instantly share code, notes, and snippets.

@saitamanodoruji
Last active December 13, 2015 17:48
Show Gist options
  • Save saitamanodoruji/4950258 to your computer and use it in GitHub Desktop.
Save saitamanodoruji/4950258 to your computer and use it in GitHub Desktop.
TumblrTagCommand
// ==UserScript==
// @name TumblrTagCommand
// @namespace http://whym.github.com
// @description Minibuffer command for tagging Tumblr posts. Type 'p' to mark/unmark a post and type 'g' to tag. Requires Minibuffer, LDRize & Autopagerize.
// @include http://www.tumblr.com/dashboard*
// @include http://www.tumblr.com/show/*
// @include http://www.tumblr.com/drafts*
// @include http://www.tumblr.com/queue*
// @include http://www.tumblr.com/blog/*
// @include http://*.tumblr.com/tagged/*
// @include http://*.tumblr.com/post/*
// @include http://*.tumblr.com/private/*
// @include http://*.tumblr.com/search/*
// @include http://*.tumblr.com/page/*
// @include http://*.tumblr.com/archive/*
// @include http://*.tumblr.com/
// @run-at document-end
// @grant GM_openInTab
// @version 0.0.10.0
// @update 2013-06-11
// @updateURL https://gist.github.com/saitamanodoruji/4950258/raw/tumblrtagcommand.user.js
// @downloadURL https://gist.github.com/saitamanodoruji/4950258/raw/tumblrtagcommand.user.js
// ==/UserScript==
// origin:
// http://userscripts.org/scripts/show/56547
// most parts are copied from ReblogCommand
// http://coderepos.org/share/browser/lang/javascript/userscripts/reblogcommand.user.js
(function() {
const KEY_TAG = 'g'
const KEY_TAG_ALL = 'G'
const COMMAND_TAG = 'tumblr-tag-add'
const COMMAND_POSTS = 'tumblr-posts'
const ALLOW_OWN_DOMAIN = true
const REGEXP_SEPARATOR = /,/
const SEPARATOR = ','
const FETCH_URL = 'http://www.tumblr.com/svc/post/fetch'
const UPDATE_URL = 'http://www.tumblr.com/svc/post/update'
const DASHBOARD_URL = 'http://www.tumblr.com/dashboard'
var boot = function() {
var $X = sharedObject.Minibuffer.$X
var D = sharedObject.Minibuffer.D
var createDocumentFromString = sharedObject.Minibuffer.createDocumentFromString
// ----------------------------------------------------------------------------
// Tumblr Tag
// ----------------------------------------------------------------------------
var parseTagList = function(args) {
var ret = []
if ( !(args instanceof Array) ) {
args = [args]
}
args.forEach(function(x){
x = (''+x).trim()
if (x.length > 0) {
ret = ret.concat(x.split(REGEXP_SEPARATOR).map(function(x){return x.trim()}))
}
})
return ret
}
Array.prototype.uniq = function() {
var ret = []
var result = this.reduce(function(res, item) {
if (!res.found[item]) {
res.values.push(item)
res.found[item] = 1
ret.push(item)
}
return res
}, { values: [], found: {}})
return ret
}
Array.prototype.remove = function(x) {
var removed = {}
if ( x instanceof Array ) {
x.forEach(function(x){
removed[x] = 1
})
} else {
removed[x] = 1
}
return this.filter(function(item) {
return !removed[item]
})
}
if ( String.prototype.trim == undefined ) {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/, '')
}
}
var isTumblrDashboardURL = function(url) {
return url.match(/^http:\/\/www\.tumblr\.com\/(?:dashboard|blog|show|likes|liked\/by|tagged)/) ? true : false
}
var isTumblrUserURL = function(url) {
return url.match(/^https?:\/\/[^.]+\.tumblr\.com\/post\/(\d+)/) ||
// tumblr allow to use own domain. but this is risky.
// $X('id("tumblr_controls")[self::iframe]', Boolean)
(ALLOW_OWN_DOMAIN && url.match(/^https?:\/\/[^\/]+\/post\/(\d+)/)) ? true : false
}
// copy from tombloo
var unescapeHTML = function(s) {
return s.replace(/"/g, '"')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
}
var getPIDAndChannelID = function(url) {
url = unescapeHTML(url)
if (/&pid=([^&]*)&.*&name=([^&"]*)/.test(url))
return {
post_id: RegExp.$1,
channel_id: RegExp.$2
}
}
var createToken = function(doc) {
var tumblr_ctrls = doc.querySelector('#tumblr_controls')
if (tumblr_ctrls) { return getPIDAndChannelID(tumblr_ctrls.src) }
}
var getTokenFromPermalink = function(url) {
var d = D(),
gtfpd = d.xhttp.get(url).
next(function(res) {
var doc = createDocumentFromString(res.responseText)
return createToken(doc)
}).
error(function(e) {
var message = e.message || e
if (confirm(message + '\nfailed to get reblog key.\nopen the post page ? \n' + url))
GM_openInTab(url, true)
})
return gtfpd
}
var createUpdateData = function(res_fetch, token) {
var data_update = {
form_key: token.form_key,
channel_id: token.channel_id,
post_id: token.post_id,
edit: true,
silent: true,
context_id: ''
}
for (var n in res_fetch) { data_update[n] = res_fetch[n] }
for (var n in data_update.post) { data_update['post[' + n + ']'] = data_update.post[n] }
data_update['post[state]'] = data_update['post[state]'].toString()
if (data_update.post.type == 'photo') {
// photos
data_update.MAX_FILE_SIZE = '10485760'
if (data_update.post.photos && data_update.post.photos.length) {
var photoset_order = []
data_update.post.photos.forEach(function(p) {
data_update['images[' + p.id + ']'] = ''
photoset_order.push(p.id)
})
data_update['post[photoset_order]'] = photoset_order.join(',')
}
delete data_update.post
} else if (data_update.post.type == 'audio') {
// audios
data_update.album_art = data_update.post.audio_artwork
data_update.pre_upload = ''
data_update.preupload_url = ''
data_update.remove_album_art = ''
data_update.MAX_FILE_SIZE = '10485760'
for (var n in data_update['post[id3_tags]']) {
data_update['id3_tags[' + n.toLowerCase() + ']'] = data_update['post[id3_tags]'][n]
}
} else if (data_update.post.type == 'video') {
// videos
data_update.pre_upload = ''
data_update.preupload_url = ''
data_update.preupload_ch = ''
data_update.MAX_FILE_SIZE = '10485760'
if (data_update.post.is_direct_video == false) {
data_update.valid_embed_code = '1'
} else if (data_update.post.is_direct_video == true) {
data_update.valid_embed_code = ''
data_update.keep_video = '1'
}
}
return data_update
}
var getTokens = function(stdin, deferred, message) {
var getTokensd
if (isTumblrDashboardURL(window.location.href)
&& stdin.every(function(s) { return s.nodeName == 'DIV' })) {
getTokensd = deferred.next(function() {
var tokens = {}
stdin.forEach(function(s, i) {
tokens[i] = {
post_id: s.getAttribute('data-post-id'),
channel_id: s.getAttribute('data-tumblelog-name') ||
document.querySelector('#search_form input[name="t"]').value
}
sharedObject.Minibuffer.status(COMMAND_TAG + tokens[i].post_id, message)
})
return tokens
})
} else if (isTumblrUserURL(window.location.href) && !stdin.length) {
var tokens = { 0: createToken(document)}
sharedObject.Minibuffer.status(COMMAND_TAG + tokens[0].post_id, message)
getTokensd = deferred.next(function() { return tokens })
} else {
var urls = []
if (stdin.every(function(s) { return s.nodeName == 'A' } )) {
// pinned-or-current-link
urls = stdin.map(function(s) { return s.href })
} else if (stdin.every(function(s) { return typeof s == 'string' })) {
// location
urls = stdin
}
urls = urls.filter(isTumblrUserURL)
urls.forEach(function(url) {
sharedObject.Minibuffer.status(COMMAND_TAG + url.match(/\/post\/(\d+)/)[1], message)
})
getTokensd = deferred.parallel(urls.map(getTokenFromPermalink))
}
return getTokensd
}
var getFormKey = function(deferred, tokens) {
var getFormKeyd
if(isTumblrDashboardURL(window.location.href)) {
getFormKeyd = deferred.next(function() {
return document.body.getAttribute('data-form-key')
})
} else {
getFormKeyd = deferred.next(function() {
return deferred.xhttp.get(DASHBOARD_URL)
}).
next(function(res) {
return res.responseText.match(/data-form-key="([^"]+)"/)[1]
}).
error(function(e) {
var message = e.message || e
edit_urls = tokens.map(function(t) {
return 'http://www.tumblr.com/edit/' + t.post_id + '?redirect_to=%2Fdashboard%2F2%2F' + (parseInt(t.post_id) + 1)
})
if (confirm(message + '\nfailed to get form_key.\nedit ' + tokens.length + ' post(s) manually ? \n' + edit_urls.join('\n'))) {
edit_urls.forEach(function(url) { GM_openInTab(url, true) })
}
})
}
return getFormKeyd
}
var edit = function(token, form_key, appends, removes, message) {
token.form_key = form_key
var d = sharedObject.Minibuffer.D(),
editd = d.xhttp.post(FETCH_URL, JSON.stringify(token)).
next(function(res) {
if (res.status == 200) {
if (!res.finalUrl.match(/^http:\/\/www\.tumblr\.com\/svc\/post\/fetch/)) {
return editd.fail('unexpected response (fetch): ' + res.finalUrl)
} else {
var res_fetch = JSON.parse(res.responseText),
data_update = createUpdateData(res_fetch, token)
if (data_update['post[tags]'] === null) {
data_update['post[tags]'] = appends.join(SEPARATOR)
} else {
data_update['post[tags]'] = parseTagList(data_update['post[tags]']).concat(appends).remove(removes).uniq().join(SEPARATOR)
}
return d.xhttp.post(UPDATE_URL, JSON.stringify(data_update))
}
} else {
return editd.fail(res.status + ' in adding tag (fetch)')
}
}).
next(function(res) {
if (res.status == 200) {
if (!res.finalUrl.match(/^http:\/\/www\.tumblr\.com\/svc\/post\/update/)) {
return editd.fail('unexpected response (update): ' + res.finalUrl)
} else {
sharedObject.Minibuffer.status(COMMAND_TAG + token.post_id, message + ' ... done.', 100)
}
} else {
return editd.fail(res.status + ' in adding tag (update)')
}
}).
error(function(e) {
var mes = 'error: ',
edit_url = 'http://www.tumblr.com/edit/' + token.post_id + '?redirect_to=%2Fdashboard%2F2%2F' + (parseInt(token.post_id) + 1)
sharedObject.Minibuffer.status(COMMAND_TAG + token.post_id, message + ' ... error.', 100)
if (typeof(e) == 'object') {
var arr = [e.message]
for (var n in e) { arr.push(n + ' = ' + e[n]) }
mes += arr.join('\n')
} else { mes += e }
if (confirm(mes + '\nedit manually ? \n' + edit_url)) {
GM_openInTab(edit_url, true)
}
})
return d
}
// ----------------------------------------------------------------------------
// Command
// ----------------------------------------------------------------------------
var getTargetCommand = function() {
var target_cmd = ''
if (isTumblrDashboardURL(window.location.href)) {
target_cmd = 'pinned-or-current-node'
} else if (!isTumblrUserURL(window.location.href)) {
target_cmd = 'pinned-or-current-link'
}
return target_cmd
}
sharedObject.Minibuffer.addShortcutkey({
key: KEY_TAG,
description: 'Tag Tumblr posts',
command: function() {
var target_cmd = getTargetCommand(),
clear_pin = '',
tags = prompt('Input new tags and the tags to be deleted.\n(ex. To add \'picture\' and remove \'pict\', use \'picture,-pict\'.)')
if (target_cmd != '') target_cmd = target_cmd + ' | '
clear_pin = (/^pinned-or-current-(?:node|link) | $/.test(target_cmd)) ? ' | clear-pin' : ''
sharedObject.Minibuffer.execute(target_cmd + [COMMAND_TAG, tags].join(' ') + clear_pin)
}
})
sharedObject.Minibuffer.addShortcutkey({
key: KEY_TAG_ALL,
description: 'Tag all Tumblr posts in screen',
command: function() {
var target_cmd = isTumblrDashboardURL(window.location.href) ? 'all-node' : 'tumblr-posts',
tags = prompt('Input new tags and the tags to be deleted.\n(ex. To add \'picture\' and remove \'pict\', use \'picture,-pict\'.)')
sharedObject.Minibuffer.execute(target_cmd + ' | '+[COMMAND_TAG, tags].join(' '))
}
})
sharedObject.Minibuffer.addCommand({
name: COMMAND_POSTS,
command: function(stdin) {
var linkx = sharedObject.LDRize.getSiteinfo().link,
parax = sharedObject.LDRize.getSiteinfo().paragraph,
getfirst = function(x) { return x[0] != undefined ? x[0] : x },
items = $X(parax).map(function(i){ return getfirst($X(linkx, i)) })
return items
}
})
sharedObject.Minibuffer.addCommand({
name: COMMAND_TAG,
command: function(stdin) {
if (!stdin.length) {
if (isTumblrDashboardURL(window.location.href.toString())) {
stdin = sharedObject.Minibuffer.execute('pinned-or-current-node')
} else if (!isTumblrUserURL(window.location.href.toString())) {
stdin = sharedObject.Minibuffer.execute('pinned-or-current-link')
}
}
// edit tags
var args = parseTagList(this.args.join(' '))
if (args.length == 0) {
sharedObject.Minibuffer.status(COMMAND_TAG, 'No tag is specified', 1000)
return null
}
var appends = [], removes = [], message = '', tokens = []
args.forEach(function(x) {
if (x.charAt(0) == '-') {
removes.push(x.substr(1))
} else {
if (x.charAt(0) == '+') x = x.substr(1)
appends.push(x)
}
})
appends = parseTagList(appends).uniq()
removes = parseTagList(removes).uniq()
if (appends.length > 0) {
message += 'Adding '+appends.join(SEPARATOR)
}
if (removes.length > 0) {
message += (message.length > 0 ? ', removing ': 'Removing ') + removes.join(SEPARATOR)
}
var deferred = sharedObject.Minibuffer.D()
var tagd = deferred.next(function() {
return getTokens(stdin, deferred, message)
}).
next(function(values) {
for (var i in values) { tokens[i] = values[i] }
if (tokens.length > 1) sharedObject.Minibuffer.status(COMMAND_TAG, 'Everything is OK', 1000)
return getFormKey(deferred, tokens)
}).
next(function(form_key) {
deferred.parallel(tokens.map(function(token) {
return edit(token, form_key, appends, removes, message)
}))
}).
error(function(e) {
var message = [e.message || e]
if (typeof(e) == 'object') {
for ( var n in e ) { message.push(n + ': ' + e[n]) }
}
alert(message.join('\n'))
})
return stdin
}
})
}
if (sharedObject.Minibuffer) {
boot()
} else {
window.addEventListener('GM_MinibufferLoaded', boot, false)
}
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment