Large

Facebook APR (application post remover)

By Sigi_cz Last update Sep 22, 2011 — Installed 48,261 times.

There are 28 previous versions of this script.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name            Facebook APR (application post remover)
// @description     remove old posts from FB applications (games, quizes, etc ...)
// @author          Sigi_cz
// @version         2.55
// @homepage        http://userscripts.org/scripts/show/92953
// @licence         http://creativecommons.org/licenses/by-nc-nd/3.0/
// @include         http://userscripts.org/scripts/source/92953.meta.js*
// @include         http://www.facebook.com/*
// @include         https://www.facebook.com/*
// ==/UserScript==

// some code parts are inspired or borroved from many great scripts (why reinvent wheel?)
// thanks to:
// "FFixer" http://userscripts.org/scripts/show/8861
// "FB MafiaWars Addon" http://userscripts.org/scripts/show/90615
// "Facebook Recent Activity" http://userscripts.org/scripts/show/68225
// "google.com"
// "stackoverflow.com"

/*jslint browser: true, devel: true, evil: true*/
/*global unsafeWindow:true, window:false, self:false, chrome:false, localStorage:true, XPathResult:false,
  GM_addStyle:true, GM_log:true, GM_getValue:true, GM_setValue:true, GM_deleteValue:true */

(function () {
var isDebug = true;


//------------------------------------------------
// script informations
//------------------------------------------------

var script_info = {
		name: 'Facebook Application Post Remover',
		name_med: 'FB Application Post Remover',
		name_short: 'FB APR',
		author: 'Sigi_cz',
		author_mail: 'sigi_cz@centrum.cz',
		id: '92953',
		ver: '2.55',
		url: 'http://userscripts.org/scripts/show/92953',
		meta: 'http://userscripts.org/scripts/source/92953.meta.js'
};


//------------------------------------------------
// skip "weird" pages
//------------------------------------------------

var stop_now = false, addr = window.location.href;
try {
	// don't run in frames, except updater
	if (self !== window.top) {
		if (!addr.match(script_info.meta)) { stop_now = true; }
	}
	// don't run on weird pages
	else if (
			(addr.indexOf('/ai.php') > 0) ||
			(addr.indexOf('/ajax/') > 0) ||
			(addr.indexOf('/connect/') > 0) ||
			(addr.indexOf('/extern/') > 0) ||
			(addr.indexOf('/plugins/') > 0) ||
			(addr.indexOf('/widgets/') > 0)) { stop_now = true; }
} catch(x) { debugMsg('skip "weird" pages error: ' + x, 1); stop_now = true; }
debugMsg('skip "weird" pages - ' + (stop_now ? 'stop' : 'continue') + '\n' + addr, 1);
if (stop_now) { return; }


//------------------------------------------------
// unsafeWindow
//------------------------------------------------

if (typeof unsafeWindow === 'undefined') { unsafeWindow = window; }

// detect Chromium browser - finally working code
var getDOMWindow = function () {
	var elt = document.createElement('div');
	elt.setAttribute('onclick', 'return window;');
	return elt.onclick();
};
if (typeof chrome  !== 'undefined' && chrome.extension) {
	unsafeWindow = getDOMWindow();
}


//------------------------------------------------
// custom settings
//------------------------------------------------

var cfg_default_user = {
		all_apps: false, // remove all application posts
		time_limit: 12, // remove posts older then X hours
		autorun: false, // start automatically (works only in Opera)
		only_user_posts: false, // false: remove all app. post; true: remove only your app. posts
		continue_proc: true, // search last known post and continue processing (disabled if hide_posts = true)
		hide_posts: false, // don't remove posts, only hide
		xhr_delay: 300, // XMLHttpRequest delay X in ms (you don't want to remove all posts at once)
		language: 'auto', // GUI language
		appIDs: [] // ID's of applications to remove
		, // temporary join global and user cfg
//};
//var cfg_default_global = {
		lastUpdateCheck: 0,
		version: '0',
		updateInterval: 6, // hours
		debug_mode: true // debug mode
};
var default_appIDs = [
		['10979261223','Mafia Wars'],
		['234860566661','Treasure Isle'],
		['46755028429','Castle Age'],
		['102452128776','FarmVille'],
		['130402594779','Kingdoms of Camelot']];
cfg_default_user.appIDs = default_appIDs;

var cfg = {}; // global configuration
var cfu = {}; // user configuration


//------------------------------------------------
// text strings
//------------------------------------------------

var text_strings = {
// translations can be made from 'en' text strings.
// untranslated lines can be removed and 'en' string will be used instead
// keep %number markers correctly placed - they are replaced later.
	languages : ['cs', 'en', 'es', 'nl', 'ro', 'sk', 'zh'],
	// English by Sigi_cz - default
	en : {
		'_language' : 'English',
		'cfg-language' : 'language: ',
		'cfg-config' : 'Configuration',
		'cfg-author' : 'author',
		'cfg-author_email' : 'send e-mail to author',
		'cfg-licence' : 'is licensed under a',
		'cfg-appIDs' : 'appID\'s to remove:',
		'cfg-appIDs-hlp' : 'each row contains one appID, optional text behind ID is comment',
		'cfg-all_apps' : 'all applications',
		'cfg-all_apps-hlp' : 'remove posts from all applications',
		'cfg-time_limit' : '(hours) time limit',
		'cfg-time_limit-hlp' : 'remove posts older then X hours',
		'cfg-continue_proc' : 'continue processing',
		'cfg-continue_proc-hlp' : 'search last known post and continue processing',
		'cfg-autorun' : 'start automatically',
		'cfg-autorun-hlp' : 'processing starts when page is loaded',
		'cfg-only_user_posts' : 'only own posts',
		'cfg-only_user_posts-hlp' : 'check only your app. posts or also from friends',
		'cfg-hide_posts' : 'hide posts',
		'cfg-hide_posts-hlp' : 'don\'t remove posts, only hide',
		'cfg-xhr_delay' : '(ms) delay for removing',
		'cfg-xhr_delay-hlp' : 'you don\'t want to remove all posts at once',
		'cfg-debug_mode' : 'debug mode',
		'gui-runbtn' : 'APR',
		'gui-runbtn-hlp' : 'start posts processing',
		'gui-cfgbtn' : 'Configure APR',
		'gui-cfgbtn-hlp' : 'show APR configuration window',
		'gui-close' : 'Close',
		'gui-save' : 'Save',
		'gui-add' : 'Add',
		'gui-remove' : 'Remove',
		'inf-error' : 'error - see console for details.',
		'inf-search' : 'searching last known old post %1 - page %2.',
		'inf-process' : 'processing visible posts.',
		'inf-xremoved' : '%1 posts total, %2 old removed.',
		'inf-xhidden' : '%1 posts total, %2 old hidden.',
		'str-update_long' : ' UPDATE FOUND%1%1 %2%1 installed version: %3%1 actual version: %4%1 update URL: %5%1%1 Do you want to update now?%1%1',
		'str-addremove' : 'add/remove application',
		'str-unknown' : 'unknown page',
		'str-unknown_long' : 'I don\'t know this page. Should I do something here?%1Contact me with suggestions: ',
		'str-old_profile' : 'old profile',
		'str-old_profile_long' : 'Old profile page layout is unsupported by APR.%1(I can\'t debug new features on it anymore.)%1%1Please switch to %2new profile layout%3.',
		'str-APRhome' : 'homepage',
		'str-APRhome_long' : 'Home sweet home :)',
		'_language_end' : 'English'
	},
	// Czech by Sigi_cz
	cs : {
		'_language' : 'Česky (Czech)',
		'cfg-language' : 'jazyk: ',
		'cfg-config' : 'Nastavení',
		'cfg-author' : 'autor',
		'cfg-author_email' : 'poslat e-mail autorovi',
		'cfg-licence' : 'je licencován pod',
		'cfg-appIDs' : 'ID aplikací k odstranění:',
		'cfg-appIDs-hlp' : 'každý řádek obsahuje jedno ID, text za ID je komentář',
		'cfg-all_apps' : 'všechny aplikace',
		'cfg-all_apps-hlp' : 'odstranit příspěvky ze všech aplikací',
		'cfg-time_limit' : '(hodin) časový limit',
		'cfg-time_limit-hlp' : 'odstranit příspěvky starší než X hodin',
		'cfg-continue_proc' : 'pokračovat ve zpracování',
		'cfg-continue_proc-hlp' : 'vyhledat poslední nezpracovaný příspěvek a pokračovat ve zpracování',
		'cfg-autorun' : 'automatické spouštění',
		'cfg-autorun-hlp' : 'zpracování se spustí ihned po načtení stránky',
		'cfg-only_user_posts' : 'pouze vlastní příspěvky',
		'cfg-only_user_posts-hlp' : 'kontrolovat pouze vlastní příspěvky, nebo i od přátel',
		'cfg-hide_posts' : 'skrývat příspěvky',
		'cfg-hide_posts-hlp' : 'příspěvky nemazat ale pouze skrývat',
		'cfg-xhr_delay' : '(ms) prodleva pro mazání',
		'cfg-xhr_delay-hlp' : 'nechceme odstraňovat všechny příspěvky najednou',
		'cfg-debug_mode' : 'režim ladění',
		'gui-runbtn' : 'APR',
		'gui-runbtn-hlp' : 'spustit zpracování příspěvků',
		'gui-cfgbtn' : 'Nastavit APR',
		'gui-cfgbtn-hlp' : 'zobrazit konfigurační okno APR',
		'gui-close' : 'Zavřít',
		'gui-save' : 'Uložit',
		'gui-add' : 'Přidat',
		'gui-remove' : 'Odebrat',
		'inf-error' : 'chyba - více informací v chybové konzoli.',
		'inf-search' : 'hledám poslední známý nezpracovaný příspěvek.',
		'inf-process' : 'zpracovávám zobrazené příspěvky.',
		'inf-xremoved' : '%1 příspěvků celkem, %2 starých odstraněno.',
		'inf-xhidden' : '%1 příspěvků celkem, %2 starých skryto.',
		'str-update_long' : ' NALEZENA AKTUALIZACE%1%1 %2%1 nainstalovaná verze: %3%1 aktuální verze: %4%1 adresa aktualizace: %5%1%1 Přejete si provést aktualizaci?%1%1',
		'str-addremove' : 'přidat/odebrat aplikaci',
		'str-unknown' : 'neznámá stránka',
		'str-unknown_long' : 'Tady to neznám. Měl bych tu něco dělat?%1Kontaktujte mě s nápady: ',
		'str-old_profile' : 'starý profil',
		'str-old_profile_long' : 'APR nepodporuje starý layout profilu%1(Už na něm nemůžu testovat nové věci.)%1%1Prosím přejděte na %2nový layout%3.',
		'str-APRhome' : 'homepage',
		'str-APRhome_long' : 'Domov sladký domov :)',
		'_language_end' : 'Česky (Czech)'
	},
	// Dutch translation by Stievydude
	nl : {
		'_language' : 'Nederlands (Dutch)',
		'cfg-language' : 'taal: ',
		'cfg-config' : 'Configuratie',
		'cfg-author' : 'auteur',
		'cfg-author_email' : 'verstuur een e-mail naar de auteur',
		'cfg-licence' : 'is gelicentieerd onder een',
		'cfg-appIDs' : 'te verwijderen toepassingID\'s:',
		'cfg-appIDs-hlp' : 'elke rij bevat één toepassingID, optionele tekst achter het ID is commentaar',
		'cfg-all_apps' : 'alle toepassingen',
		'cfg-all_apps-hlp' : 'verwijder posts van alle toepassingen',
		'cfg-time_limit' : '(uren) tijdslimiet',
		'cfg-time_limit-hlp' : 'verwijder posts ouder dan X uren',
		'cfg-continue_proc' : 'verdergaan met verwerken',
		'cfg-continue_proc-hlp' : 'zoek laatst gekende post en ga verder met verwerken',
		'cfg-autorun' : 'start automatisch',
		'cfg-autorun-hlp' : 'verwerken start wanneer de pagina is geladen',
		'cfg-only_user_posts' : 'alleen eigen posts',
		'cfg-only_user_posts-hlp' : 'controleer alleen eigen toepassingposts of ook van vrienden',
		'cfg-hide_posts' : 'verberg posts',
		'cfg-hide_posts-hlp' : 'verwijder geen posts, alleen verbergen',
		'cfg-xhr_delay' : '(ms) vertraging voor verwijderen ',
		'cfg-xhr_delay-hlp' : 'Je wil niet alle posts ineens verwijderen',
		'cfg-debug_mode' : 'debug modus',
		'gui-runbtn' : 'APR',
		'gui-runbtn-hlp' : 'start met verwerken posts',
		'gui-cfgbtn' : 'Configureer APR',
		'gui-cfgbtn-hlp' : 'toon APR configuratievenster',
		'gui-close' : 'Sluit',
		'gui-save' : 'Bewaar',
		'gui-add' : 'Voeg toe',
		'gui-remove' : 'Verwijder',
		'inf-error' : 'fout - zie console voor details.',
		'inf-search' : 'bezig met laatst bekende post te zoeken %1 - pagina %2.',
		'inf-process' : 'zichtbare posts verwerken.',
		'inf-xremoved' : '%1 posts in totaal, %2 oude verwijderd.',
		'inf-xhidden' : '%1 posts in totaal, %2 oude verborgen.',
		'str-update_long' : ' NIEUWE VERSIE GEVONDEN%1%1 %2%1 geïnstalleerde versie: %3%1 huidige versie: %4%1 update URL: %5%1%1 Nu updaten?%1%1',
		'str-addremove' : 'toepassing toevoegen/verwijderen',
		'str-unknown' : 'onbekende pagina',
		'str-unknown_long' : 'Ik ken deze pagina niet. Zou ik hier iets moeten doen? %1Contacteer me met suggesties: ',
		'str-old_profile' : 'oud profiel',
		'str-old_profile_long' : 'Oude profielpagina. De layout wordt niet ondersteund door APR.%1(Ik kan de nieuwe functies op deze pagina niet meer debuggen.)%1%1Stap a.u.b over naar het %2nieuw profiel%3.',
		'str-APRhome' : 'startpagina',
		'str-APRhome_long' : 'Zoals het klokje thuis tikt, tikt het nergens :)',
		'_language_end' : 'Nederlands (Dutch)'
	},
	// Spanish translation by Néstor
	es : {
		'_language' : 'Español (Spanish)',
		'cfg-language' : 'idioma: ',
		'cfg-config' : 'Configuración',
		'cfg-author' : 'autor',
		'cfg-author_email' : 'enviar e-mail al autor',
		'cfg-licence' : 'está bajo una licencia de',
		'cfg-appIDs' : 'appID\'s a borrar:',
		'cfg-appIDs-hlp' : 'cada linea contiene un appID, texto opcional detrás del ID es comentario',
		'cfg-all_apps' : 'Todas las aplicaciones',
		'cfg-all_apps-hlp' : 'borrar posts de todas aplicaciones',
		'cfg-time_limit' : '(horas) tiempo límite',
		'cfg-time_limit-hlp' : 'borrar posts más viejos que x horas',
		'cfg-continue_proc' : 'continua procesando',
		'cfg-continue_proc-hlp' : 'buscar último post y continuar procesando',
		'cfg-autorun' : 'comenzar automáticamente',
		'cfg-autorun-hlp' : 'proceso comienza cuando la página está cargada',
		'cfg-only_user_posts' : 'solo posts propios',
		'cfg-only_user_posts-hlp' : 'revisar sólo los posts de tus propias apps o tambien de amigos',
		'cfg-hide_posts' : 'ocultar posts',
		'cfg-hide_posts-hlp' : 'no borrar posts, sólo esconderlos',
		'cfg-xhr_delay' : '(ms) demora para el borrado',
		'cfg-xhr_delay-hlp' : 'no querrás borrar todos los posts al mismo tiempo',
		'cfg-debug_mode' : 'debug mode',
		'gui-runbtn' : 'APR',
		'gui-runbtn-hlp' : 'Comenzar a procesar posts',
		'gui-cfgbtn' : 'Configurar APR',
		'gui-cfgbtn-hlp' : 'Mostrar configuración APR',
		'gui-close' : 'Cerrar',
		'gui-save' : 'Guardar',
		'gui-add' : 'Agregar',
		'gui-remove' : 'Borrar',
		'inf-error' : 'error - mira la consola para detalles.',
		'inf-search' : 'buscando último post viejo conocido %1 - página %2.',
		'inf-process' : 'procesando posts visibles.',
		'inf-xremoved' : '%1 posts total, %2 viejos borrados.',
		'inf-xhidden' : '%1 posts total, %2 viejos ocultados.',
		'str-update_long' : ' ACTUALIZACIÓN ENCONTRADA%1%1 %2%1 versión instalada: %3%1 versión actual: %4%1 URL de actualiación: %5%1%1 ¿Quieres actualizar ahora?%1%1',
		'str-addremove' : 'agregar/quitar aplicación',
		'str-unknown' : 'página desconocida',
		'str-unknown_long' : 'No conozco esta página. ¿Debería hacer algo aquí?%1Contáctame con sugerencias: ',
		'str-old_profile' : 'viejo perfil',
		'str-old_profile_long' : 'El viejo diseño de página de perfil no es soportado por APR.%1(Ya no se puede depurar nuevas características en este.)%1%1Por favor cambie al %2nuevo diseño de perfil%3.',
		'str-APRhome' : 'homepage',
		'str-APRhome_long' : 'Hogar dulce hogar :)',
		'_language_end' : 'Español (Spanish)'
	},
	// Romanian translation by Virgil Damian
	ro : {
		'_language' : 'Romana (Romanian)',
		'cfg-language' : 'limba: ',
		'cfg-config' : 'Configurare',
		'cfg-author' : 'autor',
		'cfg-author_email' : 'trimite e-mail autorului',
		'cfg-licence' : 'are licenta',
		'cfg-appIDs' : 'appID-uri de sters:',
		'cfg-appIDs-hlp' : 'fiecare rand contine un appID, textul optional de dupa ID e comentariu',
		'cfg-all_apps' : 'toate aplicatiile',
		'cfg-all_apps-hlp' : 'sterge postarile de la toate aplicatiile',
		'cfg-time_limit' : '(ore) limita de timp',
		'cfg-time_limit-hlp' : 'sterge postarile mai vechi de X ore',
		'cfg-continue_proc' : 'continua procesarea',
		'cfg-continue_proc-hlp' : 'cauta ultima postare cunoscuta si continua procesarea',
		'cfg-autorun' : 'porneste automat',
		'cfg-autorun-hlp' : 'procesarea incepe cand se incarca pagina',
		'cfg-only_user_posts' : 'doar postarile proprii',
		'cfg-only_user_posts-hlp' : 'cauta doar postarile tale sau si pe cele ale prietenilor',
		'cfg-hide_posts' : 'ascunde postarile',
		'cfg-hide_posts-hlp' : 'postarile nu sunt sterse, doar ascunse',
		'cfg-xhr_delay' : '(ms) intarziere pentru stergere',
		'cfg-xhr_delay-hlp' : 'n-ai vrea sa stergi toate postarile deodata',
		'cfg-debug_mode' : 'debug mode',
		'gui-runbtn' : 'APR',
		'gui-runbtn-hlp' : 'incepe procesarea postarilor',
		'gui-cfgbtn' : 'Configureaza APR',
		'gui-cfgbtn-hlp' : 'arata fereastra de configurare APR',
		'gui-close' : 'Inchide',
		'gui-save' : 'Salveaza',
		'gui-add' : 'Adauga',
		'gui-remove' : 'Sterge',
		'inf-error' : 'eroare - vezi consola pentru detalii.',
		'inf-search' : 'se cauta ultima postare veche %1 - pagina %2.',
		'inf-process' : 'se proceseaza postarile vizible.',
		'inf-xremoved' : '%1 postari in total, %2 postari vechi sterse.',
		'inf-xhidden' : '%1 postari in total, %2 postari vechi ascunse.',
		'str-update_long' : ' ACTUALIZARE GASITA%1%1 %2%1 versiune instalata: %3%1 versiune actuala: %4%1 URL actualizare: %5%1%1 Vrei sa actualizezi acum?%1%1',
		'str-addremove' : 'adauga/sterge aplicatie',
		'str-unknown' : 'pagina necunoscuta',
		'str-unknown_long' : 'Nu conosc pagina aceasta. Ar trebui sa fac ceva aici?%1Contacteaza-ma cu sugestii: ',
		'str-old_profile' : 'profil vechi',
		'str-old_profile_long' : 'Stilul vechi pentru pagina de profil nu este suportat de APR.%1(Nu mai pot corecta proprietatile noi.)%1%1Te rog treci la %2noua tema de profil%3.',
		'str-APRhome' : 'pagina acasa',
		'str-APRhome_long' : 'Casa, dulce casa :)',
		'_language_end' : 'Romana (Romanian)'
	},
	// zh-tw translation by R-way Orz
	zh : {
		'_language' : '繁體中文 (Chinese)',
		'cfg-language' : '語言: ',
		'cfg-config' : '設定',
		'cfg-author' : '作者',
		'cfg-author_email' : '寄信給作者',
		'cfg-licence' : '依此授權:',
		'cfg-appIDs' : '限定移除的應用程式 ID:',
		'cfg-appIDs-hlp' : '每列包含一個應用程式 ID, ID 後為附註選填性文字',
		'cfg-all_apps' : '所有應用程式',
		'cfg-all_apps-hlp' : '移除所有應用程式的貼文',
		'cfg-time_limit' : '(小時) 保留時限',
		'cfg-time_limit-hlp' : '移除 N 小時以前的貼文',
		'cfg-continue_proc' : '接續處理',
		'cfg-continue_proc-hlp' : '從上次已知的最後一則貼文開始繼續處理',
		'cfg-autorun' : '自動開始',
		'cfg-autorun-hlp' : '網頁載入完即開始處理',
		'cfg-only_user_posts' : '僅自己的貼文',
		'cfg-only_user_posts-hlp' : '勾選的話則僅針對自己的應用程式貼文;否則一併處理朋友們的應用程式貼文',
		'cfg-hide_posts' : '隱藏貼文',
		'cfg-hide_posts-hlp' : '僅隱藏貼文而不移除 ',
		'cfg-xhr_delay' : '(毫秒) 間隔移除動作',
		'cfg-xhr_delay-hlp' : '若你不想一次秒殺所有貼文',
		'cfg-debug_mode' : '偵錯模式',
		'gui-runbtn' : 'APR',
		'gui-runbtn-hlp' : '開始處理貼文',
		'gui-cfgbtn' : '設定 APR',
		'gui-cfgbtn-hlp' : '顯示 APR 設定視窗',
		'gui-close' : '關閉',
		'gui-save' : '儲存',
		'gui-add' : '新增',
		'gui-remove' : '移除',
		'inf-error' : '錯誤 - 細節請查看主控台.',
		'inf-search' : '正在搜尋上次已知的最後一則貼文 %1 - 第 %2 頁.',
		'inf-process' : '正在處理可見貼文.',
		'inf-xremoved' : '總共 %1 則貼文, %2 則舊文已移除.',
		'inf-xhidden' : '總共 %1 則貼文, %2 則舊文已隱藏.',
		'str-update_long' : ' 發現更新%1%1 %2%1 已安裝版本: %3%1 實際版本: %4%1 更新網址: %5%1%1 現在就更新嗎?%1%1',
		'str-addremove' : '新增/移除 應用程式',
		'str-unknown' : '未知網頁',
		'str-unknown_long' : '這個網頁我不瞭啊. 我該幹些啥?%1給點建議吧: ',
		'str-old_profile' : '舊版個人檔案',
		'str-old_profile_long' : 'APR 不支援舊式個人檔案的頁面排版.%1(我再也無法維護舊版頁面裡的新功能.)%1%1請切換到 %2新版個人檔案%3.',
		'str-APRhome' : '首頁',
		'str-APRhome_long' : '甜蜜的家 :)',
		'_language_end' : '繁體中文 (Chinese)'
	},
	// Slovak translation by Strateny
	sk : {
		'_language' : 'Slovensky (Slovak)',
		'cfg-language' : 'jazyk: ',
		'cfg-config' : 'Nastavenia',
		'cfg-author' : 'autor',
		'cfg-author_email' : 'poslať e-mail autorovi',
		'cfg-licence' : 'je licencovaný pod',
		'cfg-appIDs' : 'ID aplikácií, ktoré budú odstraňované:',
		'cfg-appIDs-hlp' : 'každý riadok obsahuje jedno ID, text za ID je iba komentár',
		'cfg-all_apps' : 'všetky aplikácie',
		'cfg-all_apps-hlp' : 'odstrániť príspevky od všetkých aplikácií',
		'cfg-time_limit' : 'hodín - časový limit',
		'cfg-time_limit-hlp' : 'odstrániť príspevky staršie ako X hodín',
		'cfg-continue_proc' : 'pokračovať vo vymazávaní',
		'cfg-continue_proc-hlp' : 'vyhľadať posledný nespracovaný príspevok a pokračovať vo vymazávaní',
		'cfg-autorun' : 'automatické spúšťanie',
		'cfg-autorun-hlp' : 'vymazávanie sa spustí automaticky po načítaní stránky',
		'cfg-only_user_posts' : 'len vlastné príspevky',
		'cfg-only_user_posts-hlp' : 'spracovať len vlastné príspevky (od priateľov ponechať)',
		'cfg-hide_posts' : 'príspevky iba skrývať',
		'cfg-hide_posts-hlp' : 'príspevky nevymazávať, ale iba skrývať',
		'cfg-xhr_delay' : '(ms) oneskorenie pre vymazávanie',
		'cfg-xhr_delay-hlp' : 'čakanie na odozvu servera, príliš nízky čas výrazne spomalí prehliadač',
		'cfg-debug_mode' : 'ladiaci režim',
		'cfg-debug_mode-hlp' : 'help pre ladiaci režim',
		'gui-runbtn' : 'APR',
		'gui-runbtn-hlp' : 'spustiť spracovanie príspevkov',
		'gui-cfgbtn' : 'Nastaviť APR',
		'gui-cfgbtn-hlp' : 'zobraziť konfiguračné okno APR',
		'gui-close' : 'Zavrieť',
		'gui-save' : 'Uložiť',
		'gui-add' : 'Pridať',
		'gui-remove' : 'Odobrať',
		'inf-error' : 'chyba - viac informácií nájdete v chybovej konzole.',
		'inf-search' : 'vyhľadávam posledný známy nespracovaný príspevok.',
		'inf-process' : 'spracovávam zobrazené príspevky.',
		'inf-xremoved' : '%1 príspevkov celkom, z toho bolo %2 starých odstránených.',
		'inf-xhidden' : '%1 príspevkov celkem, z toho bolo %2 starých skrytých.',
		'str-update_long' : ' NÁJDENÁ AKTUALIZÁCIA%1%1 %2%1 nainštalovaná verzia: %3%1 aktuálna verzia: %4%1 adresa aktualizácie: %5%1%1 Chcete vykonať aktualizáciu?%1%1',
		'str-addremove' : 'pridať/odobrať aplikáciu',
		'str-unknown' : 'neznáma stránka',
		'str-unknown_long' : 'Tuto to nepoznám. Mal by som tu niečo robiť?%1Ak máte nápad, kontaktujte ma: ',
		'str-old_profile' : 'starý profil',
		'str-old_profile_long' : 'APR nepodporuje starý layout profilu%1(Už na ňom nemôžem testovať nové veci.)%1%1Prosím prejdite na %2nový layout%3.',
		'str-APRhome' : 'homepage',
		'str-APRhome_long' : 'Domov sladký domov :)',
		'_language_end' : 'Slovensky (Slovak)'
	},
	text_strings_end : ''
};


//------------------------------------------------
// some helpfull functions
//------------------------------------------------

function doClick(elm) { var e = document.createEvent('MouseEvents'); e.initEvent('click', true, true, window, 0); elm.dispatchEvent(e); }

function destroy(elm) { if (elm && elm.parentNode) { elm.parentNode.removeChild(elm); } }

function testClass(elm, cls) { if (typeof cls === 'string') { cls = new RegExp('(^|\\s)' + cls + '(\\s|$)'); } return (elm.className && cls.test(elm.className)); }

function insertBefore(elm, ref) { if (ref && ref.parentNode) { ref.parentNode.insertBefore(elm, ref); } }

// select elements by 'tagname' '#ID' '.class' './xpath' '//xpath'
function $(q, root, single) {
	if (root && typeof root === 'string') {
		root = $(root, null, true);
		if (!root) { return null; }
	}
	root = root || document;
	if (q[0]==='#') { return root.getElementById(q.substr(1)); }
	else if (q[0]==='/' || (q[0]==='.' && q[1]==='/')) {
		if (single) { return document.evaluate(q, root, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; }
		return document.evaluate(q, root, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	}
	else if (q[0]==='.') { return root.getElementsByClassName(q.substr(1)); }
	return root.getElementsByTagName(q);
}

// add event listeners
function on(type, elm, func) {
	if (type instanceof Array) { var ii=type.length; while (ii--) { on(type[ii], elm, func); } return; }
	if (elm instanceof Array) { var jj=elm.length; while (jj--) { on(type, elm[jj], func); } return; }
	(typeof elm === 'string' ? $(elm) : elm).addEventListener(type, func, false);
}

// GM_addStyle emulation
function addStyle(css) {
	if (typeof GM_addStyle !== 'undefined') { return GM_addStyle(css); }
	else {
		var ishead = document.getElementsByTagName('head')[0];
		var document_element = (!ishead ? document.documentElement : null);
		var link_stylesheet = document.createElement('link');
		link_stylesheet.rel = 'stylesheet';
		link_stylesheet.type = 'text/css';
		link_stylesheet.href = 'data:text/css,' + encodeURIComponent(css);
		if (ishead) { ishead.appendChild(link_stylesheet); }
		else { document_element.insertBefore(link_stylesheet, document_element.firstChild); }
	}
}

// check if string is part of array
function contains(arr, str, identical) {
	var ii = arr.length;
	while (ii--) {
		if (!identical) { if (arr[ii].indexOf(str) >= 0) { return ii; } }
		else { if (arr[ii] === str) { return ii; } }
	}
	return ii;
}

// send XMLHttpRequest
function xhr(url, callback_fn, data) {
	var res = new XMLHttpRequest();
	res.onreadystatechange = function () { if (typeof callback_fn === 'function' && res.readyState === 4 && res.status === 200) { callback_fn(res.responseText); } };
	res.open(data ? 'POST' : 'GET', url, true);
	res.setRequestHeader('User-agent', window.navigator.userAgent);
	if (data) {
		res.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
		res.setRequestHeader('Connection', 'close');
		res.setRequestHeader('Content-length', data.length);
	}
	res.send(data||null);
}

// delay script processing
function delay(millis) {
	var date = new Date();
	var curDate = null;
	do { curDate = new Date(); }
	while (curDate-date < millis);
}

// write debug info to console
function debugMsg(msg, force) {
	if (!force && !isDebug) { return false; }
	if (force === 0) { return false; }
	msg = script_info.name_short + ': ' + msg;
	if (typeof GM_log !== 'undefined') { GM_log(msg); return true; }
	if (typeof opera !== 'undefined' && opera.postError) { opera.postError(msg); return true; }
	if (typeof console !== 'undefined' && console.log) { console.log(msg); return true; }
	alert(msg);
	return false;
}

function env(key) {
	if (typeof unsafeWindow.Env !== 'undefined' && typeof unsafeWindow.Env[key] !== 'undefined') { return unsafeWindow.Env[key]; }
	return 'undefined';
}

if (typeof GM_openInTab !== 'function') { GM_openInTab = function (url) { window.open(url, ''); } }


//------------------------------------------------
// Data storage
//------------------------------------------------

// implement JSON functions if they're not already defined - modified from
// http://www.sitepoint.com/blogs/2009/08/19/javascript-json-serialization/
if (!this.JSON) { this.JSON = {}; }
if (typeof JSON.stringify !== 'function') {
	JSON.stringify = function (obj) {
		var t = typeof (obj);
		if (t !== 'object' || obj === null) {
			if (t === 'string') { obj = '"' + obj.replace('"', '\\"') + '"'; }
			return String(obj);
		} else {
			var n, v, json = [], arr = (obj && obj.constructor === Array);
			for (n in obj) {
				v = obj[n]; t = typeof(v);
				if (t !== 'function') {
					if (t === 'string') { v = '"' + v.replace('"', '\\"') + '"'; }
					if ( t === 'object' && v !== null) { v = JSON.stringify(v); }
					json.push((arr ? '' : '"' + n + '":') + String(v));
				}
			}
			return (arr ? '[' : '{') + String(json) + (arr ? ']' : '}');
		}
	};
}
if (typeof JSON.parse !== 'function') {
	JSON.parse = function (str) {
		if (str === '') { str = '""'; }
		eval('var p=' + str + ';');
		return p;
	};
}

// prepare data storage functions
function storageInit() {
	debugMsg('initialize data storage');

	// detect available storage type
	var storage = 'none';
	try {
		if (typeof GM_getValue === 'function' && typeof GM_setValue === 'function') {
			// test if greasemonkey's functions work.
			var GM_testkey = 'GM_testkey',
			    GM_testvalue = 'GM_testvalue-' + (new Date() * 1);
			GM_setValue(GM_testkey, GM_testvalue);
			if (GM_getValue(GM_testkey, false) === GM_testvalue) { storage = 'greasemonkey'; }
			GM_deleteValue(GM_testkey);
		}
	} catch(x) { debugMsg('storageInit error: ' + x, 1); }
	if (storage === 'none' && typeof localStorage === 'object') { storage = 'localstorage'; }
	debugMsg('storage type=' + storage);

	// some shitty browser detected
	if (storage === 'none') {
		setTimeout(function () {
			alert('Sorry but your browser sux ...\n' +
			      'No permanent storage detected - initiating temporary storage.\n' +
			      'All changes will be lost after page refresh!');
		}, 0);
		localStorage = (function () {
			var ls_instance = {},
			    tmp_array = [];
			ls_instance.setItem = function (name, value){
				tmp_array[name] = value;
			};
			ls_instance.getItem = function (name) {
				return tmp_array[name] || null;
			};
			ls_instance.removeItem = function (name) {
				tmp_array[name] = null;
			};
			return ls_instance;
		})();
		storage = 'fakestorage';
	}

	// fake GM_ functions
	if (storage === 'localstorage' || storage === 'fakestorage') {
		GM_setValue = function (name, value) {
			localStorage.setItem(name, value);
		};
		GM_getValue = function (name, defaultValue) {
			return localStorage.getItem(name) || defaultValue;
		};
		GM_deleteValue = function (name) {
			localStorage.removeItem(name);
		};
	}

	return storage;
}

// function to handle configuration data
var CfgHandler = function (name, defaults, load) {
	var cfg_name = 'FB_APR-' + name,
			options = defaults,
			cfg_handler = {};
	var o_diff = {added: {}, added_cnt: 0, removed: {}, removed_cnt: 0},
			first_load = true;

	function toGood(value, def) {
		switch (typeof def) {
			case 'string' : return String(value);
			case 'number' : return isNaN(value) === false ? parseInt(value, 10) : def;
			case 'boolean' : return value === true;
			default: return typeof value === typeof def ? value : def;
		}
	}

	cfg_handler.str = function (){
		return cfg_name + '|JSON=' + (JSON.stringify(options));
	};
	cfg_handler.diff = function () {
		return o_diff;
	};

	//use (any) handler to save/load single value config
	cfg_handler.sset = function (key, value){
		setTimeout(function () {
			GM_setValue(cfg_name + '-' + key, value);
		}, 0);
	};
	cfg_handler.sget = function (key, default_val) {
		return GM_getValue(cfg_name + '-' + key) || default_val;
	};
	cfg_handler.sremove = function (key) {
		GM_deleteValue(cfg_name + '-' + key);
	};

	//save/load config, access values with set/get
	cfg_handler.set = function (key, value, save) {
		options[key] = toGood(value, options[key]);
		if (save === true) { cfg_handler.save(); }
	};
	cfg_handler.get = function (key) {
		return options[key];
	};
	cfg_handler.remove = function (key) {
		delete options[key];
	};
	cfg_handler.save = function (callback) {
		//setTimeout(function () {
			GM_setValue(cfg_name, JSON.stringify(options));
			if (typeof callback === 'function') {
				callback.apply(this);
			}
		//}, 0);
	};
	cfg_handler.load = function (callback) {
		//setTimeout(function () {
			var optt = GM_getValue(cfg_name, null);
			if (optt === null) { return; } // use default options
			var tempOpt = JSON.parse(optt);

			for (key in tempOpt){
				if (typeof options[key] !== 'undefined') {
					options[key] = toGood(tempOpt[key], options[key]);
				}
			}

			// check for default-loaded differences
			if (first_load) {

				for (key in options){
					if (typeof tempOpt[key] === 'undefined') {
						o_diff.added[key] = options[key];
						o_diff.added_cnt++;
					}
				}

				for (key in tempOpt){
					if (typeof options[key] === 'undefined') {
						o_diff.removed[key] = tempOpt[key];
						o_diff.removed_cnt++;
					}
				}

				first_load = false;
			}

			if (typeof callback === 'function') {
				callback.apply(this);
			}
		//}, 0);
	};

	if (load === true) { cfg_handler.load(); }
	return cfg_handler;
};


//------------------------------------------------
// "GUI"
//------------------------------------------------

var apr_language = 'en';

// set current language
function guiLanguage() {
	debugMsg('GUI select language');

	// try read document locale or default to english
	var loc = document.body.className.match(/locale_([^ ]+)/i) || ['', 'en'],
	    cfglng = cfu.get('language');
	if (cfglng === 'auto') {
		apr_language = loc[1].toLowerCase();
		if (!text_strings[apr_language]) {
			apr_language = apr_language.split('_')[0];
			if (!text_strings[apr_language]) { apr_language = 'en'; }
		}
	} else { apr_language = cfglng; }
}

// Get a string in the current language, default to english or return empty.
// Replace %x with text from repl array
function str(key, repl) {
	var out_string, lng = apr_language;
	if (!text_strings[lng][key]) { lng = 'en'; }
	if (text_strings[lng][key]) { out_string = text_strings[lng][key]; }
	else { out_string = ''; }
	if (repl) {
		if (typeof repl === 'number') { repl = repl + ''; }
		if (typeof repl === 'string') { repl = repl.split('#&@'); }
		if (repl instanceof Array) { var rgx, ii = repl.length; while (ii--) { rgx = new RegExp('%' + (ii + 1), 'g'); out_string = out_string.replace(rgx, repl[ii]); } }
	}
	return out_string;
}

// create element
function $elm(eType, eId, eClass, eContent) {
	if ($('#' + eId)) { destroy($('#' + eId)); }
	try {
		var newElm = document.createElement(eType);
		newElm.id = eId;
		if (typeof eClass === 'string') { newElm.className = eClass; }
		if (typeof eContent === 'string') { newElm.innerHTML = eContent; }
		return newElm;
	} catch(x) { debugMsg('elm error: ' + x, 1); }
}

// show info in apr_info div
function aprInfo(text) {
	debugMsg('info: "' + text + '"', 1);

	try {
		var result = $('#apr_info');
		result.firstChild.innerHTML = 'APR: ' + text;
		result.style.height = 'auto';
		result.style.overflow = 'visible';
		result.style.visibility = 'visible';
	} catch(x) { debugMsg('aprInfo error: ' + x, 1); }
}

// hide apr_info div
function guiInfoHide() {
	debugMsg('GUI hide info div');

	try {
		var result = $('#apr_info');
		result.style.height = '0px';
		result.style.overflow = 'hidden';
		result.style.visibility = 'hidden';
	} catch(x) { debugMsg('guiInfoHide error: ' + x, 1); }
}

// create div for informations
function guiCreateInfoDiv(elm) {
	debugMsg('GUI create info div');

	if ($('#apr_info')) { return; }
	try {
		addStyle([
		         '#apr_info { height:0px; padding:0 5px; display:block; border:1px dotted grey; overflow:hidden; visibility:hidden; }',
		         '#apr_info:hover #apr_info_close { color:red; }',
		         ''].join(''));

		var ELMinfo = $elm('div', 'apr_info', null, [
				'<span style="float:left;">APR: information area.</span>',
				'<span id="apr_info_close" style="float:right;" title="' + str('gui-close') + '"> [x]</span>',
				'<div style="clear:both;"></div>',
				''].join(''));
		insertBefore(ELMinfo, $(elm).firstChild);
		on('click', '#apr_info', guiInfoHide);
	} catch(x) { debugMsg('guiCreateInfoDiv error: ' + x, 1); }
}

// create button on right side of top menu
function guiCreateRunButton() {
	debugMsg('GUI create run button');

	if ($('#apr_runbtn')) { return; }
	try {
		var ELMbutton = $elm('li', 'apr_runbtn', null,
				'<a onclick="javascript:return(void);" title="' + str('gui-runbtn-hlp') + '">' + str('gui-runbtn') + '</a>');
		$('#pageNav').appendChild(ELMbutton);
		on('click', '#apr_runbtn', function () { aprProcess(); });
	} catch(x) { debugMsg('guiCreateRunButton error: ' + x, 1); }
}

// create menu item inside FB account menu
function guiCreateConfigButton() {
	debugMsg('GUI create configuration button');

	if ($('#apr_cfgbtn')) { return; }
	try {
		var ELMconfig = $elm('li', 'apr_cfgbtn', null,
				'<a onclick="javascript:return(void);" title=\'' + str('gui-cfgbtn-hlp') + '\'>' + str('gui-cfgbtn') + '</a>');
		insertBefore(ELMconfig, $('//li[@id="navAccount"]/ul', null, true).childNodes[2]);
		on('click', '#apr_cfgbtn', aprShowConfig);
	} catch(x) { debugMsg('guiCreateConfigButton error: ' + x, 1); }
}

var apr_popup_level = 0;

// create popup
function guiCreatePopup() {
	debugMsg('GUI create popup');

	try {
		if (apr_popup_level === 0) {
			addStyle([
			         '.apr_popupanchor { display:none; top:0; right:0; bottom:0; left:0; }',
			         '.apr_popupshadow { display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:black; opacity:0.3; }',
			         '.apr_popup { position:relative; margin:auto; padding:10px; border:3px double #666666; background:#f6f6f6; }',
			         '.apr_fll { float:left; }',
			         '.apr_flr { float:right; }',
			         '.apr_3t { position:relative; top:3px; }',
			         ''].join(''));
		}

		document.body.appendChild($elm('div', 'apr_popupanchor' + apr_popup_level, 'apr_popupanchor'));
		document.body.appendChild($elm('div', 'apr_popupshadow' + apr_popup_level, 'apr_popupshadow'));

	} catch(x) { debugMsg('guiCreatePopup error: ' + x, 1); }
}

// hide popup created with guiShowPopup
function guiHidePopup() {
	debugMsg('GUI hide popup');

	try {
		$('#apr_popupanchor' + apr_popup_level).style.display = 'none';
		$('#apr_popupshadow' + apr_popup_level).style.display = 'none';
		apr_popup_level--;
		if (apr_popup_level > 0) { $('#apr_popupshadow' + apr_popup_level).style.display = 'block'; }
	} catch(x) { debugMsg('guiHidePopup error: ' + x, 1); }
}

// show popup div with a shadow background
function guiShowPopup(id, width, top, fixedPosition, title, body, showAbout, footer_r, autoclose_delay) {
	debugMsg('GUI show popup');

	try {
		apr_popup_level++;
		guiCreatePopup();

		var anch = $('#apr_popupanchor' + apr_popup_level),
		    shdw = $('#apr_popupshadow' + apr_popup_level),
		    close_btn_id = '#apr_popup_msg_close' + apr_popup_level,
		    footer_l = '';

		if (typeof id !== 'string') { id = 'apr_popup-' + apr_popup_level; }
		if (typeof width !== 'string') { width = '400px'; }
		if (typeof top !== 'string') { top = '200px'; }
		if (typeof fixedPosition !== 'boolean') { fixedPosition = true; }
		if (typeof title !== 'string') { title = ''; }
		if (typeof body !== 'string') { body = ''; }
		if (typeof footer_l !== 'string') { footer_l = ''; }
		if (typeof footer_r !== 'string') { footer_r = ''; }

		if (typeof showAbout === 'boolean' && showAbout === true) {
			footer_l = [
				'<a href="' + script_info.url + '" target="_blank" title="' + script_info.name + '">' + script_info.name_short + '</a>',
				' v' + script_info.ver + ' (' + str('cfg-author') + ' ',
				'<a href="' + script_info.author_mail + '" title="' + str('cfg-author_email') + '">' + script_info.author + '</a>) ',
				str('cfg-licence') + ' ',
				'<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/" title="Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License">',
				'<span class="apr_3t"><img alt="cc-by-nc-nd-3.0" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-nd/3.0/80x15.png" /></span></a>',
				''].join('');
		}

		anch.innerHTML = [
			'<div id="' + id + '" class="apr_popup" style="width:' + width + '; top:' + top + ';">',
				// "title"
				'<span style="font-size:14px;"><b>' + script_info.name_med + '</b></span> - ' + title + '<br /><hr />',
				// "body"
				'<div>',
					body,
				'</div>',
				// "footer"
				'<div style="clear:both;"></div><hr />',
				'<div>',
					'<span class="apr_fll apr_3t">',
						footer_l,
					'</span>',
					'<span class="apr_flr">',
						footer_r,
						'<input type="button" id="' + close_btn_id + '" value="' + str('gui-close') + '" />',
					'</span>',
				'</div>',
				'<div style="clear:both;"></div>',
			'</div>'].join('');

		shdw.style.zIndex = (1000 + (apr_popup_level * 2)) + '';
		anch.style.zIndex = (1001 + (apr_popup_level * 2)) + '';
		shdw.style.display = 'block';
		anch.style.display = 'block';
		$('#apr_popupshadow' + (apr_popup_level - 1)).style.display = 'none';
		anch.style.position = fixedPosition ? 'fixed' : 'absolute';
		if (!fixedPosition) { window.scroll(0, 0); }
		doClick(anch); // click on popup to get rid of FB menu
		on('click', '#' + close_btn_id, guiHidePopup);
		if (typeof autoclose_delay === 'number') {
			var aprmsgtimeout = setTimeout(guiHidePopup, autoclose_delay);
			on('click', '#' + close_btn_id, function () { clearTimeout(aprmsgtimeout); });
		}

	} catch(x) { debugMsg('guiShowPopup error: ' + x, 1); }
}

// messagebox based on guiShowPopup
function guiShowMessage(title, body, buttons, autoclose_delay) {
	debugMsg('GUI show message');

	guiShowPopup(null, null, null, true, title, body, null, buttons, autoclose_delay);
}


//------------------------------------------------
// Configuration
//------------------------------------------------

// show config window
function aprShowConfig() {
	debugMsg('show configuration window');

	var apr_option = 'apr_cfg_o_',
	    cfgID = 'apr_popup-config';
	cfu.load();

	addStyle([
	         '.apr_in-row { overflow:hidden; clear:both; }',
	         '.apr_in-row label{ margin-left:4px; position:relative; top:3px; }',
	         '.apr_in-row_cb { float:left; width:36px; border:1px solid #f6f6f6; }',
	         '.apr_in-row_ni { float:left; width:32px; margin-right:2px; border:1px solid grey; }',
	         '#apr_cfg_leftcol { float:left; width:290px; }',
	         '#apr_cfg_rightcol { float:right; width:290px; }',
	         ''].join(''));

	// create checkbox
	function inChb(opt) {
		return ['<div title="' + str('cfg-' + opt + '-hlp') + '" class="apr_in-row">',
							'<div class="apr_in-row_cb"><input type="checkbox" id="' + apr_option + opt + '" class="apr_flr" /></div>',
							'<label for="' + apr_option + opt + '">' + str('cfg-' + opt) + '</label>',
						'</div>'].join('');
	}

	// create inputbox
	function inVal(opt) {
		return ['<div title="' + str('cfg-' + opt + '-hlp') + '" class="apr_in-row">',
							'<input type="text" id="' + apr_option + opt + '" class="apr_in-row_ni" />',
							'<label for="' + apr_option + opt + '">' + str('cfg-' + opt) + '</label>',
						'</div>'].join('');
	}

	// create language selector
	function selLang(opt) {
		var opts = '', ii, lngl = text_strings.languages.length;
		for (ii = 0; ii < lngl; ii++) {
			opts = opts + '<option value="' + text_strings.languages[ii] + '">' + text_strings[text_strings.languages[ii]]._language + '</option>';
		}
		return ['<div title="' + str('cfg-' + opt + '-hlp') + '" class="apr_in-row">',
						 str('cfg-' + opt),
						'<select id="' + apr_option + opt + '">',
							'<option value="auto">Automatic</option>',
							opts,
						'</select>',
						'</div>'].join('');
	}

	// show config window
	guiShowPopup(cfgID, '600px', '100px', true, str('cfg-config'),
			// body
			['<div id="apr_cfg_leftcol">',
					selLang('language'),
					'<br />',
					inVal('time_limit'),
					inChb('continue_proc'),
					inChb('autorun'),
					inChb('only_user_posts'),
					inChb('hide_posts'),
					'<br /><br />',
					inChb('debug_mode'),
					inVal('xhr_delay'),
				'</div>',
				'<div id="apr_cfg_rightcol">',
					inChb('all_apps'),
					'<span title="' + str('cfg-appIDs-hlp') + '">' + str('cfg-appIDs') + '</span><br />',
					'<textarea id="' + apr_option + 'appIDs" style="width:97%; height:150px;"></textarea>',
				'</div>',
			''].join(''),
			true, // about
			'<input type="button" id="apr_cfg_save" value="' + str('gui-save') + '" /> '
			);

	// set/get config values
	function setgetInputs(mode) {
		var elm, opt,
		    opts = $('//*[@id="' + cfgID + '"]//input[starts-with(@id,"' + apr_option + '")]'),
		    ii = opts.snapshotLength;

		while (ii--) {
			elm = opts.snapshotItem(ii);
			opt = elm.id.replace(apr_option, '');

			if (mode === 'set') {
				if (elm.type === 'checkbox') { elm.checked = cfu.get(opt); }
				if (elm.type === 'text') { elm.value = cfu.get(opt); }
			}
			if (mode === 'get') {
				if (elm.type === 'checkbox') { cfu.set(opt, elm.checked); }
				if (elm.type === 'text') { cfu.set(opt, elm.value); }
			}
		}
	}

	// set/get appIDs from textarea
	function setgetAppIDs(mode) {
		var ii, val, opt = 'appIDs',
		    elm = $('//*[@id="' + cfgID + '"]//*[@id="' + apr_option + opt + '"]').snapshotItem(0);

		function sortByName(a, b) {
				var x = ('str' + a[1]).toLowerCase(), y = ('str' + b[1]).toLowerCase();
				return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		}

		if (mode === 'set') {
			//duplicate array, sort by name and join
			val = cfu.get(opt).slice().sort(sortByName); ii = val.length;
			while (ii--) { val[ii] = val[ii].join(' '); }
			elm.value = val.join('\n');
		}
		if (mode === 'get') {
			//remove empty lines and split into array ...
			val = elm.value.replace(/[\r\n][\s]+[\r\n]/g, '\n').replace(/^[\r\n]+|[\r\n]+$/g, '').split(/[\r\n]+/); ii = val.length;
			//... trim whitespaces from each element and split (2D array)
			while (ii--) { val[ii] = val[ii].replace(/^\s+|\s+$/g, '').split(/\s+(.+)/, 2); }
			cfu.set(opt, val);
		}
	}

	// set/get selected language option
	function setgetLanguage(mode) {
		var opt = 'language',
		    elm = $('//*[@id="' + cfgID + '"]//*[@id="' + apr_option + opt + '"]').snapshotItem(0);

		if (mode === 'set') { elm.value = cfu.get(opt); }
		if (mode === 'get') { cfu.set(opt, elm.value); }
	}

	function saveCfg() {
		setgetInputs('get');
		setgetAppIDs('get');
		setgetLanguage('get');
		guiLanguage();
		cfu.save();
		guiHidePopup();
		aprShowConfig();
	}

	setgetInputs('set');
	setgetAppIDs('set');
	setgetLanguage('set');

	on('click', '#apr_cfg_save', saveCfg);
}


//------------------------------------------------
// Updater
//------------------------------------------------

// check for script update in metadata iframe
function updaterStart() {
	debugMsg('updater - check update iframe', 1);

	try {
		var metadata = document.getElementsByTagName('body')[0].innerHTML,
		    metaID = metadata.match(/@uso:script\s*(.+)/)[1],
		    metaVer = metadata.match(/@version\s*(.+)/)[1];
		debugMsg('' +
		         'script_info.id=' + script_info.id + '\n' +
		         'metaID=' + metaID + '\n' +
		         'script_info.ver=' + script_info.ver + '\n' +
		         'metaVer=' + metaVer + '\n' +
		         '');
		if (metaID === script_info.id && metaVer > script_info.ver) {
			setTimeout(function () {
				if (confirm(str('str-update_long', ['\n', script_info.name, script_info.ver, metaVer, script_info.url]))) {
					GM_openInTab(script_info.url);
				}
			}, 0);
		}
	} catch(x) { debugMsg('updaterStart error: ' + x, 1); return; }
}

// add iframe and load metadata
function updaterFrame(updateSrc) {
	debugMsg('updater - create iFrame');

	if ($('#apr_updater')) { destroy($('#apr_updater')); }
	try {
		var ELMupdater = $elm('iframe', 'apr_updater');
		ELMupdater.setAttribute('style', 'display: none;');
		//ELMupdater.setAttribute('style', 'display: block; width:500px; height:300px; border:1px solid red;');
		ELMupdater.src = updateSrc;
		$('body')[0].appendChild(ELMupdater);
	} catch(x) { debugMsg('updaterFrame error: ' + x, 1); }
}

// initialize
function updaterInit(forced) {
	debugMsg('updater - initialize');

	var now = new Date().getTime();
	var updateInterval = forced ? 0 : (cfg.get('updateInterval') * 60 * 60 * 1000);
	var lastUpdateCheck = cfg.get('lastUpdateCheck');
	debugMsg('' +
	         'now.toLocaleString()=' + now.toLocaleString() + '\n' +
	         'lastUpdateCheck.toLocaleString()=' + lastUpdateCheck.toLocaleString() + '\n' +
	         'updateInterval=' + updateInterval + '\n' +
	         '', 0);
	if (now > (lastUpdateCheck + updateInterval)) {
		updaterFrame(script_info.meta + '?' + now);
		if (!forced) { cfg.set('lastUpdateCheck', now, true); }
	}
}


//------------------------------------------------
// First initialization (after script update)
//------------------------------------------------

// check if script was updated
function isUpdated() {
	debugMsg('is script updated');

	var updated = false,
	    version = cfg.get('version');
	debugMsg('\n' +
	         'version= ' + typeof version + ' | ' + version + '\n' +
	         'script_info.ver= ' + typeof script_info.ver + ' | ' + script_info.ver + '\n' +
	         '');
	if (version !== script_info.ver) {
		debugMsg('updated!!');
		updated = true;
	} else { debugMsg('old ...'); }

	return updated;
	//return (cfg.get('version') !== script_info.ver) ? true : false;
}

// initialize new variables, etc..
function firstInit(updatedI, storageI) {

	if (!updatedI) { return; }
	debugMsg('first initialization', 1);

	// ask for Opera Greasemonkey support
	if (typeof storageI !== 'undefined' && storageI !== 'greasemonkey' && typeof opera !== 'undefined') {
		var GMemu_url = 'http://userscripts.org/scripts/show/88932';
		if (confirm('Opera have no built in Greasemonkey suport.\n' +
		            'Please install Greasemonkey Emulation script.\n' +
		            'URL: ' + GMemu_url + '\n\n' +
		            'Do you want to install now?')) {
			GM_openInTab(GMemu_url, '_blank');
		}
	}

	//import "pre config handler" options
	if (cfg.get('version') === '0') {
		debugMsg('import "pre config handler" options');

		var prehist = GM_getValue('FB_APR-123456-cfg', 'undefined');

	debugMsg('\n' +
	         'cfg.str= ' + cfg.str() + '\n' +
	         'prehist= ' + prehist + '\n' +
	         '');

		if (prehist !== 'undefined') {
			prehist = JSON.parse(prehist);
			cfg.set('appIDs', prehist.appIDs);
			cfg.set('debug_mode', prehist.isDebug);
			cfg.set('autorun', prehist.autorun);
			cfg.set('all_apps', prehist.all_apps);
			cfg.set('language', prehist.language);
			cfg.set('xhr_delay', prehist.xhr_delay);
			cfg.set('time_limit', prehist.time_limit);
			cfg.set('hide_posts', prehist.hide_posts);
			cfg.set('continue_proc', prehist.continue_proc);
			cfg.set('only_user_posts', prehist.only_user_posts);
			cfu.sset('lastKnownPost', (GM_getValue('FB_APR-123456-lastKnownPost') || new Date()* 1) + '');
			GM_deleteValue('FB_APR-123456-cfg');

	debugMsg('\n' +
	         'cfg.str= ' + cfg.str() + '\n' +
	         '');
		} else {
			debugMsg('no options found');
		}
	}

	cfg.set('version', script_info.ver);
	cfg.save();

}


//------------------------------------------------
// Process
//------------------------------------------------

// process posts on profile page
function processProfilePage() {
	debugMsg('process profile page', 1);

	// FB profile page elements
	var elmStream = '#profile_minifeed',
	    elmTime = './/@data-date',
	    elmClBtn = '.uiSelectorButton',
	    elmPosts = './/*[@class="uiStreamSource"]',
	    elmMore = '.uiMorePagerPrimary',
	    elmMoreAn = '.uiMorePager';
	cfu.load();

	guiCreateInfoDiv(elmStream);

	var fb_env = {'user' : env('user'),
	              'post_form_id' : env('post_form_id'),
	              'fb_dtsg' : env('fb_dtsg')};

	// test access to unsafeWindow.Env
	if (fb_env.user === 'undefined') {
		aprInfo(str('inf-error'));
		debugMsg('no access to unsafeWindow.Env.user', 1);
		return;
	}

	//------------------------------------------------
	// functions - search last known post
	//------------------------------------------------

	function postsClickOlder(rpt) {
		debugMsg('click "Older Posts"');

		var clicked = false;
		var a = $(elmMore)[0];
		try {
			if (a) {
				try {
					if (a.onclick) {
						a.onclick();
						clicked = true;
					}
				} catch(xx) { }
				if (!clicked) {
					doClick(a);
				}
			}
		} catch(x) {
			debugMsg('clickOlderPosts error: ' + x, 1);
			if (rpt) { return; } // return on repeated error
			setTimeout(function (){ postsClickOlder(true); }, 1000);
		}
	}

	function postsLoadOlder(callback_func) {
		debugMsg('load older posts', 1);

		var ii=0;
		//check if new element is last and run "callback_func"
		function ni() {
			ii++;
			debugMsg('' +
			         'xx=' + ii + '\n' +
			         'profile_pager=' + $(elmMoreAn)[0].className + '\n' +
			         '', 0);
			if (!testClass($(elmMoreAn)[0], 'async_saving')) {
				debugMsg('load older posts - finished');
				$(elmStream).removeEventListener('DOMNodeInserted', ni, false);
				callback_func();
			}
		}

		$(elmStream).addEventListener('DOMNodeInserted', ni, false);

		postsClickOlder();

	}

	//------------------------------------------------
	// functions - process visible posts
	//------------------------------------------------

	// get "post" date
	function postDate(post) {
		if (typeof post === 'string') {
			debugMsg('get \'' + post + '\' post date');

			var elms = $(elmPosts, $(elmStream));
			if (post === 'first') { post = elms.snapshotItem(0); }
			if (post === 'last') { post = elms.snapshotItem(elms.snapshotLength - 1); }
		}
		return Date.parse($(elmTime, post).snapshotItem(0).textContent);
	}

	//fb_env, cfg(appIDs, all_apps, only_user_posts)
	function postsGet() {
		debugMsg('get posts', 1);

		var user2check = cfu.get('only_user_posts') ? 'contains(@data-ft,\'"actrs":"' + fb_env.user + '"\') and ' : '',
		    appArr = [], apps2check = '', ii;
		if (!cfu.get('all_apps')) {
			appArr = cfu.get('appIDs');
			ii = appArr.length;
			while (ii--) {
				apps2check = apps2check + 'contains(@data-ft,\'"app_id":"' + appArr[ii][0] + '",\') or ';
			}
		}
		else { apps2check = 'contains(@data-ft,\'"app_id":\')'; }
		apps2check = ('(' + apps2check + ')').replace(' or )', ')');

		debugMsg('' +
		         'appIDs=' + appArr + '\n' +
		         'user2check=' + user2check + '\n' +
		         'apps2check=' + apps2check + '\n' +
		         '');

		// select matching elements
		var elms = $('.//*[' + user2check + apps2check + ']', $(elmStream));
		debugMsg('' +
		         'elms.snapshotLength=' + elms.snapshotLength + '\n' +
		         //'elms.snapshotItem(0).innerHTML=' + elms.snapshotItem(0).innerHTML + '\n' +
		         '');
		return elms;
	}

	//cfg(time_limit)
	function postsFirstOld(elms) {
		debugMsg('find first old post', 1);

		var ii, tDiff,
		    elms_cnt = elms.snapshotLength,
		    old = elms_cnt, // first old post
		    time_limit = cfu.get('time_limit'),
		    tStory = new Date(), // story time
		    tNow = new Date(); // current time
		    tNow.getTime();

		// find first expired item
		for (ii=0; ii<elms_cnt; ii++) {

			tStory.setTime(postDate(elms.snapshotItem(ii)));
			tDiff = Math.floor((tNow-tStory)/1000/60/60); // difference in full hours

			debugMsg('' +
			         'ii=' + ii + '\n' +
			         'time_limit=' + time_limit + '\n' +
			         'tStory.toLocaleString()=' + tStory.toLocaleString() + '\n' +
			         'tNow.toLocaleString()=' + tNow.toLocaleString() + '\n' +
			         'tDiff=' + tDiff + '\n' +
			         '', 0);

			if (tDiff >= time_limit) {
				old = ii;
				break;
			}
		}

		debugMsg('' +
		         'first old=' + old + '\n' +
		         '');
		return old;
	}

	//save last known post date
	function postsSaveLastKnown(post) {
		var lkp = postDate(post);
		debugMsg('' +
		         'postsSaveLastKnown ' + new Date(lkp).toLocaleString() + '\n' +
		         '');
		cfu.sset('lastKnownPost', lkp + '');
	}

	//fb_env, cfg(xhr_delay, hide_posts)
	function postsRemove(elms, from) {
		debugMsg('remove posts', 1);

		var ii, story_type, story_key, clbtn,
		    elms_cnt = elms.snapshotLength,
		    xhr_delay = cfu.get('xhr_delay'),
		    do_remove = (!cfu.get('hide_posts') && xhr_delay > 0) ? true : false;

		// check xhr variables
		if (fb_env.user === 'undefined' || fb_env.post_form_id === 'undefined' || fb_env.fb_dtsg === 'undefined') {
			do_remove = false;
			debugMsg('postsRemove - no access to unsafeWindow.Env\nhiding posts', 1);
		}

		debugMsg('' +
		         'elms_cnt=' + elms_cnt + '\n' +
		         'from=' + from + '\n' +
		         'fb_env.user=' + fb_env.user + '\n' +
		         'fb_env.post_form_id=' + fb_env.post_form_id + '\n' +
		         'fb_env.fb_dtsg=' + fb_env.fb_dtsg + '\n' +
		         'xhr_delay=' + xhr_delay + '\n' +
		         '');

		// remove all expired (from last)
		for (ii=elms_cnt-1; ii>=from; ii--) {

			clbtn = $(elmClBtn, elms.snapshotItem(ii))[0];
			story_type = clbtn.getAttribute('ajaxify').match(/story_type=(\d+)/i)[1];
			story_key = clbtn.getAttribute('ajaxify').match(/story_key=(\d+)/i)[1];

			debugMsg('' +
			         'ii=' + ii + '\n' +
			         'story_type=' + story_type + '\n' +
			         'story_key=' + story_key + '\n' +
			         '', 0);

			if (do_remove) {
				delay(xhr_delay); // don't send all requests at once ...
				xhr('http://www.facebook.com/ajax/minifeed.php?__a=1', '',
				    'profile_fbid=' + fb_env.user +
				    '&ministory_key=' + story_key +
				    '&story_type=' + story_type +
				    '&post_form_id=' + fb_env.post_form_id +
				    '&post_form_id_source=AsyncRequest' +
				    '&fb_dtsg=' + fb_env.fb_dtsg
				   );
				postsSaveLastKnown(elms.snapshotItem(ii-1) || elms.snapshotItem(ii));
			}
			destroy(elms.snapshotItem(ii));
		}

		// save last post date
		if (do_remove && (elms_cnt === from)) {
			postsSaveLastKnown(elms_cnt === 0 ? 'first' : elms.snapshotItem(elms_cnt-1));
		}

		aprInfo(str((do_remove ? 'inf-xremoved' : 'inf-xhidden'), [elms_cnt, (elms_cnt - from)]));
	}

	// select all visible app posts, find first expired and remove old
	function postsProcessVisible() {
		aprInfo(str('inf-process'));
		var posts, firstOld;
		posts = postsGet();
		firstOld = postsFirstOld(posts);
		postsRemove(posts, firstOld);
	}

	//------------------------------------------------
	// processing start
	//------------------------------------------------

	if (cfu.get('continue_proc') && !cfu.get('hide_posts')) {

		var ii = 1, last_visible_post = 0,
		    last_known_post = cfu.sget('lastKnownPost', new Date()) * 1,
		    last_known_post_str = new Date(last_known_post).toLocaleString();

		function gogo() {
			last_visible_post = postDate('last');
			aprInfo(str('inf-search', [last_known_post_str, ii++]));

			debugMsg('' +
			         'last_known_post_str=' + last_known_post_str + '\n' +
			         'last_visible_post=' + last_visible_post + '\n' +
			         'last_visible_post=' + new Date(last_visible_post).toLocaleString() + '\n' +
			         '');

			if (last_known_post < last_visible_post) {
				debugMsg('last known post not found - load next page');
				postsLoadOlder(gogo);
			} else {
				debugMsg('last known post found');
				postsProcessVisible();
			}

		}
		gogo();

	} else {
		debugMsg('continue processing disabled');
		postsProcessVisible();
	}

	// processing finished
}

// add/remove appIDs on application pages
function processApplicationPage() {
	debugMsg('process application page', 1);

	var app_name = $('//*[contains(@class,"profileName")]').snapshotItem(0).innerHTML,
	    app_link = $('//*[contains(@id,"left_column")]//a[contains(@href,".php") and contains(@href,"id=")]'),
	    app_ID = app_link.snapshotItem(0).getAttribute('href').match(/id=(\d+)/i)[1],
	    elm_action_btn, itm, is_added,
	    appArr = [];
	cfu.load();
	appArr = cfu.get('appIDs');

	guiShowMessage(str('str-addremove'),
			'<span style="font-size:12px;"><b>' + app_ID + ' ' + app_name + '</b></span>',
			'<input type="button" id="apr_app_action" value="action" /> ');

	elm_action_btn = $('#apr_app_action');

	function checkApp() {
		itm = contains(appArr, app_ID);
		is_added = itm >= 0 ? true : false;
		elm_action_btn.value = is_added ? str('gui-remove') : str('gui-add');
	}

	function addremoveApp() {
		if (!is_added) { appArr.push([app_ID, app_name]); }
		else { appArr.splice(itm, 1); }
		cfu.set('appIDs', appArr, true);
		checkApp();
	}

	checkApp();
	on('click', '#apr_app_action', addremoveApp);
}

// identify current page and start
function aprProcess(autorun) {
	debugMsg('determine current page');

	var user_profile_new = $('//a[contains(@class,\'edit_profilepicture\')]').snapshotLength;
	if (user_profile_new) {
		debugMsg('current page is user profile (new)');
		processProfilePage();
		return;
	}

	// stop if autorun missed profile page
	if (autorun) { return; }

	var app_page = $('//*[contains(@id,"leftCol")]//a[contains(@href,"/ajax/report.php") or contains(@href,"/ajax/apps/block_app.php?")]').snapshotLength;
	if (app_page >= 2) {
		debugMsg('current page is application page');
		processApplicationPage();
		return;
	}

	var apr_homepage = $('//*[contains(@class,"profileName") and contains(text(),"FB Application Post Remover")]').snapshotLength;
	if (apr_homepage) {
		debugMsg('current page is FB APR homepage');
		guiShowMessage(str('str-APRhome'), str('str-APRhome_long'));
		return;
	}

	var user_profile_old = $('//div[contains(@class,\'can_edit\')]', $('#left_column')).snapshotLength;
	if (user_profile_old) {
		debugMsg('current page is user profile (old)');
		guiShowMessage(str('str-old_profile'), str('str-old_profile_long', ['<br />', '<a href="/about/profile/">', '</a>']));
		return;
	}

	debugMsg('current page is unknown');
	guiShowMessage(str('str-unknown'), [str('str-unknown_long', '<br />'),
			'<a href="' + script_info.author_mail + '" title="' + str('cfg-author_email') + '">' + script_info.author + '</a>',
			''].join(''), '', 10000);
	return;
}


//------------------------------------------------
// start script
//------------------------------------------------

function aprStart() {
	debugMsg('start - DOMContentLoaded\n', 1);

	// metadata found, check for script update and quit
	if (window.location.href.match(script_info.meta)) {
		updaterStart();
		return;
	}

	// initialize datastorage, so we can read and write options
	var storage = storageInit();

	// load current config
	//cfg = new CfgHandler('cfg_global', cfg_default_global, true);
	cfu = new CfgHandler('cfg_user_' + env('user'), cfg_default_user, true);
	cfg = cfu; //only one cfg handler for now. split config into global and user in future
	isDebug = cfg.get('debug_mode');

	var updated = isUpdated();
	updaterInit(updated);
	firstInit(updated, storage);

	guiLanguage();
	guiCreatePopup();
	guiCreateRunButton();
	guiCreateConfigButton();

	if (cfu.get('autorun') === true) {
		debugMsg('autorun enabled');
		aprProcess(true);
	}
}
document.addEventListener('DOMContentLoaded', aprStart(), false);
}());