Flickr Search Tagging

By dariusz Last update Dec 2, 2008 — Installed 224 times.

There are 4 previous versions of this script.

// Flickr Search Tagging
// (c) Dariusz Grabka 2008
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// It allows the user to propose tags to the authors of other images.
// It makes it a little easier by keeping around your last 1 - 4 queries,
// just in case you forgot what you were looking for :)
//
// This is part of a Master's thesis. All rights are strictly reserved.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "Flickr Searching Tagging", and click Uninstall.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name          Flickr Search Tagging
// @namespace     http://grabka.org/projects/searchtagging/
// @description   Tag anyone's photos, with a little help from your search terms.
// @include       http://*.flickr.com/*
// @include       http://flickr.com/*
// ==/UserScript==

// translate-able text

var t = new Object(); t.en = new Object();
t.en.tags_title = "Proposed Tags";
t.en.searchtags_title = "Recent Searches";
t.en.duplicate_error = "Looks like one of the tags you proposed is a duplicate.";
t.en.spam_error = "Oops, you've exceeded the number of tags you can add in one day. Try again tomorrow :).";
t.en.generic_error = "Oops, an unexpected error occurred.";

// set the language

var lang = t.en;

// the url which is called for AJAX stuff

var fst_ajax_url = "http://grabka.org/projects/searchtagging/";

/*
	===============
	START FUNCTIONS
	===============
*/


// returns true if the current user can tag the photo being viewed

function canTag () {

// for the experiment, this is always false;
return false;
// ----

		if (document.getElementById("addtagbox"))	{
			return true;
		} else {
			return false;
		}
}

// returns at tag text link for the given tag text
// will add a delete and a [use] link if the user can tag the photo

function buildTagLink(txt, can_tag) {

	var etxt = escape(txt.replace(" ", "", "g"));

	var tag = "<a href=\"/photos/tags/" + etxt + "/\" title=\"Click this icon to see other photos and videos tagged with "+ txt +"\" class=\"globe\" onMouseOver=\"this.childNodes[0].src='http://l.yimg.com/www.flickr.com/images/icon_globe_over.gif';\" onMouseOut=\"this.childNodes[0].src='http://l.yimg.com/www.flickr.com/images/icon_globe.gif';\"><img src=\"http://l.yimg.com/www.flickr.com/images/icon_globe.gif\" width=\"16\" height=\"16\" class=\"icon\" alt=\"Click this icon to see all public photos and videos tagged with " + txt + "\" /></a> <a href=\"" + user + "tags/" + etxt +"/\" class=\"Plain\">" + txt + "</a>";

	if (can_tag) {
		tag = tag + "&nbsp;<a href=\"#\" onClick=\"javascript:proposeTerm(\'"+txt+"\'); return false;\" class=\"Grey\">[use]</a>&nbsp;<a href=\"" + fst_ajax_url + "delete/" + p_id + "/" + etxt + "\" title=\"Delete this tag?\" class=\"Grey\">[x]</a>";
	}

	return tag;	
}

// gup - gets the value of a variable from the URL
// courtesy of http://www.netlobo.com/url_query_string_javascript.html

function gup( name ) {
	
	name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
	var regexS = "[\\?&]"+name+"=([^&#]*)";
	var regex = new RegExp( regexS );
	var results = regex.exec( window.location.href );
	
	if( results == null )
		return "";
	else
		return results[1];
}

// insertAfter .. I can't believe this doesn't exist in javascript

function insertAfter(new_node, existing_node) {
	// if the existing node has a following sibling, insert the current
	// node before it. otherwise appending it to the parent nodeis_
	// will correctly place it just after the existing node.

	if (existing_node.nextSibling) {
	// there is a next sibling. insert before it using the mutual
	// parent's insertBefore() method.
	existing_node.parentNode.insertBefore(new_node, existing_node.nextSibling);
	} else {
	// there is no next sibling. append to the end of the parent's
	// node list.
	existing_node.parentNode.appendChild(new_node);
	}

} // insertAfter()

// build the display for the search tags
function buildSearchTags() {

	var str = "<style>.fst_query1 a { font-size: 1.5em; text-decoration: none; } .fst_query2 a { font-size: 1.2em; text-decoration: none; } .fst_query3 a { font-size: 1.0em; text-decoration: none; } .fst_query4 a { font-size: 0.8em; text-decoration: none; }</style>";

	var term;
	var terms = GM_getValue("query1", "");

	// if there is nothing in the first query string, then there is nothing to display ... probably :)
	if (terms.length < 1)
	{
		return "<div>No recent searches.</div>";
	}

	var terms_array = terms.split(",");
	for each (term in terms_array) {
		str = str + "<span class=\"fst_query1\"><a href=\"#\" onClick=\"javascript:proposeTerm(\'" + term + "\'); return false;\">" + term + "</a></span> ";
	}

	terms = GM_getValue("query2", "");
	terms_array = terms.split(",");
	for each (term in terms_array) {
		str = str + "<span class=\"fst_query2\"><a href=\"#\" onClick=\"javascript:proposeTerm(\'" + term + "\'); return false;\">" + term + "</a></span>&nbsp;";
	}

	terms = GM_getValue("query3", "");
	terms_array = terms.split(",");
	for each (term in terms_array) {
		str = str + "<span class=\"fst_query3\"><a href=\"#\" onClick=\"javascript:proposeTerm(\'" + term + "\'); return false;\">" + term + "</a></span> ";
	}

	terms = GM_getValue("query4", "");
	terms_array = terms.split(",");
	for each (term in terms_array) {
		str = str + "<span class=\"fst_query4\"><a href=\"#\" onClick=\"javascript:proposeTerm(\'" + term + "\'); return false;\">" + term + "</a></span> ";
	}

	return  str;

}

