Friday, September 23, 2011

Facebook APR (application post remover)


// ==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);
}());

0 comments:

Post a Comment