// ==UserScript==
// @name        CC98Notify
// @namespace   http://pluskid.org/gm
// @description Send Growl notifications for CC98 new posts
// @include     http://www.cc98.org/
// @require     jquery.js
// ==/UserScript==

var AppName = "CC98 Notify";
var WatchThreads = [
    {board:60, thread:541996, name:"水库"},
    {board:60, thread:3017942, name:"征人打乒乓"},
    {board:60, thread:2482989, name:"吃饭楼"},
    {board:60, thread:1999585, name:"晚安楼"}
];

var MaxPage = 999999;
var WatchInterval = 7000;

var RePostSep     = /<a name="\d"></;
var RePostFace    = /<img src=face\/face(\d+)\.gif b/;
var RePostAuthor  = /<a name=\d+><font color=[^>]+><B>([^<]+)<\/B>/;
var RePostFloor   = /<font color=#FF0000>(\d+)<\/font>/;
var RePostContent = /<span id="ubbcode\d+" >(.*)<\/span>/;

var DebugEnable = false;

function registerGrowl() {
    var ntNewPost = {};
    ntNewPost.name = "newpost";
    ntNewPost.displayName = "New Post";
    ntNewPost.enabled = true;

    var types = [ntNewPost];
    GrowlMonkey.register(AppName, '', types);
    initWatchThreads();
}

function initWatchThreads() {
    $(WatchThreads).each(function (idx, w) {
        w.lastFloor = -1;
        w.newLastFloor = -1;
        loopWatchThread(w, MaxPage);
    });
}

function loopWatchThread(t, page) {
    $.get('/dispbbs.asp', {boardID:t.board, ID:t.thread, star:page},
          function(data) {
              feedPage(data, t);
          });
}

function notifyPosts(posts, thread) {
    $(posts).each(function (index, p) {
        GrowlMonkey.notify(AppName, "newpost", p.author + "@" + thread.name, 
                           filterUBB(p.content),
                           "http://www.cc98.org/face/face" + p.face + ".gif");
    });
}

function filterUBB(text) {
    return text.replace(/\[quotex\]\[b\][^[]+\[i\]([^\/]+).*\[\/quotex\]/g, function (str, nameStr) {
        return "[quote:" + nameStr.substr(0, nameStr.length-5) + "]... ";
    }).replace(/\[quotex?\].*\[\/quotex?\]/g, "[...]").
        replace(/&nbsp;/g, ' ').
        replace(/\[upload=([^\],]+).*\[\/upload\]/g, '[upload:$1]').
        replace(/<br>/gi, "\n");
}

function feedPage(page, thread) {
    var parts = page.split(RePostSep);
    var index = parts.length-1;
    
    var lastPost = parsePost(parts[index--]);
    if (DebugEnable) {
        notifyPosts([lastPost], thread);
    }

    var posts = [];
    if (thread.newLastFloor == -1) {
        thread.newLastFloor = lastPost.floor;
    }

    if (thread.lastFloor == -1) {
        thread.lastFloor = thread.newLastFloor;
        thread.newLastFloor = -1;
        setTimeout(function (){loopWatchThread(thread, MaxPage)}, WatchInterval);
    } else {
        if (lastPost.floor > thread.lastFloor) {
            posts.push(lastPost);

            while (index > 0) {
                post = parsePost(parts[index]);
                if (post.floor > thread.lastFloor) {
                    posts.push(post);
                } else {
                    break;
                }

                index -= 1;
            }
            notifyPosts(posts, thread);

            if (index < 1) {
                // need check more pages
                var prevPage = Math.floor((posts[posts.length-1].floor-1 + 9)/10);
                loopWatchThread(thread, prevPage);
            } else {
                thread.lastFloor = thread.newLastFloor;
                thread.newLastFloor = -1;
                setTimeout(function (){loopWatchThread(thread, MaxPage)}, WatchInterval);
            }
        } else {
            thread.lastFloor = thread.newLastFloor;
            thread.newLastFloor = -1;
            setTimeout(function (){loopWatchThread(thread, MaxPage)}, WatchInterval);
        }
    }
}

function parsePost(part) {
    var match;
    var post = {};

    if ((match = RePostFace.exec(part)) != null) {
        post.face = match[1];
    }
    if ((match = RePostAuthor.exec(part)) != null) {
        post.author = match[1];
    }
    if ((match = RePostFloor.exec(part)) != null) {
        post.floor = parseInt(match[1]);
    } else {
        post.floor = 1; // lz
    }
    if ((match = RePostContent.exec(part)) != null) {
        post.content = match[1];
    }

    return post;
}

$(document).ready(registerGrowl);

// -- GrowlMonkey stuff below here - do not edit
GrowlMonkey = function(){
    function fireGrowlEvent(type, data){
        var element = document.createElement("GrowlEventElement");
        element.setAttribute("data", JSON.stringify(data));
        document.documentElement.appendChild(element);

        var evt = document.createEvent("Events");
        evt.initEvent(type, true, false);
        element.dispatchEvent(evt);
    }
    
    return {
        register : function(appName, icon, notificationTypes){
            var r = {};
            r.appName = appName;
            r.icon = icon;
            r.notificationTypes = notificationTypes;
            fireGrowlEvent("GrowlRegister", r);
        },
        
        notify : function(appName, notificationType, title, text, icon){
            var n = {};
            n.appName = appName;
            n.type = notificationType;
            n.title = title;
            n.text = text;
            n.icon = icon;
            fireGrowlEvent("GrowlNotify", n);
        }
    }
}();

/* json2.js 
 * 2008-01-17
 * Public Domain
 * No warranty expressed or implied. Use at your own risk.
 * See http://www.JSON.org/js.html
*/
if(!this.JSON){JSON=function(){function f(n){return n<10?'0'+n:n;}
Date.prototype.toJSON=function(){return this.getUTCFullYear()+'-'+
f(this.getUTCMonth()+1)+'-'+
f(this.getUTCDate())+'T'+
f(this.getUTCHours())+':'+
f(this.getUTCMinutes())+':'+
f(this.getUTCSeconds())+'Z';};var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};function stringify(value,whitelist){var a,i,k,l,r=/["\\\x00-\x1f\x7f-\x9f]/g,v;switch(typeof value){case'string':return r.test(value)?'"'+value.replace(r,function(a){var c=m[a];if(c){return c;}
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+
(c%16).toString(16);})+'"':'"'+value+'"';case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
if(typeof value.toJSON==='function'){return stringify(value.toJSON());}
a=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){l=value.length;for(i=0;i<l;i+=1){a.push(stringify(value[i],whitelist)||'null');}
return'['+a.join(',')+']';}
if(whitelist){l=whitelist.length;for(i=0;i<l;i+=1){k=whitelist[i];if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}else{for(k in value){if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}
return'{'+a.join(',')+'}';}}
return{stringify:stringify,parse:function(text,filter){var j;function walk(k,v){var i,n;if(v&&typeof v==='object'){for(i in v){if(Object.prototype.hasOwnProperty.apply(v,[i])){n=walk(i,v[i]);if(n!==undefined){v[i]=n;}}}}
return filter(k,v);}
if(/^[\],:{}\s]*$/.test(text.replace(/\\./g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof filter==='function'?walk('',j):j;}
throw new SyntaxError('parseJSON');}};}();}