// build the form that has the "propose" text field and button in it

function buildProposalForm() {

	var form = "<form action=\"\" method=\"get\" id=\"proposedtagadderform\" name=\"proposedtagadderform\"><input onblur=\"\" type=\"text\" name=\"pt\" style=\"margin-bottom: 0px; width:150px\" id=\"proposetagbox\" value=\"\"><input type=\"submit\" class=\"SmallButt\" value=\"PROPOSE\"></form>";

	return form;
}

// standardises a raw query from a URL, turns it into a string 

function decodeQuery(q) {

	// decode the query, change it lower case

	q = decodeURI(q);
	q = q.toLowerCase();

	// terms should be separated with +'s

	q = q.replace(" ", "+", "g");

	// find things that are quoted, treat them as one word
	// store them in quoted_tags and nuke 'em

	var quoted_tags = q.match(/\"[\.\:\%a-z0-9\-\+]+\"/g);
	q = q.replace(/\"[\.\:\%a-z0-9\-\+]+\"/g, "", "g");

	// now get the rest of the tags, the ones without quotation marks

	var simple_tags = q.match(/[\.\:\%a-z0-9\-]+/g);

	// store them in a new tag array

	var tag_array = new Array();
	var tag;

	for each (tag in simple_tags) {
		tag_array.push(tag);
	}

	for each (tag in quoted_tags) {
		var x = tag.replace('"', "", 'g'); // strip the quotations marks, don't need them anymore
		tag_array.push(x);
	}

	tag_array.sort(); // sort the array :)

	q = tag_array.toString(); // and make it a string!

	return q;
}

// function to store the query in one of the four storage storage spots in the browser

function storeQuery(q) {

	q = decodeQuery(q);

	GM_log("FST - storing " + q);

	var q1 = GM_getValue("query1");
	if (q1 == q) return;

	var q2 = GM_getValue("query2");
	if (q2 == q) {
		GM_setValue("query1", q);
		GM_setValue("query2", q1);
		return;
	}

	var q3 = GM_getValue("query3");
	if (q3 == q) {
		GM_setValue("query1", q);
		GM_setValue("query2", q1);
		GM_setValue("query3", q2);
		return;
	}

	GM_setValue("query1", q);
	GM_setValue("query2", q1);
	GM_setValue("query3", q2);
	GM_setValue("query4", q3);

}

// a rough sleep function
// courtesy of http://www.phpied.com/sleep-in-javascript/

function sleep(milliseconds) {
  var start = new Date().getTime();
  for (var i = 0; i < 1e7; i++) {
    if ((new Date().getTime() - start) > milliseconds){
      break;
    }
  }
}

/*
	================
	END OF FUNCTIONS 
	================
*/

// get the photo ID, the user id, owner id if any

var p_id = unsafeWindow.page_photo_id;
var user_id = unsafeWindow.global_nsid;
var owner_id;

if (unsafeWindow.page_p)
{
	var owner_url = unsafeWindow.page_p.ownersUrl;
	var url_array = owner_url.split("/");
	var owner_id = url_array[2];
}

if (!user_id) user_id = "0";
if (!owner_id) owner_id = "0";

// create the div container for the whole thing

var fst = document.createElement("div");
fst.id = "fst";

// deal with the incoming tag proposals, if any

var proposing = /\?pt\=/.test(document.URL);
var proposing_done = false;

if (proposing) {

	// make the URL for the proposal
	// get the tags

	var ptags = (gup("pt"));
	ptags = decodeQuery(ptags);

	if (ptags.length > 0) {

		// get all of the search keywords

		var allkeywords = GM_getValue("query1", "") + ";" + GM_getValue("query2", "") + ";" + GM_getValue("query3", "") + ";" + GM_getValue("query4", "");

		// build the URL

		var get_url = fst_ajax_url + "add/" + p_id + "/" + user_id + "/" + ptags + "/" + owner_id + "/" + allkeywords;
		
		GM_xmlhttpRequest({
		method: 'GET',
		url: get_url,
		headers: {
		'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
		'Accept': 'application/json',
		},

		onload: function(responseDetails) {

			// once we get here, proposing is "done" on the server side

			proposing_done = true;

			// if the response from the server was ok ..

			if (responseDetails.status != 200) { GM_log(responseDetails); return; }

			// get the tags out of the response text

			var feedback = eval('(' + responseDetails.responseText + ')');

			// make sure the status returned OK

			if (feedback.status != "ok") {

				var err = document.createElement("div");
				err.id = "error";
				err.className = "Problem_small";

				if (/Duplicate\ entry/.test(feedback.error)) {

					// we could have a duplicate tag being proposed ...

					GM_log("Duplicate tag error");
					err.innerHTML = lang.duplicate_error;
					// not showing the duplicate tag error .. obvious and annoying
					// fst.parentNode.insertBefore(err, fst);

				} else if (/Exceeded\ daily/.test(feedback.error)) {
					
					// or a spam problem ...

					GM_log("Daily tag limit: " + feedback.error);
					err.innerHTML = lang.spam_error;

					// show this error to the user

					fst.parentNode.insertBefore(err, fst);

				} else {

					// report any other error

					GM_log("Tag proposal error: " + feedback.error);
					err.innerHTML = lang.generic_error;
				}

				err.style.marginTop = "10px";
				
			} 
		}
		});
	} // if length of ptags
}

// if this is stuff true, we're on a photo page (probably)

var photo_page = /\/photos\//.test(document.URL);

if (p_id && photo_page) {

	// user, ownership, etc. from the page

	var user = unsafeWindow.page_p.ownersUrl;
	
	// get the tags for the current image	
	// that url should return JSON formatted proposed tags for the given photo id

	var get_url = fst_ajax_url + "get/" + p_id;

	// the proposed tags container

	var proposedTags = document.createElement("div");
	proposedTags.id = "proposedTags";

	// the proposed tags title

	var proposedTagsTitle = document.createElement("h4");
	proposedTagsTitle.id = "proposedTagsTitle";
	proposedTagsTitle.innerHTML = lang.tags_title;

	// add the title as the first thing

	proposedTags.insertBefore(proposedTagsTitle, proposedTags.firstChild);

	// make the ajax call to get the already propsed tags

	GM_xmlhttpRequest({
		method: 'GET',
		url: get_url,
		headers: {
		'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
		'Accept': 'application/json',
		},
		onload: function(responseDetails) {

			if (proposing)
			{
				sleep(500);
			}

			// if the response from the server was ok ..

			if (responseDetails.status != 200) return;

			// get the tags out of the response text

			var photo_tags = eval('(' + responseDetails.responseText + ')');

			// make sure the status returned OK

			if (photo_tags.status != "ok") return;

			if (photo_tags.count > 0) {

				// insert the tags after the H4

				var insertHere = proposedTagsTitle;

				for (var i = 0; i < photo_tags.count; i++) {

					// create a new div element
					var tag = document.createElement("div");
					tag.id = "fsttag" + i;
					tag.innerHTML = buildTagLink(photo_tags.tags[i].text, canTag());

					// append it
					insertAfter(tag, insertHere);
					insertHere = tag;
				}
			}
		}
	});

	// add the proposed tags to the FST block

	fst.insertBefore(proposedTags, fst.firstChild);

	// the form for adding proposed tags

	var proposalForm = document.createElement("div");
	proposalForm.id = "proposalForm";
	proposalForm.innerHTML = buildProposalForm();
	proposalForm.style.marginTop = "10px";

	insertAfter(proposalForm, proposedTags);

	// tags from searches

	var searchTags = document.createElement("div");
	searchTags.id = "searchTags";

	// the title for the search tags

	var searchTagsTitle = document.createElement("h4");
	searchTagsTitle.id = "searchTagsTitle";
	searchTagsTitle.innerHTML = lang.searchtags_title;

	// add the title as the first thing

	searchTags.insertBefore(searchTagsTitle, searchTags.firstChild);

	// append the actual tags

	var searchTagList = document.createElement("div");
	searchTagList.id = "searchTagList";
	searchTagList.style.marginLeft = "20px"; 	
	searchTagList.innerHTML = buildSearchTags();
	insertAfter(searchTagList, searchTagsTitle);

	// add the whole thing after the proposal form

	insertAfter(searchTags, proposalForm);

	// some stuff changes if you can tag a photo

	if (canTag()) {
		proposalForm.style.display = "none";
		unsafeWindow.tagrs_showForm();
		var boxName = "addtagbox";
	} else {
		var boxName = "proposetagbox";
	}

	// add some scripts that we'll need

	var scripts = document.createElement("div");	
	scripts.innerHTML = "<script>function proposeTerm (term) { var inputBox = document.getElementById(\"" + boxName + "\"); inputBox.value = inputBox.value + \" \" + \'\"\' + term + \'\"\'; }</script>";
	insertAfter(scripts, proposalForm);

} // end of "if photo page"

// if there is a q= and a /search/ in the URL, it's probably the result of a search query

var q_matches = /q=/.test(document.URL);
var search_matches = /\/search\//.test(document.URL);
if (q_matches && search_matches) {

	// get the value of q variables, and unescape them

	var query = (gup("q"));
	storeQuery(query);

}

// figure out where to insert this mess we've created

var insertHere = document.getElementById("tagadder");
if (!insertHere) insertHere = document.getElementById("thetags");
if (!insertHere) insertHere = document.getElementById("moderation");

// display the final output here

if (insertHere) { insertAfter(fst, insertHere); }