<commit_before>{
	"translatorID":"d3b1d34c-f8a1-43bb-9dd6-27aa6403b217",
	"translatorType":4,
	"label":"YouTube",
	"creator":"Sean Takats and Michael Berkowitz",
	"target":"https?://[^/]*youtube\\.com\\/",
	"minVersion":"1.0.0rc4",
	"maxVersion":"",
	"priority":100,
	"inRepository":true,
	"lastUpdated":"2008-03-30 08:30:00"
}

function detectWeb(doc, url){
	var namespace = doc.documentElement.namespaceURI;
	var nsResolver = namespace ? function(prefix) {
			if (prefix == 'x') return namespace; else return null;
		} : null;

	var xpath = '//input[@type="hidden" and @name="video_id"]';
	if(doc.evaluate(xpath, doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()) {
		return "videoRecording";
	}
	if (doc.evaluate('//div[@class="vtitle"]/a[@class="vtitlelink" and contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
		return "multiple";
	}
	if (doc.evaluate('//div[starts-with(@class, "vtitle")]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
		return "multiple";
	}
	if (doc.evaluate('//div[@class="vltitle"]/div[@class="vlshortTitle"]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
		return "multiple";
	}
}

function doWeb(doc, url){
	var namespace = doc.documentElement.namespaceURI;
	var nsResolver = namespace ? function(prefix) {
			if (prefix == 'x') return namespace; else return null;
		} : null;
	var host = doc.location.host;
	var video_ids = new Array();
	var xpath = '//input[@type="hidden" and @name="video_id"]';
	var elmts;
	var elmt;
	elmts = doc.evaluate(xpath, doc, nsResolver, XPathResult.ANY_TYPE, null);
	elmt = elmts.iterateNext();
	if(elmt) {
		//single video
		var video_id = elmt.value;
		video_ids.push(video_id);
	} else {
		// multiple videos
		var items = new Object();
		var videoRe = /\/watch\?v=([a-zA-Z0-9-_]+)/;
// search results
		if (elmt = doc.evaluate('//div[@class="vtitle"]/a[@class="vtitlelink" and contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
			elmts = doc.evaluate('//div[@class="vtitle"]/a[@class="vtitlelink" and contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null);
// categories and community pages and user pages and browse pages
		} else if (doc.evaluate('//div[starts-with(@class, "vtitle")]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
			elmts = doc.evaluate('//div[starts-with(@class, "vtitle")]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null);
		} else if (doc.evaluate('//div[@class="vltitle"]/div[@class="vlshortTitle"]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
			elmts = doc.evaluate('//div[@class="vltitle"]/div[@class="vlshortTitle"]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null);
		}
		while (elmt = elmts.iterateNext()){
			var title = elmt.textContent;
			title = Zotero.Utilities.trimInternal(title);
			var link = elmt.href;
			var m = videoRe(link);
			var video_id = m[1];
			items[video_id] = title;
		}
		items = Zotero.selectItems(items);
		if(!items) return true;
		for(var i in items) {
			video_ids.push(i);
		}
	}
	getData(video_ids, host);
}

function getData(ids, host){
	var uris = new Array();
	var url = "http://gdata.youtube.com/feeds/videos/";
	for each(var id in ids){
		uris.push(url+id);
	}
	Zotero.Utilities.HTTP.doGet(uris, function(text) {
		// clean up header
		text = text.replace(/<\?xml[^>]*\?>/, "");
		text = text.replace(/<entry[^>]*>/, "<entry>");
		// replace colons in XML tags
		text = text.replace(/<media:/g, "<media_").replace(/<\/media:/g, "</media_");
//		text = text.replace(/<yt:/g, "<yt_").replace(/<\/yt:/g, "</yt_");
		text = text.replace(/yt:/g, "yt_");
		text = text.replace(/<gd:/g, "<gd_").replace(/<\/gd:/g, "</gd_");
		text = text.replace(/<\/?(georss|gml)[^>]+>/g, "");
		// pad xml
		text = "<zotero>"+text+"</zotero>";
		var xml = new XML(text);
		var newItem = new Zotero.Item("videoRecording");
		var title = "";
		var title = xml..media_title[0].text().toString();
		if (xml..media_title.length()){
			var title = Zotero.Utilities.cleanString(xml..media_title[0].text().toString());
			if (title == ""){
				title = " ";
			}
			newItem.title = title;
		}
		if (xml..media_keywords.length()){
			var keywords = xml..media_keywords[0].text().toString();
			keywords = keywords.split(",");
			for each(var tag in keywords){
				newItem.tags.push(Zotero.Utilities.trimInternal(tag));
			}
		}
		if (xml..published.length()){
			var date = xml..published[0].text().toString();
			newItem.date = date.substr(0, 10);
		}
		if (xml..author.name.length()){
			var author = xml..author.name[0].text().toString();
			newItem.creators.push(Zotero.Utilities.cleanAuthor(author, "contributor", true));
		}
		if (xml..media_player.length()){
			var url = xml..media_player[0].@url.toString();
			newItem.url = url;
			newItem.attachments.push({title:"YouTube Link", snapshot:false, mimeType:"text/html", url:url});
		}
		if (xml..yt_duration.length()){
			var runningTime = xml..yt_duration[0].@seconds.toString();
			newItem.runningTime = runningTime + " seconds";
		}
		if (xml..media_description.length()){
			newItem.abstractNote = xml..media_description[0].text().toString();
		}

		var next_url = newItem.url.replace(/\/\/([^/]+)/, "//" + host).replace("watch?v=", "v/") + '&rel=1';
		Zotero.Utilities.loadDocument(next_url, function(newDoc) {
			var new_url = newDoc.location.href.replace("swf/l.swf", "get_video");
			newItem.attachments.push({url:new_url, title:"YouTube Video Recording", mimeType:"video/x-flv"});
			newItem.complete();
		}, function() {Zotero.done;});
	});
	Zotero.wait();
}<commit_msg>Fixes YouTube attachments<commit_after>{
	"translatorID":"d3b1d34c-f8a1-43bb-9dd6-27aa6403b217",
	"translatorType":4,
	"label":"YouTube",
	"creator":"Sean Takats and Michael Berkowitz",
	"target":"https?://[^/]*youtube\\.com\\/",
	"minVersion":"1.0.0rc4",
	"maxVersion":"",
	"priority":100,
	"inRepository":true,
	"lastUpdated":"2008-10-29 17:00:00"
}

function detectWeb(doc, url){
	var namespace = doc.documentElement.namespaceURI;
	var nsResolver = namespace ? function(prefix) {
			if (prefix == 'x') return namespace; else return null;
		} : null;

	var xpath = '//input[@type="hidden" and @name="video_id"]';
	if(doc.evaluate(xpath, doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()) {
		return "videoRecording";
	}
	if (doc.evaluate('//div[@class="vtitle"]/a[@class="vtitlelink" and contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
		return "multiple";
	}
	if (doc.evaluate('//div[starts-with(@class, "vtitle")]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
		return "multiple";
	}
	if (doc.evaluate('//div[@class="vltitle"]/div[@class="vlshortTitle"]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
		return "multiple";
	}
}

function doWeb(doc, url){
	var namespace = doc.documentElement.namespaceURI;
	var nsResolver = namespace ? function(prefix) {
			if (prefix == 'x') return namespace; else return null;
		} : null;
	var host = doc.location.host;
	var video_ids = new Array();
	var xpath = '//input[@type="hidden" and @name="video_id"]';
	var elmts;
	var elmt;
	elmts = doc.evaluate(xpath, doc, nsResolver, XPathResult.ANY_TYPE, null);
	elmt = elmts.iterateNext();
	if(elmt) {
		//single video
		var video_id = elmt.value;
		video_ids.push(video_id);
	} else {
		// multiple videos
		var items = new Object();
		var videoRe = /\/watch\?v=([a-zA-Z0-9-_]+)/;
// search results
		if (elmt = doc.evaluate('//div[@class="vtitle"]/a[@class="vtitlelink" and contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
			elmts = doc.evaluate('//div[@class="vtitle"]/a[@class="vtitlelink" and contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null);
// categories and community pages and user pages and browse pages
		} else if (doc.evaluate('//div[starts-with(@class, "vtitle")]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
			elmts = doc.evaluate('//div[starts-with(@class, "vtitle")]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null);
		} else if (doc.evaluate('//div[@class="vltitle"]/div[@class="vlshortTitle"]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null).iterateNext()){
			elmts = doc.evaluate('//div[@class="vltitle"]/div[@class="vlshortTitle"]/a[contains(@href, "/watch?v=")]', doc, nsResolver, XPathResult.ANY_TYPE, null);
		}
		while (elmt = elmts.iterateNext()){
			var title = elmt.textContent;
			title = Zotero.Utilities.trimInternal(title);
			var link = elmt.href;
			var m = videoRe(link);
			var video_id = m[1];
			items[video_id] = title;
		}
		items = Zotero.selectItems(items);
		if(!items) return true;
		for(var i in items) {
			video_ids.push(i);
		}
	}
	getData(video_ids, host);
}

function getData(ids, host){
	var uris = new Array();
	var url = "http://gdata.youtube.com/feeds/videos/";
	for each(var id in ids){
		uris.push(url+id);
	}
	Zotero.Utilities.HTTP.doGet(uris, function(text) {
		// clean up header
		text = text.replace(/<\?xml[^>]*\?>/, "");
		text = text.replace(/<entry[^>]*>/, "<entry>");
		// replace colons in XML tags
		text = text.replace(/<media:/g, "<media_").replace(/<\/media:/g, "</media_");
//		text = text.replace(/<yt:/g, "<yt_").replace(/<\/yt:/g, "</yt_");
		text = text.replace(/yt:/g, "yt_");
		text = text.replace(/<gd:/g, "<gd_").replace(/<\/gd:/g, "</gd_");
		text = text.replace(/<\/?(georss|gml)[^>]+>/g, "");
		// pad xml
		text = "<zotero>"+text+"</zotero>";
		var xml = new XML(text);
		var newItem = new Zotero.Item("videoRecording");
		var title = "";
		var title = xml..media_title[0].text().toString();
		if (xml..media_title.length()){
			var title = Zotero.Utilities.cleanString(xml..media_title[0].text().toString());
			if (title == ""){
				title = " ";
			}
			newItem.title = title;
		}
		if (xml..media_keywords.length()){
			var keywords = xml..media_keywords[0].text().toString();
			keywords = keywords.split(",");
			for each(var tag in keywords){
				newItem.tags.push(Zotero.Utilities.trimInternal(tag));
			}
		}
		if (xml..published.length()){
			var date = xml..published[0].text().toString();
			newItem.date = date.substr(0, 10);
		}
		if (xml..author.name.length()){
			var author = xml..author.name[0].text().toString();
			newItem.creators.push(Zotero.Utilities.cleanAuthor(author, "contributor", true));
		}
		if (xml..media_player.length()){
			var url = xml..media_player[0].@url.toString();
			newItem.url = url;
			newItem.attachments.push({title:"YouTube Link", snapshot:false, mimeType:"text/html", url:url});
		}
		if (xml..yt_duration.length()){
			var runningTime = xml..yt_duration[0].@seconds.toString();
			newItem.runningTime = runningTime + " seconds";
		}
		if (xml..media_description.length()){
			newItem.abstractNote = xml..media_description[0].text().toString();
		}

//temporary fix for downloads using techcrunch
		var techcrunchurl = "http://www.techcrunch.com/ytdownload3.php?url="+encodeURIComponent(newItem.url)+"&submit=Get+Video";
		Zotero.Utilities.HTTP.doGet(techcrunchurl, function(text) {
			var flv = text.match(/HREF='([^']+)'/);
			if (flv[1]){
				flv = flv[1];
				// title parameter needs to be encoded
				var title = flv.match(/&title=([^&]+)/);
				if (title[1]){
					title = encodeURIComponent(title[1]);
					flv = flv.replace(/&title=([^&]+)/, title);
				}
				newItem.attachments.push({url:flv, title:"YouTube Video Recording", mimeType:"video/x-flv"});
			}
			newItem.complete();
		}, function() {Zotero.done;});
	});
	Zotero.wait();
}
}<|endoftext|>


<commit_before>"use strict";
/*jslint vars: true, plusplus: true, devel: true, nomen: true*/
/*global define */

var game_state = {};

function a_execute() {
    if (game_state.options.a === "Play again.") {
        request_initial_world();
    } else {
        request_outcome_of_action(game_state.options.a);
    }
}

function a_or_an(next_letter) {
    if (next_letter === "a" ||
        next_letter === "e" ||
        next_letter === "i" ||
        next_letter === "o" ||
        next_letter === "u") {
        return "an";
    }
    return "a";
}

function b_execute() {
    if (game_state.options.b === "Don't play again.") {
        window.open("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
    } else {
        request_outcome_of_action(game_state.options.b);
    }
}

function bind_keys() {
    document.onkeypress = function(event) {
        var ascii = event.which;
        if (ascii == 49 || ascii == 65 || ascii == 97) {
            a_execute();
        } else if (ascii == 50 || ascii == 66 || ascii == 98) {
            b_execute();
        } else if (ascii == 51 || ascii == 67 || ascii == 99 &&
                   game_state.options.c !== "") {
            execute("c");
        } else if (ascii == 52 || ascii == 68 || ascii == 100 &&
                   game_state.options.d !== "") {
            execute("d");
        } else if (ascii == 53 || ascii == 69 || ascii == 101 &&
                   game_state.options.e !== "") {
            execute("e");
        }
    };
}

function execute(letter) {
    request_outcome_of_action(game_state.options[letter]);
}

function get_person_name(game_state) {
    return game_state.persons[game_state.character.person].name;
}

function get_place_name(game_state) {
    return game_state.places[game_state.character.place].name;
}

function handle_new_world(resp) {
    if (resp.message === "error") {
        handle_world_error(game_state);
    } else {
        game_state = resp;

        if (game_state.character.is_dead) {
            game_state.message += " You are dead.";
        }

        set_message(game_state);
        set_a(game_state);
        set_b(game_state);
        set_c(game_state);
        set_d(game_state);
        set_e(game_state);
        $("#score").text("Score: " + game_state.score);

        if (parseInt(game_state.score) === 0) {
        // we only need to bind the keys if this is the first frame of the game
        // and the player has a score of 0 at the first frame of the game

        // I am binding the keys at the end of the handle_new_world function
        // to prevent the client from being able to make a bad server request
        // before the server has given the client the initial game state
            bind_keys();
        }
    }
}

function handle_world_error(game_state) {
    $("#message").text("The universe rips apart and everyone dies.");
    $("#a").text("Play again.");
    $("#b").text("Don't play again.");
    $("#c").text("");
    $("#d").text("");
    $("#e").text("");
    game_state.options.a = "Play again.";
    game_state.options.b = "Don't play again.";
    game_state.options.c = "";
    game_state.options.d = "";
    game_state.options.e = "";
}

function random_choice(array) {
    return array[random_int(array.length)];
}

function random_int(n) {
    return Math.floor(Math.random() * n);
}

function request_initial_world() {
    $.get("request_initial_world.json", {}, handle_new_world);
}

function request_outcome_of_action(action) {
    game_state.action = action;
    $.get("request_outcome_of_action.json",
           game_state,
           handle_new_world);
}

// CREDITS: function based on top answer by community wiki at:
// http://stackoverflow.com/questions/2450954/
//        how-to-randomize-shuffle-a-javascript-array
function scramble(text) {
    text = text.replace(/\./g, "");
    text = text.replace(/,/g, "");
    text = text.replace(/\?/g, "");
    text = text.replace(/!/g, "");
    text = text.replace(/"/g, "");

    text = text.toLowerCase();
    var array = text.split(" ");
    var current_index = array.length, temporary_value, random_index ;

    while (0 !== current_index) {
        random_index = Math.floor(Math.random() * current_index);
        current_index -= 1;
        temporary_value = array[current_index];
        array[current_index] = array[random_index];
        array[random_index] = temporary_value;
    }

    var return_text = "";
    for (var i = 0; i < array.length; i++) {
        return_text += array[i] + " ";
    }

    return return_text;
}

function set_a(game_state) {
    switch (game_state.options.a) {
        case "ATTACK":
            $("#a").text("a. Attack " + get_person_name(game_state) + ".");
            break;

        case "GIVE_HER_CAT":
            var cat = "your cat";
            if (game_state.character.items.cat > 1) {
                cat = "one of your cats";
            }
            $("#a").text("a. Give " + get_person_name(game_state) + cat + ".");
            break;

        case "LICK_THE_GROUND":
            if (game_state.places[
                    game_state.character.place
                ].outside === true) {
                $("#a").text("a. Lick the ground.");
            } else {
                $("#a").text("a. Lick the floor.");
            }
            break;

        case "MARRY":
            $("#a").text("a. Marry " + get_person_name(game_state) + ".");
            break;

        case "Think.":
            if (game_state.action === "Think.") {
                $("#a").text("a. Think some more.");
            } else {
                $("#a").text("a. Think.");
            }
            break;

        case "YELL_A_PIRATE_PHRASE":
            $("#a").text("a. Yell, \"" +
                random_choice([
                    "Ahoy",
                    "All hands on deck",
                    "Arr Matey",
                    "Avast",
                    "Aye Aye",
                    "Dead men tell no tales",
                    "Hoist the Jolly Roger",
                    "Land ho",
                    "Send 'em to Davy Jones' locker",
                    "Shiver me timbers",
                    "Thare she blows",
                    "Walk the plank",
                    "X marks the spot",
                    "Yo, ho, ho, and a bottle of rum",
                ]) + "!\"");
            break;

        default:
            $("#a").text("a. " + game_state.options.a);
    }
}

function set_b(game_state) {
    switch (game_state.options.b) {
        case "BURN":
            $("#b").text("b. Burn " +  get_place_name(game_state) +
                " to the ground.");
            break;

        case "BUY_ITEM":
            var item = game_state.for_sell;
            $("#b").text("b. Buy " + a_or_an(item[0]) + " " + item + ".");
            break;

        case "GIVE_FLOWERS":
            if (game_state.character.place === "prison") {
                if (game_state.persons.felicity.name === "the fat lady") {
                    $("#b").text("b. Give the fat lady who feeds you a " +
                        "bouquet of flowers.");
                } else {
                    $("#b").text("b. Give Felicity your bouquet of flowers.");
                }
            } else {
                $("#b").text("b. Give her your bouquet of flowers.");
            }
            break;

        case "LOVE_POTION":
            $("#b").text("b. Use your love potion on " +
                get_person_name(game_state) + ".");
            break;

        case "SHOW_COIN":
            $("#b").text("b. Show " + get_person_name(game_state) +
                " your shiny foreign coin.");
            break;

        case "SUCK_UP":
            $("#b").text("b. Suck up to " + get_person_name(game_state) + ".");
            break;

        default:
            $("#b").text("b. " + game_state.options.b);
    }
}

function set_c(game_state) {
    if (game_state.options.c !== "") {
        switch (game_state.options.c) {
            case "GO_TO":
                var dest = game_state.destination;
                $("#c").text("c. Go to " + game_state.places[dest].name + ".");
                break;

            default:
                $("#c").text("c. " + game_state.options.c);
        }
    } else {
        $("#c").text("");
    }
}

function set_d(game_state) {
    if (game_state.options.d !== "") {
        switch (game_state.options.d) {
            case "BUY_WEAPON":
                var weapon = game_state.for_sell;
                $("#d").text("d. Buy " + a_or_an(weapons_map[weapon][0]) +
                    " " +
                    weapons_map[weapon] + ".");
                break;

            case "FLIRT_WITH":
                $("#d").text("d. Flirt with " +
                    get_person_name(game_state) + ".");
                break;

            case "Go swimming in a pool of lava.":
                if (game_state.outcome === "lava_swim") {
                    $("#d").text("d. Go swimming in another pool of lava.");
                } else {
                    $("#d").text("d. Go swimming in a pool of lava.");
                }
                break;

            case "TELL_GUARDS":
                $("#d").text("d. Tell the guards you're not a lunatic, " +
                    "you're just " + game_state.character.excuse + ".");
                break;

            default:
                $("#d").text("d. " + game_state.options.d);
        }
    } else {
        $("#d").text("");
    }
}

function set_e(game_state) {
    if (game_state.options.e !== "") {
        $("#e").text("e. " + game_state.options.e);
    } else {
        $("#e").text("");
    }
}

function set_message(game_state) {
    if (game_state.character.is_tripping) {
        $("#message").text(scramble(game_state.message));
    } else {
        $("#message").text(game_state.message);
    }
}

var weapons_map = {
    "cutlass": "cutlass",
    "dagger": "dagger",
    "hammer": "hammer",
    "iron_hammer": "iron hammer",
    "jeweled_cutlass": "jeweled cutlass",
    "pitchfork": "pitchfork",
    "long_pitchfork": "long pitchfork",
    "poison_dagger": "poison dagger",
};

$(document).ready(request_initial_world);
<commit_msg>fixed issue where you could send off multiple actions before the server could respond<commit_after>"use strict";
/*jslint vars: true, plusplus: true, devel: true, nomen: true*/
/*global define */

var game_state = {};
var USER_INPUT_ALLOWED = true;

function a_execute() {
    if (game_state.options.a === "Play again.") {
        request_initial_world();
    } else {
        request_outcome_of_action(game_state.options.a);
    }
}

function a_or_an(next_letter) {
    if (next_letter === "a" ||
        next_letter === "e" ||
        next_letter === "i" ||
        next_letter === "o" ||
        next_letter === "u") {
        return "an";
    }
    return "a";
}

function b_execute() {
    if (game_state.options.b === "Don't play again.") {
        window.open("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
    } else {
        request_outcome_of_action(game_state.options.b);
    }
}

function bind_keys() {
    document.onkeypress = function(event) {
        var ascii = event.which;
        if ((ascii == 49 || ascii == 65 || ascii == 97) && USER_INPUT_ALLOWED) {
            a_execute();
        } else if ((ascii == 50 || ascii == 66 || ascii == 98) && USER_INPUT_ALLOWED) {
            b_execute();
        } else if ((ascii == 51 || ascii == 67 || ascii == 99) && USER_INPUT_ALLOWED &&
                   game_state.options.c !== "") {
            execute("c");
        } else if ((ascii == 52 || ascii == 68 || ascii == 100) && USER_INPUT_ALLOWED &&
                   game_state.options.d !== "") {
            execute("d");
        } else if ((ascii == 53 || ascii == 69 || ascii == 101) && USER_INPUT_ALLOWED &&
                   game_state.options.e !== "") {
            execute("e");
        }
    };
}

function execute(letter) {
    request_outcome_of_action(game_state.options[letter]);
}

function get_person_name(game_state) {
    return game_state.persons[game_state.character.person].name;
}

function get_place_name(game_state) {
    return game_state.places[game_state.character.place].name;
}

function handle_new_world(resp) {
    if (resp.message === "error") {
        handle_world_error(game_state);
    } else {
        game_state = resp;

        if (game_state.character.is_dead) {
            game_state.message += " You are dead.";
        }

        set_message(game_state);
        set_a(game_state);
        set_b(game_state);
        set_c(game_state);
        set_d(game_state);
        set_e(game_state);
        $("#score").text("Score: " + game_state.score);

        if (parseInt(game_state.score) === 0) {
        // we only need to bind the keys if this is the first frame of the game
        // and the player has a score of 0 at the first frame of the game

        // I am binding the keys at the end of the handle_new_world function
        // to prevent the client from being able to make a bad server request
        // before the server has given the client the initial game state
            bind_keys();
        }
    }
    USER_INPUT_ALLOWED = true; // re-allow user inputs now that we have a new
                               // game state
}

function handle_world_error(game_state) {
    $("#message").text("The universe rips apart and everyone dies.");
    $("#a").text("Play again.");
    $("#b").text("Don't play again.");
    $("#c").text("");
    $("#d").text("");
    $("#e").text("");
    game_state.options.a = "Play again.";
    game_state.options.b = "Don't play again.";
    game_state.options.c = "";
    game_state.options.d = "";
    game_state.options.e = "";
}

function random_choice(array) {
    return array[random_int(array.length)];
}

function random_int(n) {
    return Math.floor(Math.random() * n);
}

function request_initial_world() {
    $.get("request_initial_world.json", {}, handle_new_world);
}

function request_outcome_of_action(action) {
    game_state.action = action;
    USER_INPUT_ALLOWED = false;
    $.get("request_outcome_of_action.json",
           game_state,
           handle_new_world);
}

// CREDITS: function based on top answer by community wiki at:
// http://stackoverflow.com/questions/2450954/
//        how-to-randomize-shuffle-a-javascript-array
function scramble(text) {
    text = text.replace(/\./g, "");
    text = text.replace(/,/g, "");
    text = text.replace(/\?/g, "");
    text = text.replace(/!/g, "");
    text = text.replace(/"/g, "");

    text = text.toLowerCase();
    var array = text.split(" ");
    var current_index = array.length, temporary_value, random_index ;

    while (0 !== current_index) {
        random_index = Math.floor(Math.random() * current_index);
        current_index -= 1;
        temporary_value = array[current_index];
        array[current_index] = array[random_index];
        array[random_index] = temporary_value;
    }

    var return_text = "";
    for (var i = 0; i < array.length; i++) {
        return_text += array[i] + " ";
    }

    return return_text;
}

function set_a(game_state) {
    switch (game_state.options.a) {
        case "ATTACK":
            $("#a").text("a. Attack " + get_person_name(game_state) + ".");
            break;

        case "GIVE_HER_CAT":
            var cat = "your cat";
            if (game_state.character.items.cat > 1) {
                cat = "one of your cats";
            }
            $("#a").text("a. Give " + get_person_name(game_state) + cat + ".");
            break;

        case "LICK_THE_GROUND":
            if (game_state.places[
                    game_state.character.place
                ].outside === true) {
                $("#a").text("a. Lick the ground.");
            } else {
                $("#a").text("a. Lick the floor.");
            }
            break;

        case "MARRY":
            $("#a").text("a. Marry " + get_person_name(game_state) + ".");
            break;

        case "Think.":
            if (game_state.action === "Think.") {
                $("#a").text("a. Think some more.");
            } else {
                $("#a").text("a. Think.");
            }
            break;

        case "YELL_A_PIRATE_PHRASE":
            $("#a").text("a. Yell, \"" +
                random_choice([
                    "Ahoy",
                    "All hands on deck",
                    "Arr Matey",
                    "Avast",
                    "Aye Aye",
                    "Dead men tell no tales",
                    "Hoist the Jolly Roger",
                    "Land ho",
                    "Send 'em to Davy Jones' locker",
                    "Shiver me timbers",
                    "Thare she blows",
                    "Walk the plank",
                    "X marks the spot",
                    "Yo, ho, ho, and a bottle of rum",
                ]) + "!\"");
            break;

        default:
            $("#a").text("a. " + game_state.options.a);
    }
}

function set_b(game_state) {
    switch (game_state.options.b) {
        case "BURN":
            $("#b").text("b. Burn " +  get_place_name(game_state) +
                " to the ground.");
            break;

        case "BUY_ITEM":
            var item = game_state.for_sell;
            $("#b").text("b. Buy " + a_or_an(item[0]) + " " + item + ".");
            break;

        case "GIVE_FLOWERS":
            if (game_state.character.place === "prison") {
                if (game_state.persons.felicity.name === "the fat lady") {
                    $("#b").text("b. Give the fat lady who feeds you a " +
                        "bouquet of flowers.");
                } else {
                    $("#b").text("b. Give Felicity your bouquet of flowers.");
                }
            } else {
                $("#b").text("b. Give her your bouquet of flowers.");
            }
            break;

        case "LOVE_POTION":
            $("#b").text("b. Use your love potion on " +
                get_person_name(game_state) + ".");
            break;

        case "SHOW_COIN":
            $("#b").text("b. Show " + get_person_name(game_state) +
                " your shiny foreign coin.");
            break;

        case "SUCK_UP":
            $("#b").text("b. Suck up to " + get_person_name(game_state) + ".");
            break;

        default:
            $("#b").text("b. " + game_state.options.b);
    }
}

function set_c(game_state) {
    if (game_state.options.c !== "") {
        switch (game_state.options.c) {
            case "GO_TO":
                var dest = game_state.destination;
                $("#c").text("c. Go to " + game_state.places[dest].name + ".");
                break;

            default:
                $("#c").text("c. " + game_state.options.c);
        }
    } else {
        $("#c").text("");
    }
}

function set_d(game_state) {
    if (game_state.options.d !== "") {
        switch (game_state.options.d) {
            case "BUY_WEAPON":
                var weapon = game_state.for_sell;
                $("#d").text("d. Buy " + a_or_an(weapons_map[weapon][0]) +
                    " " +
                    weapons_map[weapon] + ".");
                break;

            case "FLIRT_WITH":
                $("#d").text("d. Flirt with " +
                    get_person_name(game_state) + ".");
                break;

            case "Go swimming in a pool of lava.":
                if (game_state.outcome === "lava_swim") {
                    $("#d").text("d. Go swimming in another pool of lava.");
                } else {
                    $("#d").text("d. Go swimming in a pool of lava.");
                }
                break;

            case "TELL_GUARDS":
                $("#d").text("d. Tell the guards you're not a lunatic, " +
                    "you're just " + game_state.character.excuse + ".");
                break;

            default:
                $("#d").text("d. " + game_state.options.d);
        }
    } else {
        $("#d").text("");
    }
}

function set_e(game_state) {
    if (game_state.options.e !== "") {
        $("#e").text("e. " + game_state.options.e);
    } else {
        $("#e").text("");
    }
}

function set_message(game_state) {
    if (game_state.character.is_tripping) {
        $("#message").text(scramble(game_state.message));
    } else {
        $("#message").text(game_state.message);
    }
}

var weapons_map = {
    "cutlass": "cutlass",
    "dagger": "dagger",
    "hammer": "hammer",
    "iron_hammer": "iron hammer",
    "jeweled_cutlass": "jeweled cutlass",
    "pitchfork": "pitchfork",
    "long_pitchfork": "long pitchfork",
    "poison_dagger": "poison dagger",
};

$(document).ready(request_initial_world);
<|endoftext|>


<commit_before>"use strict";

var Backbone = require('../backbone')
  , _ = require('underscore')

module.exports = Backbone.View.extend({
  events: {
    'click .delete-scan': 'handleDeleteClick',
    'click .move-scan': 'handleMoveClick',
    'dragover #scan-drop': function (e) { e.preventDefault(); e.stopPropagation(); },
    'dragenter #scan-drop': 'handleDragenter',
    'dragleave #scan-drop': 'handleDragleave',
    'drop #scan-drop.drop-ready': 'handleDrop'
  },
  className: 'scan-container',
  initialize: function () {
    this.render();
    if (this.collection.needsNormalization()) {
      this.collection.normalizeOrderingValues();
    }
    this.listenTo(this.collection, 'add remove sort', this.render);
  },
  render: function () {
    var that = this
      , template = require('../templates/scans.html')

    this.$el.html( template({ scans: that.collection }) );
    this.$dropTarget = this.$('#scan-drop');
    this.refreshScanButtons();
  },
  handleDeleteClick: function (e) {
    e.preventDefault();
    var cid = e.target.dataset.scan
      , scan = this.collection.get(cid)

    scan.destroy();
    this.render();
  },
  handleMoveClick: function (e) {
    var scan = this.collection.get(e.target.dataset.scan)
      , direction = parseInt(e.target.dataset.direction, 10);

    this.collection.move(scan, this.collection.indexOf(scan) + direction);
  },
  refreshScanButtons: function () {
    var $moveBtns = this.$('.move-scan').prop('disabled', false);
    $moveBtns.first().prop('disabled', 'true');
    $moveBtns.last().prop('disabled', 'true');
  },
  getImageFiles: function (fileList) {
    var imageFiles = [];
    for (var i = 0; i < fileList.length; i++) {
      if (fileList[i].type.match('image/')) {
        imageFiles.push(fileList[i]);
      }
    }
    return imageFiles;
  },
  handleDragenter: function (e) {
    e.preventDefault();
    e.stopPropagation();

    var dt = e.originalEvent.dataTransfer;

    if (_.contains(dt.types, 'Files')) {
      this.$dropTarget.addClass('drop-ready').css('background', '#f0f0f0');
    }
  },
  handleDragleave: function () {
    this.$dropTarget.removeClass('drop-ready').css('background', '');
  },
  handleDrop: function (e) {
    e.preventDefault();
    var imageFiles = this.getImageFiles(e.originalEvent.dataTransfer.files);
    this.handleDragleave();

    imageFiles.forEach(function (imageFile) {
      var that = this;
      this.collection
        .add({ image_file: imageFile })
        .save().done(that.render.bind(that));
    }, this);
  }
});
<commit_msg>Fix bug: confused `evt.target` with `evt.currentTarget`<commit_after>"use strict";

var Backbone = require('../backbone')
  , _ = require('underscore')

module.exports = Backbone.View.extend({
  events: {
    'click .delete-scan': 'handleDeleteClick',
    'click .move-scan': 'handleMoveClick',
    'dragover #scan-drop': function (e) { e.preventDefault(); e.stopPropagation(); },
    'dragenter #scan-drop': 'handleDragenter',
    'dragleave #scan-drop': 'handleDragleave',
    'drop #scan-drop.drop-ready': 'handleDrop'
  },
  className: 'scan-container',
  initialize: function () {
    this.render();
    if (this.collection.needsNormalization()) {
      this.collection.normalizeOrderingValues();
    }
    this.listenTo(this.collection, 'add remove sort', this.render);
  },
  render: function () {
    var that = this
      , template = require('../templates/scans.html')

    this.$el.html( template({ scans: that.collection }) );
    this.$dropTarget = this.$('#scan-drop');
    this.refreshScanButtons();
  },
  handleDeleteClick: function (e) {
    e.preventDefault();
    var cid = e.currentTarget.dataset.scan
      , scan = this.collection.get(cid)

    scan.destroy();
    this.render();
  },
  handleMoveClick: function (e) {
    var scan = this.collection.get(e.currentTarget.dataset.scan)
      , direction = parseInt(e.currentTarget.dataset.direction, 10);

    this.collection.move(scan, this.collection.indexOf(scan) + direction);
  },
  refreshScanButtons: function () {
    var $moveBtns = this.$('.move-scan').prop('disabled', false);
    $moveBtns.first().prop('disabled', 'true');
    $moveBtns.last().prop('disabled', 'true');
  },
  getImageFiles: function (fileList) {
    var imageFiles = [];
    for (var i = 0; i < fileList.length; i++) {
      if (fileList[i].type.match('image/')) {
        imageFiles.push(fileList[i]);
      }
    }
    return imageFiles;
  },
  handleDragenter: function (e) {
    e.preventDefault();
    e.stopPropagation();

    var dt = e.originalEvent.dataTransfer;

    if (_.contains(dt.types, 'Files')) {
      this.$dropTarget.addClass('drop-ready').css('background', '#f0f0f0');
    }
  },
  handleDragleave: function () {
    this.$dropTarget.removeClass('drop-ready').css('background', '');
  },
  handleDrop: function (e) {
    e.preventDefault();
    var imageFiles = this.getImageFiles(e.originalEvent.dataTransfer.files);
    this.handleDragleave();

    imageFiles.forEach(function (imageFile) {
      var that = this;
      this.collection
        .add({ image_file: imageFile })
        .save().done(that.render.bind(that));
    }, this);
  }
});
<|endoftext|>


<commit_before>import React from "react";
import { connect } from "react-redux";
import Card from "../framework/card";
import computeResponsive from "../../util/computeResponsive";
import { twoColumnBreakpoint } from "../../util/globals";
import { titleFont, headerFont, medGrey, darkGrey } from "../../globalStyles";
import { applyFilterQuery } from "../../actions/treeProperties";
import { prettyString, authorString } from "../../util/stringHelpers";

@connect((state) => {
  return {
    browserDimensions: state.browserDimensions.browserDimensions,
    filters: state.controls.filters,
    metadata: state.metadata.metadata,
    nodes: state.tree.nodes,
    visibility: state.tree.visibility
  };
})
class Info extends React.Component {
  constructor(props) {
    super(props);
  }
  static propTypes = {
    sidebar: React.PropTypes.bool.isRequired,
    filters: React.PropTypes.object.isRequired,
    metadata: React.PropTypes.object, // not required. starts as null
    nodes: React.PropTypes.array, // not required. starts as null
    visibility: React.PropTypes.array, // not required. starts as null
    dispatch: React.PropTypes.func.isRequired
  }

  getStyles() {
    let fontSize = 36;
    if (this.props.browserDimensions.width < 1000) {
      fontSize = 32;
    }
    if (this.props.browserDimensions.width < 800) {
      fontSize = 28;
    }
    if (this.props.browserDimensions.width < 600) {
      fontSize = 24;
    }
    if (this.props.browserDimensions.width < 400) {
      fontSize = 24;
    }
    return {
      title: {
        fontFamily: titleFont,
        fontSize: fontSize,
        marginLeft: 5,
        marginTop: 5,
        marginBottom: 10,
        fontWeight: 300,
        color: darkGrey,
        letterSpacing: "-1px",
        maxWidth: 960
      },
      n: {
        fontFamily: headerFont,
        fontSize: 15,
        lineHeight: "28px",
        marginLeft: 10,
        marginTop: 5,
        marginBottom: 10,
        fontWeight: 500,
        color: medGrey
      }
    };
  }

  getNumSelectedAuthors() {
    if (!Object.prototype.hasOwnProperty.call(this.props.filters, "authors") || this.props.filters.authors.length === 0) {
      return Object.keys(this.props.metadata.author_info).length;
    }
    return this.props.filters.authors.length;
  }
  getNumSelectedTips() {
    let count = 0;
    this.props.nodes.forEach((d, idx) => {
      if (!d.hasChildren && this.props.visibility[idx] === "visible") count += 1;
    });
    return count;
  }
  summariseFilters() {
    const otherFilters = Object.keys(this.props.filters)
      // .filter((n) => n !== "authors")
      .filter((n) => this.props.filters[n].length > 0);
    if (otherFilters.length === 0) return null;
    return (
      <g>
        {`${otherFilters.length} active filters (click to remove): `}
        {otherFilters.map((n) => (
          {this.clearFilterButton(n)}{' // '}
        ))}
      </g>
    );
  }
  clearFilterButton(field) {
    return (
       this.props.dispatch(applyFilterQuery(field, []))}
        role="button"
        tabIndex={0}
      >
        {field}

    );
  }
  summariseSelectedAuthors(nSelectedAuthors) {
    const authorInfo = this.props.filters.authors.map((v) => ({
      name: authorString(v),
      n: this.props.metadata.author_info[v].n,
      title: prettyString(this.props.metadata.author_info[v].title)
    }));
    /* case 1 (no selected authors) has already been handled */
    /* case 2: a single author selected */
    if (nSelectedAuthors === 1) {
      return (<g>{"Data from "}{authorInfo[0].name}{" : \""}{authorInfo[0].title}{"\""}</g>);
    /* case 3: a "few" authors */
    } else if (nSelectedAuthors < 4) {
      return (
        <g>
          {"Data from "}
          {authorInfo.slice(0, authorInfo.length - 1).map((cv, idx) => (
            <g key={this.props.filters.authors[idx].v}>
              {cv.name}{` (n=${cv.n}), `}
            </g>
          ))}
          {" and "}{authorInfo[authorInfo.length - 1].name}{` (n=${authorInfo[authorInfo.length - 1].n}).`}
        </g>
      );
    }
    /* case 4 (fallthrough) more than "a few" authors selected */
    return null;
  }

  render() {
    if (!this.props.metadata || !this.props.nodes || !this.props.visibility) return null;
    const responsive = computeResponsive({
      horizontal: this.props.browserDimensions && this.props.browserDimensions.width > twoColumnBreakpoint ? 0.5 : 1,
      vertical: 1.0,
      browserDimensions: this.props.browserDimensions,
      sidebar: this.props.sidebar,
      minHeight: 480,
      maxAspectRatio: 1.0
    });
    const styles = this.getStyles();
    const title = this.props.metadata.title ? this.props.metadata.title : "";
    const nTotalSamples = this.props.metadata.virus_count;
    const nTotalAuthors = Object.keys(this.props.metadata.author_info).length;
    const nSelectedAuthors = this.getNumSelectedAuthors(); // will be equal to nTotalAuthors if none selected
    const nSelectedSamples = this.getNumSelectedTips();
    return (
      <Card center>

            {title}

            {`Showing ${nSelectedSamples} out of ${nTotalSamples} viruses from ${nSelectedAuthors} `}
            {nTotalAuthors === nSelectedAuthors ? "publications" : `out of ${nTotalAuthors} publications`}

            {nSelectedAuthors === nTotalAuthors ? null : this.summariseSelectedAuthors(nSelectedAuthors)}

            {this.summariseFilters()}

      </Card>
    );
  }
}

export default Info;
<commit_msg>fix react warning<commit_after>import React from "react";
import { connect } from "react-redux";
import Card from "../framework/card";
import computeResponsive from "../../util/computeResponsive";
import { twoColumnBreakpoint } from "../../util/globals";
import { titleFont, headerFont, medGrey, darkGrey } from "../../globalStyles";
import { applyFilterQuery } from "../../actions/treeProperties";
import { prettyString, authorString } from "../../util/stringHelpers";

@connect((state) => {
  return {
    browserDimensions: state.browserDimensions.browserDimensions,
    filters: state.controls.filters,
    metadata: state.metadata.metadata,
    nodes: state.tree.nodes,
    visibility: state.tree.visibility
  };
})
class Info extends React.Component {
  constructor(props) {
    super(props);
  }
  static propTypes = {
    sidebar: React.PropTypes.bool.isRequired,
    filters: React.PropTypes.object.isRequired,
    metadata: React.PropTypes.object, // not required. starts as null
    nodes: React.PropTypes.array, // not required. starts as null
    visibility: React.PropTypes.array, // not required. starts as null
    dispatch: React.PropTypes.func.isRequired
  }

  getStyles() {
    let fontSize = 36;
    if (this.props.browserDimensions.width < 1000) {
      fontSize = 32;
    }
    if (this.props.browserDimensions.width < 800) {
      fontSize = 28;
    }
    if (this.props.browserDimensions.width < 600) {
      fontSize = 24;
    }
    if (this.props.browserDimensions.width < 400) {
      fontSize = 24;
    }
    return {
      title: {
        fontFamily: titleFont,
        fontSize: fontSize,
        marginLeft: 5,
        marginTop: 5,
        marginBottom: 10,
        fontWeight: 300,
        color: darkGrey,
        letterSpacing: "-1px",
        maxWidth: 960
      },
      n: {
        fontFamily: headerFont,
        fontSize: 15,
        lineHeight: "28px",
        marginLeft: 10,
        marginTop: 5,
        marginBottom: 10,
        fontWeight: 500,
        color: medGrey
      }
    };
  }

  getNumSelectedAuthors() {
    if (!Object.prototype.hasOwnProperty.call(this.props.filters, "authors") || this.props.filters.authors.length === 0) {
      return Object.keys(this.props.metadata.author_info).length;
    }
    return this.props.filters.authors.length;
  }
  getNumSelectedTips() {
    let count = 0;
    this.props.nodes.forEach((d, idx) => {
      if (!d.hasChildren && this.props.visibility[idx] === "visible") count += 1;
    });
    return count;
  }
  summariseFilters() {
    const otherFilters = Object.keys(this.props.filters)
      // .filter((n) => n !== "authors")
      .filter((n) => this.props.filters[n].length > 0);
    if (otherFilters.length === 0) return null;
    return (
      <g>
        {`${otherFilters.length} active filters (click to remove): `}
        {otherFilters.map((n) => (
          {this.clearFilterButton(n)}{' // '}
        ))}
      </g>
    );
  }
  clearFilterButton(field) {
    return (
       this.props.dispatch(applyFilterQuery(field, []))}
        role="button"
        tabIndex={0}
      >
        {field}

    );
  }
  summariseSelectedAuthors(nSelectedAuthors) {
    const authorInfo = this.props.filters.authors.map((v) => ({
      name: authorString(v),
      n: this.props.metadata.author_info[v].n,
      title: prettyString(this.props.metadata.author_info[v].title)
    }));
    /* case 1 (no selected authors) has already been handled */
    /* case 2: a single author selected */
    if (nSelectedAuthors === 1) {
      return (<g>{"Data from "}{authorInfo[0].name}{" : \""}{authorInfo[0].title}{"\""}</g>);
    /* case 3: a "few" authors */
    } else if (nSelectedAuthors < 4) {
      return (
        <g>
          {"Data from "}
          {authorInfo.slice(0, authorInfo.length - 1).map((cv, idx) => (
            <g key={this.props.filters.authors[idx].v}>
              {cv.name}{` (n=${cv.n}), `}
            </g>
          ))}
          {" and "}{authorInfo[authorInfo.length - 1].name}{` (n=${authorInfo[authorInfo.length - 1].n}).`}
        </g>
      );
    }
    /* case 4 (fallthrough) more than "a few" authors selected */
    return null;
  }

  render() {
    if (!this.props.metadata || !this.props.nodes || !this.props.visibility) return null;
    const responsive = computeResponsive({
      horizontal: this.props.browserDimensions && this.props.browserDimensions.width > twoColumnBreakpoint ? 0.5 : 1,
      vertical: 1.0,
      browserDimensions: this.props.browserDimensions,
      sidebar: this.props.sidebar,
      minHeight: 480,
      maxAspectRatio: 1.0
    });
    const styles = this.getStyles();
    const title = this.props.metadata.title ? this.props.metadata.title : "";
    const nTotalSamples = this.props.metadata.virus_count;
    const nTotalAuthors = Object.keys(this.props.metadata.author_info).length;
    const nSelectedAuthors = this.getNumSelectedAuthors(); // will be equal to nTotalAuthors if none selected
    const nSelectedSamples = this.getNumSelectedTips();
    return (
      <Card center>

            {title}

            {`Showing ${nSelectedSamples} out of ${nTotalSamples} viruses from ${nSelectedAuthors} `}
            {nTotalAuthors === nSelectedAuthors ? "publications" : `out of ${nTotalAuthors} publications`}

            {nSelectedAuthors === nTotalAuthors ? null : this.summariseSelectedAuthors(nSelectedAuthors)}

            {this.summariseFilters()}

      </Card>
    );
  }
}

export default Info;
<|endoftext|>


<commit_before>'use strict';

var ltx = require('ltx'),
  util = require('util'),
  XModule = require('../../../../core/XModule'),
  JID = require('node-xmpp-core').JID,
  logger = require('../../../../core/Logger')('roaster');

var NS_ROASTER = 'jabber:iq:roster';

/*
 * RFC 3921: Roaster
 * http://xmpp.org/rfcs/rfc3921.html#roster
 */
function Roaster(options) {
  // initialize options
  if (!options) {
    options = {};
  }

  this.options = options;

  XModule.call(this);

  this.storage = options.storage;

  logger.info('load ' + this.name);
}
util.inherits(Roaster, XModule);

Roaster.prototype.name = 'RFC 3921: Roaster';
Roaster.prototype.version = '0.1.0';

Roaster.prototype.Error = {
  NotFound: ltx.parse('<error type=\'cancel\'><item-not-found xmlns=\'urn:ietf:params:xml:ns:xmpp-stanzas\'/></error>')
};

Roaster.prototype.initialize = function () {};

/*
 * Detects if the stanza is a roaster request
 *
 * Sample:
 * <iq from='juliet@example.com/balcony' type='get' id='roster_1'>
 *   <query xmlns='jabber:iq:roster'/>
 * </iq>
 */
Roaster.prototype.match = function (stanza) {
  if (stanza.is('iq') && stanza.attrs.type === 'get' && (stanza.getChild('query', NS_ROASTER))) {
    logger.debug('detected roaster get request');
    return true;
  } else if (stanza.is('iq') && stanza.attrs.type === 'set' && (stanza.getChild('query', NS_ROASTER))) {
    logger.debug('detected roaster set request');
    return true;
  }
  return false;
};

Roaster.prototype.convertXMLtoJSON = function (xmlItem) {

  var item = {};
  // set jid
  item.jid = xmlItem.attrs.jid;

  // set name
  if (xmlItem.attrs.name) {
    item.name = xmlItem.attrs.name;
  }

  var groupItems = [];
  var groups = xmlItem.getChildren('group');
  for (var i = 0; i < groups.length; i++) {
    groupItems.push(groups[i].getText());
  }
  item.group = JSON.stringify(groupItems);

  return item;
};

Roaster.prototype.convertJSONItemtoXML = function (item, xmlQuery) {
  var xitem = xmlQuery.c('item', {
    jid: item.jid,
    name: item.Roaster.name,
    subscription: item.Roaster.subscription
  });

  // iterate over group items
  if (item.Roaster.group) {
    try {
      var groups = JSON.parse(item.Roaster.group);
      for (var j = 0; j < groups.length; j++) {
        logger.debug(groups[j]);
        xitem.c('group').t(groups[j]);
      }
    } catch (err) {
      logger.warn(err);
    }
  }
}

Roaster.prototype.convertJSONtoXML = function (jsonList) {
  var query = new ltx.Element('query', {
    xmlns: NS_ROASTER
  });

  for (var i = 0; i < jsonList.length; i++) {
    this.convertJSONItemtoXML(jsonList[i], query);
  }
  return query;
};

/**
 * Returns the roaster list
 */
Roaster.prototype.handleGetRoaster = function (stanza) {
  logger.debug('handleGetRoaster ' + stanza.toString());
  var self = this;
  var jid = new JID(stanza.attrs.from).bare();

  // create user if required
  this.storage.findOrCreateUser(jid.toString())
    .spread(function (user) {
      logger.debug('roaster user: ' + JSON.stringify(user));
      return user.getRoaster();
    }).then(function (list) {
      // send response
      logger.debug('roaster entries: ' + JSON.stringify(list));

      var roasterResult = new ltx.Element('iq', {
        from: stanza.attrs.to,
        to: stanza.attrs.from,
        id: stanza.attrs.id,
        type: 'result'
      });

      if (!list) {
        list = [];
      }

      roasterResult.cnode(self.convertJSONtoXML(list));
      logger.debug(roasterResult.toString());
      logger.debug('send roaster to ' + stanza.attrs.from);
      self.send(roasterResult);

    }).catch(function (err) {
      logger.warn(err);
      self.sendError(stanza);
    });
};

/**
 * Verifies a roaster item before we store it
 * @param  {[type]} item json roaster item
 * @return {[type]}      true if the item is okay
 */
Roaster.prototype.verifyItem = function (item) {
  if ((item === null) ||
    (item.jid === null) ||
    (item.jid === undefined)) {
    logger.error('jid not set');
    return false;
  }

  return true;
};

/**
 * Updates a roaster item
 */
Roaster.prototype.handleUpdateRoasterItem = function (stanza, item) {
  logger.debug('handleUpdateRoasterItem ' + stanza.toString());
  try {
    var self = this;
    var jid = new JID(stanza.attrs.from).bare();
    var roasteritem = this.convertXMLtoJSON(item);
    logger.debug('Converted ' + item.toString() + ' to ' + JSON.stringify(roasteritem));

    if (!this.verifyItem(roasteritem)) {
      throw new Error('roaster item not properly set');
    }

    // TODO: simplify Promise structure
    this.storage.findOrCreateUser(jid.toString())
      .spread(function (user, created) { // jshint ignore:line
        if (user) {
          // search friend:
          self.storage.findOrCreateUser(
            roasteritem.jid.toString()
          ).spread(function (friend, created) { // jshint ignore:line

            logger.debug(JSON.stringify(user))
            logger.debug(JSON.stringify(friend))

            if (friend) {
              // extract from roasteritem
              user.addRoaster(friend, {
                name: roasteritem.name,
                group: roasteritem.group,
                subscription: 'none'
              }).then(function () {
                self.sendSuccess(stanza);
              });
            } else {
              self.sendError(stanza);
            }
          }).catch(function (err) {
            logger.error(err);
            self.sendError(stanza);
          });

        } else {
          self.sendError(stanza);
        }

      }).catch(function (err) {
        // lets catch error and respond with
        logger.error(err);
        self.sendError(stanza);
      });

  } catch (err) {
    self.sendError(stanza, err);
  }
};

/**
 * Deletes a roaster item
 */
Roaster.prototype.handleDeleteRoasterItem = function (stanza, item) {
  logger.debug('handleDeleteRoasterItem ' + stanza.toString());
  try {
    var self = this;
    var jid = new JID(stanza.attrs.from).bare();
    var roasteritem = this.convertXMLtoJSON(item);

    if (!this.verifyItem(roasteritem)) {
      throw new Error('roaster item not properly set');
    }

    // TODO: search both user with one request
    // eg. find user with roasters and delete the specific roaster
    this.storage.User.find({
      where: {
        jid: jid.toString()
      }
    }).then(function (user) {
      if (user) {
        // search friend:
        self.storage.User.find({
          where: {
            jid: roasteritem.jid
          }
        }).then(function (friend) {

          if (friend) {
            // extract from roasteritem
            user.removeRoaster(friend).then(function () {
              self.sendSuccess(stanza);
            });
          } else {
            self.sendError(stanza);
          }
        }).catch(function (err) {
          logger.error(err);
          self.sendError(stanza);
        });

      } else {
        self.sendError(stanza);
      }
    }).error(function (err) {
      logger.error(err);
      self.sendError(stanza);
    });

  } catch (err) {
    self.sendError(stanza, err);
  }
};

/**
 * handles the component requests
 */
Roaster.prototype.handle = function (stanza) {

  // return roaster list
  if (stanza.attrs.type === 'get') {
    this.handleGetRoaster(stanza);
    return true;
  }
  // update or remove
  else if (stanza.attrs.type === 'set') {
    var query = stanza.getChild('query', NS_ROASTER);
    var item = query.getChild('item');

    // delete an item
    if (item.attrs.subscription === 'remove') {
      this.handleDeleteRoasterItem(stanza, item);
      return true;
    }
    // update an item
    else {
      this.handleUpdateRoasterItem(stanza, item);
      return true;
    }
  }

  return false;
};

module.exports = Roaster;
<commit_msg>implement transactions for roaster<commit_after>'use strict';

var ltx = require('ltx'),
  util = require('util'),
  XModule = require('../../../../core/XModule'),
  JID = require('node-xmpp-core').JID,
  logger = require('../../../../core/Logger')('roaster');

var NS_ROASTER = 'jabber:iq:roster';

/*
 * RFC 3921: Roaster
 * http://xmpp.org/rfcs/rfc3921.html#roster
 */
function Roaster(options) {
  // initialize options
  if (!options) {
    options = {};
  }

  this.options = options;

  XModule.call(this);

  this.storage = options.storage;

  logger.info('load ' + this.name);
}
util.inherits(Roaster, XModule);

Roaster.prototype.name = 'RFC 3921: Roaster';
Roaster.prototype.version = '0.1.0';

Roaster.prototype.Error = {
  NotFound: ltx.parse('<error type=\'cancel\'><item-not-found xmlns=\'urn:ietf:params:xml:ns:xmpp-stanzas\'/></error>')
};

Roaster.prototype.initialize = function () {};

/*
 * Detects if the stanza is a roaster request
 *
 * Sample:
 * <iq from='juliet@example.com/balcony' type='get' id='roster_1'>
 *   <query xmlns='jabber:iq:roster'/>
 * </iq>
 */
Roaster.prototype.match = function (stanza) {
  if (stanza.is('iq') && stanza.attrs.type === 'get' && (stanza.getChild('query', NS_ROASTER))) {
    logger.debug('detected roaster get request');
    return true;
  } else if (stanza.is('iq') && stanza.attrs.type === 'set' && (stanza.getChild('query', NS_ROASTER))) {
    logger.debug('detected roaster set request');
    return true;
  }
  return false;
};

Roaster.prototype.convertXMLtoJSON = function (xmlItem) {

  var item = {};
  // set jid
  item.jid = xmlItem.attrs.jid;

  // set name
  if (xmlItem.attrs.name) {
    item.name = xmlItem.attrs.name;
  }

  var groupItems = [];
  var groups = xmlItem.getChildren('group');
  for (var i = 0; i < groups.length; i++) {
    groupItems.push(groups[i].getText());
  }
  item.group = JSON.stringify(groupItems);

  logger.debug('Converted ' + xmlItem.toString() + ' to ' + JSON.stringify(item));

  return item;
};

Roaster.prototype.convertJSONItemtoXML = function (item, xmlQuery) {
  var xitem = xmlQuery.c('item', {
    jid: item.jid,
    name: item.Roaster.name,
    subscription: item.Roaster.subscription
  });

  // iterate over group items
  if (item.Roaster.group) {
    try {
      var groups = JSON.parse(item.Roaster.group);
      for (var j = 0; j < groups.length; j++) {
        logger.debug(groups[j]);
        xitem.c('group').t(groups[j]);
      }
    } catch (err) {
      logger.warn(err);
    }
  }
}

Roaster.prototype.convertJSONtoXML = function (jsonList) {
  var query = new ltx.Element('query', {
    xmlns: NS_ROASTER
  });

  for (var i = 0; i < jsonList.length; i++) {
    this.convertJSONItemtoXML(jsonList[i], query);
  }

  logger.debug('Converted ' + jsonList.toString() + ' to ' + JSON.stringify(query));

  return query;
};

Roaster.prototype.generateRoasterResultMessage = function (params) {
  var roasterResult = new ltx.Element('iq', {
    from: params.to,
    to: params.from,
    id: params.id,
    type: 'result'
  });

  if (!params.list) {
    params.list = [];
  }

  roasterResult.cnode(this.convertJSONtoXML(params.list));
  return roasterResult;
}

/**
 * Returns the roaster list
 */
Roaster.prototype.handleGetRoaster = function (stanza) {
  logger.debug('handleGetRoaster ' + stanza.toString());
  var self = this;
  var storage = this.storage;

  var jid = new JID(stanza.attrs.from).bare();

  var transaction = null;
  var resultList = null;
  storage.sequelize.transaction().then(function (t) {
    transaction = t;
    return storage.findUser(jid.toString(), {
      transaction: transaction
    });
  }).then(function (user) {
    logger.debug('roaster user: ' + JSON.stringify(user));
    return user.getRoaster({
      transaction: transaction
    });
  }).then(function(list){
    resultList = list;
    return transaction.commit();
  }).then(function () {

    logger.debug('roaster entries: ' + JSON.stringify(resultList));

    var roasterResult = self.generateRoasterResultMessage({
      'from' : stanza.attrs.from,
      'to' : stanza.attrs.to,
      'id' : stanza.attrs.id,
      'list' : resultList
    });

    logger.debug('send roaster to ' + stanza.attrs.from + ' ' + roasterResult.toString() );
    self.send(roasterResult);

  }).catch(function (err) {
    logger.warn(err);
    transaction.rollback();
    self.sendError(stanza);
  });
};

/**
 * Verifies a roaster item before we store it
 * @param  {[type]} item json roaster item
 * @return {[type]}      true if the item is okay
 */
Roaster.prototype.verifyItem = function (item) {
  if ((item === null) ||
    (item.jid === null) ||
    (item.jid === undefined)) {
    logger.error('jid not set for roaster item');
    return false;
  }
  return true;
};

/**
 * Updates a roaster item
 */
Roaster.prototype.handleUpdateRoasterItem = function (stanza, item) {
  logger.debug('handleUpdateRoasterItem ' + stanza.toString());
    var self = this;
    var storage = this.storage;

    var roasteritem = null;
    var transaction = null;
    var user = null;

    storage.sequelize.transaction().then(function (t) {
      transaction = t;

      var jid = new JID(stanza.attrs.from).bare();
      return storage.findUser(jid.toString(), {
        transaction: transaction
      })
    }).then(function (usr) {
      user = usr;

      if (!usr) {
        throw new Error('could not find user')
      }

      logger.debug(item.toString())
      roasteritem = self.convertXMLtoJSON(item);

      if (!self.verifyItem(roasteritem)) {
        throw new Error('roaster item not properly set');
      }

      // search friend
      return storage.findOrCreateUser(roasteritem.jid.toString(), {
        transaction: transaction
      })
    }).spread(function (friend, created) { // jshint ignore:line

      logger.debug(JSON.stringify(user))
      logger.debug(JSON.stringify(friend))

      if (!friend) {
        throw new Error('could not create the roaster jid');
      }

      // extract from roasteritem
      return user.addRoaster(friend, {
        name: roasteritem.name,
        group: roasteritem.group,
        subscription: 'none',
        transaction : transaction
      });
    }).then(function () {
      return transaction.commit();
    }).then(function () {
      self.sendSuccess(stanza);
    }).catch(function (err) {
      // lets catch error and respond with
      logger.warn(err);
      transaction.rollback();
      self.sendError(stanza);
    });
};

/**
 * Deletes a roaster item
 */
Roaster.prototype.handleDeleteRoasterItem = function (stanza, item) {
  logger.debug('handleDeleteRoasterItem ' + stanza.toString());

  var self = this;
  var storage = this.storage;

  // TODO: search both user with one request
  // eg. find user with roasters and delete the specific roaster
  storage.sequelize.transaction().then(function (t) {
    transaction = t;

    var jid = new JID(stanza.attrs.from).bare();
    return storage.findUser(jid.toString(), {
      transaction: transaction
    })
  }).then(function (user) {
    if (user) {
      throw new Error('no user found');
    }

    var roasteritem = this.convertXMLtoJSON(item);

    if (!this.verifyItem(roasteritem)) {
      throw new Error('roaster item not properly set');
    }

    // search friend
    return storage.findOrCreateUser(roasteritem.jid.toString(), {
      transaction: transaction
    })
  }).then(function (friend) {

    if (!friend) {
      throw new Error('could not create the roaster jid');
    }

    // extract from roasteritem
    return user.removeRoaster(friend, {
      transaction: transaction
    })
  }).then(function () {
    self.sendSuccess(stanza);
  }).catch(function (err) {
    logger.error(err);
    transaction.rollback();
    self.sendError(stanza);
  });
};

/**
 * handles the component requests
 */
Roaster.prototype.handle = function (stanza) {

  // return roaster list
  if (stanza.attrs.type === 'get') {
    this.handleGetRoaster(stanza);
    return true;
  }
  // update or remove
  else if (stanza.attrs.type === 'set') {
    var query = stanza.getChild('query', NS_ROASTER);
    var item = query.getChild('item');

    // delete an item
    if (item.attrs.subscription === 'remove') {
      this.handleDeleteRoasterItem(stanza, item);
      return true;
    }
    // update an item
    else {
      this.handleUpdateRoasterItem(stanza, item);
      return true;
    }
  }

  return false;
};

module.exports = Roaster;
<|endoftext|>


<commit_before>/*
 Copyright (c) 2011-2013, Vladimir Agafonkin
 SunCalc is a JavaScript library for calculating sun position, sunlight phases, and moon position.
 https://github.com/mourner/suncalc
 */

(function (global) { /*jshint smarttabs: true */

	"use strict";

	// export either as a CommonJS module or a global variable

	var SunCalc;

	if (typeof exports !== 'undefined') {
		SunCalc = exports;
	} else {
		SunCalc = global.SunCalc = {};
	}

	// shortcuts for easier to read formulas

	var PI   = Math.PI,
	    rad  = PI / 180,
	    sin  = Math.sin,
	    cos  = Math.cos,
	    tan  = Math.tan,
	    asin = Math.asin,
	    atan = Math.atan2,
	    acos = Math.acos;

	// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas

	// date/time constants and conversions

	var dayMs = 1000 * 60 * 60 * 24,
	    J1970 = 2440588,
	    J2000 = 2451545;

	function toJulian(date) {
		return date.valueOf() / dayMs - 0.5 + J1970;
	}
	function fromJulian(j) {
		return new Date((j + 0.5 - J1970) * dayMs);
	}
	function toDays(date) {
		return toJulian(date) - J2000;
	}

	// general calculations for position

	var e = rad * 23.4397; // obliquity of the Earth

	function getRightAscension(l, b) {
		return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));
	}
	function getDeclination(l, b) {
		return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));
	}
	function getAzimuth(H, phi, dec) {
		return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));
	}
	function getAltitude(H, phi, dec) {
		return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));
	}
	function getSiderealTime(d, lw) {
		return rad * (280.16 + 360.9856235 * d) - lw;
	}

	// general sun calculations

	function getSolarMeanAnomaly(d) {
		return rad * (357.5291 + 0.98560028 * d);
	}
	function getEquationOfCenter(M) {
		return rad * (1.9148 * sin(M) + 0.0200 * sin(2 * M) + 0.0003 * sin(3 * M));
	}
	function getEclipticLongitude(M, C) {
		var P = rad * 102.9372; // perihelion of the Earth
		return M + C + P + PI;
	}
	function getSunCoords(d) {

		var M = getSolarMeanAnomaly(d),
		    C = getEquationOfCenter(M),
		    L = getEclipticLongitude(M, C);

		return {
			dec: getDeclination(L, 0),
		    ra: getRightAscension(L, 0)
		};
	}

	// calculates sun position for a given date and latitude/longitude

	SunCalc.getPosition = function (date, lat, lng) {

		var lw  = rad * -lng,
		    phi = rad * lat,
		    d   = toDays(date),

		    c  = getSunCoords(d),
		    th = getSiderealTime(d, lw),
		    H  = th - c.ra;

		return {
			azimuth: getAzimuth(H, phi, c.dec),
			altitude: getAltitude(H, phi, c.dec)
		};
	};

	// sun times configuration (angle, morning name, evening name)

	var times = [
		[-0.83, 'sunrise',       'sunset'      ],
		[ -0.3, 'sunriseEnd',    'sunsetStart' ],
		[   -6, 'dawn',          'dusk'        ],
		[  -12, 'nauticalDawn',  'nauticalDusk'],
		[  -18, 'nightEnd',      'night'       ],
		[    6, 'goldenHourEnd', 'goldenHour'  ]
	];

	// adds a custom time to the times config

	SunCalc.addTime = function (angle, riseName, setName) {
		times.push([angle, riseName, setName]);
	};

	// calculations for sun times

	var J0 = 0.0009;

	function getJulianCycle(d, lw) {
		return Math.round(d - J0 - lw / (2 * PI));
	}
	function getApproxTransit(Ht, lw, n) {
		return J0 + (Ht + lw) / (2 * PI) + n;
	}
	function getSolarTransitJ(ds, M, L) {
		return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L);
	}
	function getHourAngle(h, phi, d) {
		return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)));
	}

	// calculates sun times for a given date and latitude/longitude

	SunCalc.getTimes = function (date, lat, lng) {

		var lw  = rad * -lng,
		    phi = rad * lat,
		    d   = toDays(date),

		    n  = getJulianCycle(d, lw),
		    ds = getApproxTransit(0, lw, n),

		    M = getSolarMeanAnomaly(ds),
		    C = getEquationOfCenter(M),
		    L = getEclipticLongitude(M, C),

		    dec = getDeclination(L, 0),

		    Jnoon = getSolarTransitJ(ds, M, L);

	    // returns set time for the given sun altitude
		function getSetJ(h) {
			var w = getHourAngle(h, phi, dec),
			    a = getApproxTransit(w, lw, n);

			return getSolarTransitJ(a, M, L);
		}

		var result = {
			solarNoon: fromJulian(Jnoon),
			nadir: fromJulian(Jnoon - 0.5)
		};

		var i, len, time,
		    angle, morningName, eveningName,
		    Jset, Jrise;

		for (i = 0, len = times.length; i < len; i += 1) {

			time = times[i];

			angle = time[0];
			morningName = time[1];
			eveningName = time[2];

			Jset = getSetJ(angle * rad);
			Jrise = Jnoon - (Jset - Jnoon);

			result[morningName] = fromJulian(Jrise);
			result[eveningName] = fromJulian(Jset);
		}

		return result;
	};

	// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas

	function getMoonCoords(d) { // geocentric ecliptic coordinates of the moon

		var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
		    M = rad * (134.963 + 13.064993 * d), // mean anomaly
		    F = rad * (93.272 + 13.229350 * d),  // mean distance

		    l  = L + rad * 6.289 * sin(M), // longitude
		    b  = rad * 5.128 * sin(F),     // latitude
		    dt = 385001 - 20905 * cos(M);  // distance to the moon in km

		return {
			ra: getRightAscension(l, b),
			dec: getDeclination(l, b),
			dist: dt
		};
	}

	SunCalc.getMoonPosition = function (date, lat, lng) {

		var lw  = rad * -lng,
		    phi = rad * lat,
		    d   = toDays(date),

		    c = getMoonCoords(d),
		    H = getSiderealTime(d, lw) - c.ra,
		    h = getAltitude(H, phi, c.dec);

		// altitude correction for refraction
		h = h + 0.017 * rad / tan(h + 10.26 * rad / (h + 5.10 * rad));

		return {
			azimuth: getAzimuth(H, phi, c.dec),
			altitude: h,
			distance: c.dist
		};
	};

	// calculations for illuminated fraction of the moon,
	// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas

	SunCalc.getMoonFraction = function (date) {

		var d = toDays(date),
		    s = getSunCoords(d),
		    m = getMoonCoords(d),

		    sdist = 1.49598e8, // distance from Earth to Sun

		    phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
		    inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi));

		return (1 + cos(inc)) / 2;
	};

}(this));
<commit_msg>fix whitespace<commit_after>/*
 Copyright (c) 2011-2013, Vladimir Agafonkin
 SunCalc is a JavaScript library for calculating sun position, sunlight phases, and moon position.
 https://github.com/mourner/suncalc
 */

(function (global) { /*jshint smarttabs: true */

	"use strict";

	// export either as a CommonJS module or a global variable

	var SunCalc;

	if (typeof exports !== 'undefined') {
		SunCalc = exports;
	} else {
		SunCalc = global.SunCalc = {};
	}

	// shortcuts for easier to read formulas

	var PI   = Math.PI,
	    rad  = PI / 180,
	    sin  = Math.sin,
	    cos  = Math.cos,
	    tan  = Math.tan,
	    asin = Math.asin,
	    atan = Math.atan2,
	    acos = Math.acos;

	// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas

	// date/time constants and conversions

	var dayMs = 1000 * 60 * 60 * 24,
	    J1970 = 2440588,
	    J2000 = 2451545;

	function toJulian(date) {
		return date.valueOf() / dayMs - 0.5 + J1970;
	}
	function fromJulian(j) {
		return new Date((j + 0.5 - J1970) * dayMs);
	}
	function toDays(date) {
		return toJulian(date) - J2000;
	}

	// general calculations for position

	var e = rad * 23.4397; // obliquity of the Earth

	function getRightAscension(l, b) {
		return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));
	}
	function getDeclination(l, b) {
		return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));
	}
	function getAzimuth(H, phi, dec) {
		return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));
	}
	function getAltitude(H, phi, dec) {
		return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));
	}
	function getSiderealTime(d, lw) {
		return rad * (280.16 + 360.9856235 * d) - lw;
	}

	// general sun calculations

	function getSolarMeanAnomaly(d) {
		return rad * (357.5291 + 0.98560028 * d);
	}
	function getEquationOfCenter(M) {
		return rad * (1.9148 * sin(M) + 0.0200 * sin(2 * M) + 0.0003 * sin(3 * M));
	}
	function getEclipticLongitude(M, C) {
		var P = rad * 102.9372; // perihelion of the Earth
		return M + C + P + PI;
	}
	function getSunCoords(d) {

		var M = getSolarMeanAnomaly(d),
		    C = getEquationOfCenter(M),
		    L = getEclipticLongitude(M, C);

		return {
			dec: getDeclination(L, 0),
		    ra: getRightAscension(L, 0)
		};
	}

	// calculates sun position for a given date and latitude/longitude

	SunCalc.getPosition = function (date, lat, lng) {

		var lw  = rad * -lng,
		    phi = rad * lat,
		    d   = toDays(date),

		    c  = getSunCoords(d),
		    th = getSiderealTime(d, lw),
		    H  = th - c.ra;

		return {
			azimuth: getAzimuth(H, phi, c.dec),
			altitude: getAltitude(H, phi, c.dec)
		};
	};

	// sun times configuration (angle, morning name, evening name)

	var times = [
		[-0.83, 'sunrise',       'sunset'      ],
		[ -0.3, 'sunriseEnd',    'sunsetStart' ],
		[   -6, 'dawn',          'dusk'        ],
		[  -12, 'nauticalDawn',  'nauticalDusk'],
		[  -18, 'nightEnd',      'night'       ],
		[    6, 'goldenHourEnd', 'goldenHour'  ]
	];

	// adds a custom time to the times config

	SunCalc.addTime = function (angle, riseName, setName) {
		times.push([angle, riseName, setName]);
	};

	// calculations for sun times

	var J0 = 0.0009;

	function getJulianCycle(d, lw) {
		return Math.round(d - J0 - lw / (2 * PI));
	}
	function getApproxTransit(Ht, lw, n) {
		return J0 + (Ht + lw) / (2 * PI) + n;
	}
	function getSolarTransitJ(ds, M, L) {
		return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L);
	}
	function getHourAngle(h, phi, d) {
		return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)));
	}

	// calculates sun times for a given date and latitude/longitude

	SunCalc.getTimes = function (date, lat, lng) {

		var lw  = rad * -lng,
		    phi = rad * lat,
		    d   = toDays(date),

		    n  = getJulianCycle(d, lw),
		    ds = getApproxTransit(0, lw, n),

		    M = getSolarMeanAnomaly(ds),
		    C = getEquationOfCenter(M),
		    L = getEclipticLongitude(M, C),

		    dec = getDeclination(L, 0),

		    Jnoon = getSolarTransitJ(ds, M, L);

		// returns set time for the given sun altitude
		function getSetJ(h) {
			var w = getHourAngle(h, phi, dec),
			    a = getApproxTransit(w, lw, n);

			return getSolarTransitJ(a, M, L);
		}

		var result = {
			solarNoon: fromJulian(Jnoon),
			nadir: fromJulian(Jnoon - 0.5)
		};

		var i, len, time,
		    angle, morningName, eveningName,
		    Jset, Jrise;

		for (i = 0, len = times.length; i < len; i += 1) {

			time = times[i];

			angle = time[0];
			morningName = time[1];
			eveningName = time[2];

			Jset = getSetJ(angle * rad);
			Jrise = Jnoon - (Jset - Jnoon);

			result[morningName] = fromJulian(Jrise);
			result[eveningName] = fromJulian(Jset);
		}

		return result;
	};

	// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas

	function getMoonCoords(d) { // geocentric ecliptic coordinates of the moon

		var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
		    M = rad * (134.963 + 13.064993 * d), // mean anomaly
		    F = rad * (93.272 + 13.229350 * d),  // mean distance

		    l  = L + rad * 6.289 * sin(M), // longitude
		    b  = rad * 5.128 * sin(F),     // latitude
		    dt = 385001 - 20905 * cos(M);  // distance to the moon in km

		return {
			ra: getRightAscension(l, b),
			dec: getDeclination(l, b),
			dist: dt
		};
	}

	SunCalc.getMoonPosition = function (date, lat, lng) {

		var lw  = rad * -lng,
		    phi = rad * lat,
		    d   = toDays(date),

		    c = getMoonCoords(d),
		    H = getSiderealTime(d, lw) - c.ra,
		    h = getAltitude(H, phi, c.dec);

		// altitude correction for refraction
		h = h + 0.017 * rad / tan(h + 10.26 * rad / (h + 5.10 * rad));

		return {
			azimuth: getAzimuth(H, phi, c.dec),
			altitude: h,
			distance: c.dist
		};
	};

	// calculations for illuminated fraction of the moon,
	// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas

	SunCalc.getMoonFraction = function (date) {

		var d = toDays(date),
		    s = getSunCoords(d),
		    m = getMoonCoords(d),

		    sdist = 1.49598e8, // distance from Earth to Sun

		    phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
		    inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi));

		return (1 + cos(inc)) / 2;
	};

}(this));
<|endoftext|>