Greasy Fork

camamba-utils

utils for modifying the DOM and fetching info of a webpage of camamba

目前为 2016-09-01 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.cloud/scripts/20132/145190/camamba-utils.js

// ==UserScript==
// @name            camamba-utils
// @name:de         camamba-utils
// @namespace       dannysaurus.camamba
// @version         0.1
// @license         MIT License
// @description     utils for modifying the DOM and fetching info of a webpage of camamba
// @description:de  utils for modifying the DOM and fetching info of a webpage of camamba
// ==/UserScript==

/*
grant           GM_getValue
grant           GM_setValue
grant           GM_deleteValue
grant           GM_listValues
connect         camamba.com
require         https://greasyfork.org/scripts/22752-object-utils/code/object-utils.js
require         https://greasyfork.org/scripts/20131-html-utils/code/html-utils.js
*/
var LIB = LIB || {};
LIB.camambaUtils = (function(){
    var objUtils = LIB.objectUtils;
    var htmlUtils = LIB.htmlUtils;
    /**
     * Wrapper for a 'button' html Element.
     * The initial class attribute is <code>class='smallbutton'</code>.
     * @param {function} [callback] - The callback function for the <code>onClick<code> event
     * @param {string} [text] - The content text of the element (text shown on the button)
     * @param {string} [id] - The value for the <code>id</code> attribute
     * @constructor
     */
    function ButtonSmall(callback, text, id) {
        if (!(this instanceof  ButtonSmall)) {
            return new ButtonSmall(callback, text, id);
        }
        htmlUtils.Button.call(this, 'smallbutton', callback, text, id);
    }
    objUtils.extend(ButtonSmall).from(htmlUtils.Button);

    /**
     * Wrapper for a 'button' html Element.
     * The initial class attribute is <code>class='tinybutton'</code>.
     * @param {function} [callback] - The callback function for the <code>onClick<code> event
     * @param {string} [text] - The content text of the element (text shown on the button)
     * @param {string} [id] - The value for the <code>id</code> attribute
     * @constructor
     */
    function ButtonTiny(callback, text, id) {
        if (!(this instanceof ButtonTiny)) {
            return new ButtonTiny(callback, text, id);
        }
        htmlUtils.Button.call(this, 'tinybutton', callback, text, id);
        this.domElement.style = 'margin:0;padding:0;width:auto;';
    }
    objUtils.extend(ButtonTiny).from(htmlUtils.Button);

    /**
     * Wrapper for a 'Select' html element for selecting camamba users.
     * The options will be generated from the list of users.
     * The initial class attribute is <code>class='smallselect'</code>.
     * @param {Object<string, User>[]} users - The list of users shown as options
     * @param {boolean} [isShowGivenNames] - <code>true</code> has options shown from the custom name if given instead of the camamba username
     * @param {function} [callback] - callback function triggered by the events <code>OnChange</code>, <code>OnFocus</code> and <code>KeyUp</code>
     * @param {string} [id] - The value for the <code>id</code> attribute
     * @constructor
     */
    function SelectUsers(users, isShowGivenNames, callback, id) {
        if (!(this instanceof SelectUsers)) {
            return new SelectUsers(users, isShowGivenNames, callback, id);
        }
        htmlUtils.Select.call(this, "smallselect", callback, id);
        Object.defineProperties(this, {
            /**
             * list of users of type {{uid:User}}
             */
            users: {
                get: function () { return users; },
                set: function (value) {
                    users = value;
                    SelectUsers.prototype.updateOptions.call(this);
                },
                enumerable: true, configurable: true
            },
            /**
             * <code>true</code> shows the custom given names
             * <code>false</code> shows the camamba names
             */
            isShowGivenNames: {
                get: function () { return isShowGivenNames; },
                set: function (value) {
                    var booleanValue = !!value;
                    if (booleanValue !== keeper.get(_idx).isShowGivenNames) {
                        isShowGivenNames = booleanValue;
                        SelectUsers.prototype.updateOptions.call(this);
                    }
                },
                enumerable: true, configurable: true
            }
        });
        SelectUsers.prototype.updateOptions.call(this);
    }
    SelectUsers.prototype = {
        constructor: SelectUsers,
        /**
         * Recreates the options from the current list of users.
         */
        updateOptions: function() {
            var sortUsers = [];
            var remainingUsers = {};
            for (var i = 0; i <= this.domElement.length - 1; i++) {
                var userInSelect = this.domElement[i].user;
                if (userInSelect) {
                    if (!this.users[userInSelect.uid]) { // deleted users
                        this.domElement.remove(i);
                    } else { // remaining users
                        remainingUsers[userInSelect.uid] = true;
                        sortUsers.push({user: userInSelect, selected: this.domElement[i].selected});
                    }
                }
            }
            Object.keys(this.users).forEach(function(uid) {
                if (!remainingUsers[uid]) { // additional users
                    var user = users[uid];
                    sortUsers.push({user: user, selected: false});
                    /**
                     * Html 'Option' Child of a Html 'Select' Element that holds a User
                     * @type {HTMLOptionElement}
                     * @property {User} user - The User related to the option
                     */
                    this.domElement.add(document.createElement('OPTION'));
                }
            }, this);
            this.domElement.length = sortUsers.length;
            var optionTextProp = _isShowGivenNames ? "name" : "uname";
            sortUsers.sort(function (a, b) {
                var nameA = a.user[optionTextProp].toLowerCase();
                var nameB = b.user[optionTextProp].toLowerCase();
                if (nameA < nameB) {
                    return -1;
                }
                if (nameA > nameB) {
                    return 1;
                }
                return 0;
            });
            sortUsers.forEach(function (opt, i) {
                this.domElement[i].text = opt.user[optionTextProp];
                this.domElement[i].value = opt.user.uid;
                this.domElement[i].user = opt.user;
                this.domElement[i].selected = opt.selected;
            }, this);
            // refresh select
            this.domElement.value = this.domElement.options[this.domElement.selectedIndex].value;
        },
        /**
         * Returns the user of the selected option
         * @returns {User} The current selected user
         */
        getSelectedUser: function() {
            return this.domElement.options[this.domElement.selectedIndex].user;
        }
    };

    var PageInfo = (function(){
        var isInGerman = location.hostname.indexOf("de.camamba.com") >= 0;
        var urlRoute = /^\/(.+?)(?:_de)?\.php.*/g.exec(location.pathname)[1];
        /**
         * Verifies an url, if it loads the German version of camamba.
         * @param {string} url The url for a camamba Page.
         * @returns {boolean} <code>true</code> if the url will request a camamba Page in German.
         */
        var urlIsGerman = function (url) {
            return (url.indexOf('www.de.camamba.com') >= 0);
        };
        /**
         * Transforms the url of a camamba Page to whether English or German forced.
         * @param {string} url - The url for a cammaba Page.
         * @param {Object.<string,string>[]} [queryParamsObj] - A key-value Object for additional query parameter to be attached to the url.
         * @param {boolean} [isResultInGerman] - <code>true</code> results a German-forced url, <code>false</code> results English forced.
         * @returns {string} The localized url
         */
        var urlLocalized = function (url, queryParamsObj, isResultInGerman) {
            var localizedUri = url;
            if (typeof isResultInGerman === "undefined") { isResultInGerman = isInGerman; }
            if (isResultInGerman && !urlIsGerman(url)) {
                localizedUri = url
                    .replace("www.camamba.com", "www.de.camamba.com")
                    .replace(".php", "_de.php");
            } else if (!isResultInGerman && urlIsGerman(url)) {
                localizedUri = url
                    .replace("www.de.camamba.com", "www.camamba.com")
                    .replace("_de.php", "php");
            }
            var queryParams = '';
            if (queryParamsObj) {
                var hasParams = url.indexOf('.php?') >= 1;
                Object.keys(queryParamsObj).forEach(function (key) {
                    var sep = (hasParams ? '&' : '?');
                    var value = queryParamsObj[key];
                    queryParams += sep + key + '=' + value;
                    hasParams = true;
                });
            }
            return localizedUri + queryParams;
        };
        return {
            isInGerman: isInGerman,
            route: urlRoute,
            urlLocalized: urlLocalized
        };
    })();
    /**
     * Represents a camamba user.
     * Intitially it tries to load the User from the database with the given uid.
     * @constructor
     * @param {string|number} uid - the users camamba uid.
     */
    function User(uid) {
        if (!(this instanceof  User)) {
            return new User(uid);
        }
        var _uid = parseInt(uid); // camamba user ID (readonly)
        var _key = 'uid' + _uid; // key for storing (readonly)
        var _hasChanged = true;
        var backingFields = {
            uname: "", name: "", note: "",
            age: Number.NaN, gender: "",
            isPremium: false, isReal: false, isSuper: false,
            location: {}, distanceInKm: Number.NaN, gps: "", place: "",
            online: {}, isOnlineNow: false, lastSeen: new Date("invalid"),
            room: {}, roomId: "", roomName: "",
            lastUpdated: Date.now(), lastChanged: Date.now()
        };
        /**
         * Sets  the value of a private field.
         * and updates <code>lastUpdated</code> and <code>hasChanged</code>.
         * @param {string} fieldName - name of the backing field
         * @param {*} val - new value to set the field with
         */
        var setField = function(fieldName, val) {
            backingFields.lastUpdated = Date.now();
            if (backingFields[fieldName] !== val) {
                backingFields[fieldName] = val;
                backingFields.lastChanged = backingFields.lastUpdated;
                _hasChanged = true;
            }
        };
        /**
         * Gets the value of a private field.
         * @param {string} fieldName - name of the backing field
         * @returns {*} value of the backing field
         */
        var getField = function(fieldName) {
            return backingFields[fieldName];
        };

        Object.defineProperties(getField("location"), {
            distanceInKm: {
                get: function() {
                    return getField("distanceInKm");
                },
                set: function(val) {
                    setField("distanceInKm", val);
                }
            },
            gps: {
                get: function() {
                    return getField("gps");
                },
                set: function(val) {
                    setField("gps", val);
                }
            },
            place: {
                get: function() {
                    return getField("place");
                },
                set: function(val) {
                    setField("place", val);
                }
            }
        });
        Object.defineProperties(getField("online"), {
            isOnlineNow: {
                get: function() {
                    return getField("isOnlineNow");
                },
                set: function(val) {
                    setField("isOnlineNow", val);
                }
            },
            lastSeen: {
                get: function() {
                    return getField("lastSeen");
                },
                set: function(val) {
                    setField("lastSeen", val);
                }
            },
            roomId: {
                get: function() {
                    return getField("roomId");
                },
                set: function(val) {
                    setField("roomId", val);
                }
            },
            roomName: {
                get: function() {
                    return getField("roomName");
                },
                set: function(val) {
                    setField("roomName", val);
                }
            }
        });

        /**
         * @name User#uid
         * @type number
         * @readonly
         */
        /**
         * @name User#key
         * @type String
         * @readonly
         */
        /**
         * @name User#hasChanged
         * @type Boolean
         * @readonly
         */
        /**
         * @name User#note
         * @type String
         */
        Object.defineProperties(this, {
            uid: {value: _uid, writable: false},
            key: {value: _key, writable: false},
            hasChanged: {
                get: function() {
                    return _hasChanged;
                }
            },
            lastChanged: {
                get: function() {
                    return getField("lastChanged");
                }
            },
            lastUpdated: {
                get: function() {
                    return getField("lastUpdated");
                }
            },
            uname: {
                get: function() {
                    return getField("uname");
                },
                set: function(val) {
                    setField("uname", val);
                }
            },
            name: {
                get: function() {
                    return getField("name") || getField("uname") || _uid.toString();
                },
                set: function(val) {
                    setField("name", val);
                }
            },
            note: {
                get: function() {
                    return getField("note");
                },
                set: function(val) {
                    setField("note", val);
                }
            },
            age: {
                get: function() {
                    return getField("age");
                },
                set: function(val) {
                    setField("age", val);
                }
            },
            gender: {
                get: function() {
                    return getField("gender");
                },
                set: function(val) {
                    setField("gender", val);
                }
            },
            isPremium: {
                get: function() {
                    return getField("isPremium");
                },
                set: function(val) {
                    setField("isPremium", val);
                }
            },
            isReal: {
                get: function() {
                    return getField("isReal");
                },
                set: function(val) {
                    setField("isReal", val);
                }
            },
            isSuper: {
                get: function() {
                    return getField("isSuper");
                },
                set: function(val) {
                    setField("isSuper", val);
                }
            },
            location: {
                get: function() {
                    return getField("location");
                }
            },
            online: {
                get: function() {
                    return getField("online");
                }
            }
        });

        /**
         * Saves or updates the user in the database of this script.
         * Overwrites an existing entry or creates a new entry if it doesn't already exist.
         */
        this.save = function() {
            User.prototype.save.call(this);
            _hasChanged = false;
        };
        /**
         * Loads the User from the database of this script.
         * @returns {Boolean} <code>true</code> if the user was found and could be successfully loaded from db
         */
        this.load = function() {
            var isSuccess = User.prototype.load.call(this);
            if (isSuccess) {
                _hasChanged = false;
            }
            return isSuccess;
        };
        /**
         * Removes the User from the database of this script.
         */
        this.remove = function() {
            User.prototype.remove.call(this);
            _hasChanged = true;
        };
        this.load();
    }

    User.prototype = {
        constructor: User,
        save: function() {
            if (this.hasChanged) {
                var saveObj = {};
                objUtils.mixin(saveObj, this, User.prototype.PROPERTY_NAMES);
                GM_setValue("uid" + this.uid, JSON.stringify(saveObj));
            }
        },
        load: function() {
            var isSuccess = true;
            var loadedString = GM_getValue("uid" + this.uid);
            if (loadedString) {
                try {
                    var loadedObj = JSON.parse(loadedString);
                    objUtils.mixin(this, loadedObj);
                } catch(e) {
                    isSuccess = false;
                }
            }
            return isSuccess;
        },
        remove: function() {
            GM_deleteValue("uid" + this.uid);
        },
        /**
         * Gets all users stored from the database determined for this Script.
         * @returns {{}<string|number,User>[]} List with all Stored Users
         */
        loadAllUsers: function() {
            var users = {};
            var storedKeys = GM_listValues();
            for (var i = 0; i <= storedKeys.length - 1; i++) {
                var key = storedKeys[i].toString();
                if (key.indexOf('uid') === 0) {
                    var uid = key.substr(3);
                    users[uid] = new User(uid);
                }
            }
            return users;
        },
        /**
         * Has the browser open the profile Page of this user.
         * @param {boolean} [asNewTab=false]
         *      <code>true</code>, Page is opened in a new tab.
         *      <code>false</code>, replaces the current Page.
         */
        openProfilePage: function(asNewTab) {
            var profPageLocPath = location.protocol + '//www.camamba.com/profile_view.php';
            var queryParamsObj = {uid: this.uid, m: 'start'};
            var uri = Page.localizeUri(profPageLocPath, queryParamsObj);
            var target = asNewTab ? '_blank' : '_self';
            window.open(uri, target);
        },
        PROPERTY_NAMES: [ 'uname', "name", "note", "age", "gender", "isPremium", "isReal", "isSuper", "location", "online", "lastChanged", "lastUpdated" ]
    };
    Object.freeze(User.prototype.PROPERTY_NAMES);

    return {
        ButtonSmall : ButtonSmall,
        ButtonTiny : ButtonTiny,
        SelectUsers : SelectUsers,
        PageInfo : PageInfo,
        User : User
    };
})();

if(false) {
     /**
     * Represents the veewards
     * @type {{lol, drama, banHammer, cheese}}
     */
    var veewards = (function () {
        function Vee(idx, name) {
            this.index = idx;
            this.name = name;
        }

        /**
         * This callback is displayed as part of the Requester class.
         * @callback Vee~sendCallback
         * @param {Date} timeSend - Time the veeward beeing send
         */

        /**
         * Tries to send a veeward.
         * @param {User} [user] - The user to whom the veeward may be send
         * @param {number} [coolDownPeriodSec] - Period in seconds for which no veeward will be send since last sent
         * @param {Vee~sendCallback} [callback] - A function to be called when the veeward got tried to be send.
         */
        Vee.prototype.send = function (user, coolDownPeriodSec, callback) {
            if (!(user && user instanceof User)) {
                user = new User(602175);
            }
            coolDownPeriodSec = parseInt(coolDownPeriodSec || 0, 10);
            if (typeof callback !== 'function') {
                callback = function (now) {
                    console.info(new Date(now * 1000), 'try to veeward ', user.name, ' with ', thisVee.name);
                };
            }

            var now = function () {
                var now = Date.now ? Date.now() : new Date().getTime();
                return Math.floor(now / 1000);
            }();
            var lastVeeTime = GM_getValue('autoVeeTimestamp', 0);
            if (lastVeeTime > now || lastVeeTime + coolDownPeriodSec < now) {
                var thisVee = this, vhttp;
                try {
                    vhttp = new ActiveXObject('Msxml2.XMLHTTP');
                } catch (e) {
                    try {
                        vhttp = new ActiveXObject('Microsoft.XMLHTTP');
                    } catch (E) {
                        vhttp = false;
                    }
                }
                if (!vhttp && typeof XMLHttpRequest != 'undefined') {
                    try {
                        vhttp = new XMLHttpRequest();
                    } catch (e) {
                        vhttp = false;
                    }
                }
                vhttp.open('POST', '/extras/setdata.php', true);
                vhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                vhttp.onreadystatechange = function () {
                    GM_setValue('autoVeeTimestamp', now);
                    callback(now);
                };
                vhttp.send('sendvee=' + this.index + '&to=' + user.uid);
            }
        };
        return {
            lol: new Vee(1, 'LoL'),
            drama: new Vee(2, 'Drama'),
            banHammer: new Vee(3, 'Ban Hammer'),
            cheese: new Vee(4, 'Cheese')
        };
    })();


    /**
     * Represents a search using the page "/search.php".
     * @constructor
     */
    var UserSearch = (function () {
        var doSearch = function (paramString, pageNr) {
            pageNr = pageNr || 0;
            var searchHref = '/search.php' + (paramString ? paramString + '&' : '?') + 'page=' + pageNr;
            var onlineUsers = {};
            Page.getAsync(searchHref, function (resp) {
                var elTablesNormal = resp.html.getElementsByClassName('searchNormal');
                var elTablesSuper = resp.html.getElementsByClassName('searchSuper');
                var timeStampInMs = Date.now();
                [elTablesNormal, elTablesSuper].forEach(function (userTables, index) {
                    var uIsSuper = index === 1;
                    [].forEach.call(userTables, function (table) {
                        var user, uId, uName, uGender, uAge, uIsPremium, uIsReal,
                            uLocation = {},
                            uOnline = {isOnlineNow: true, lastSeen: timeStampInMs};
                        var links = table.getElementsByTagName('a');
                        for (var i = 0; i <= links.length - 1; i++) {
                            var link = links[i];
                            if (!uId) {
                                var uidSearch = /javascript:sendMail\('(.+)'\)/g.exec(link.href);
                                if (uidSearch && uidSearch.length >= 2) {
                                    uId = uidSearch[1];
                                    uIsPremium = uIsReal = false;
                                    if (link.nextSibling && link.nextSibling.nextSibling && link.nextSibling.nextSibling.nextSibling) {
                                        var elPremiumReal = link.nextSibling.nextSibling.nextSibling;
                                        if (elPremiumReal) {
                                            if (elPremiumReal.getAttribute('href') === '/premium.php') {
                                                uIsPremium = true;
                                                if (elPremiumReal.nextSibling && elPremiumReal.nextSibling.nextSibling) {
                                                    elPremiumReal = elPremiumReal.nextSibling.nextSibling;
                                                }
                                            }
                                        }
                                        if (elPremiumReal && elPremiumReal.getAttribute('src') === '/gfx/real.png') {
                                            uIsReal = true;
                                        }
                                    }
                                }
                            }
                            if (!uName || !uGender) {
                                var unameSearch = /javascript:openProfile\('(.+)'\)/g.exec(link.href);
                                if (unameSearch && unameSearch.length >= 2) {
                                    uName = unameSearch[1];
                                    if (link.nextSibling && link.nextSibling.nextSibling) {
                                        var genderAgeSearch = link.nextSibling.nextSibling.textContent;
                                        var uGenderSearch = /(male|female)/g.exec(genderAgeSearch);
                                        if (uGenderSearch && uGenderSearch.length >= 2) {
                                            uGender = uGenderSearch[1];
                                            var uAgeTxt = genderAgeSearch.replace(/\D/g, "");
                                            if (uAgeTxt) {
                                                uAge = parseInt(uAgeTxt);
                                            }
                                        }
                                    }
                                }
                            }
                            if (!uOnline.roomId) {
                                var onlineSearch = /javascript:openChat\('(.+)'\)/g.exec(link.href);
                                if (onlineSearch && onlineSearch.length >= 2) {
                                    uOnline.roomId = onlineSearch[1];
                                    uOnline.roomName = link.innerHTML;
                                }
                            }
                            if (!uLocation.gps) {
                                var locationSearch = /javascript:openMap\((.+)\)/g.exec(link.href);
                                if (locationSearch && locationSearch.length >= 2) {
                                    uLocation = {
                                        gps: locationSearch[1],
                                        place: link.innerHTML
                                    };
                                    var distanceTxt = link.nextSibling.textContent.replace(/\D/g, "");
                                    if (distanceTxt) {
                                        uLocation.distanceInKm = parseInt(distanceTxt);
                                    }
                                }
                            }
                        }

                        if (uId) {
                            user = new User(uId);
                            if (uAge) {
                                user.age = uAge;
                            }
                            if (uGender) {
                                user.gender = uGender;
                            }
                            user.isPremium = uIsPremium;
                            user.isReal = uIsReal;
                            user.isSuper = uIsSuper;
                            if (uLocation.distanceInKm !== undefined) {
                                user.location.distanceInKm = uLocation.distanceInKm;
                            }
                            if (uLocation.gps) {
                                user.location.gps = uLocation.gps;
                            }
                            if (uLocation.place) {
                                user.location.place = uLocation.place;
                            }
                            if (uName) {
                                user.uname = uName;
                            }
                            user.online.isOnlineNow = uOnline.isOnlineNow;
                            user.online.lastSeen = uOnline.lastSeen;
                            user.online.roomId = uOnline.roomId;
                            user.online.roomName = uOnline.roomName;
                            onlineUsers[uId] = user;
                        }
                    });
                });
                pageNr++;
                var links = resp.html.getElementsByTagName('a');
                for (var i = links.length - 1; i >= 0; i--) {
                    var linkHref = links[i].getAttribute('href');
                    if (linkHref && linkHref.toLowerCase().indexOf(searchHref) >= 0) {
                        console.log('Link:', linkHref);
                        setTimeout(function () {
                            doSearch(paramString, pageNr);
                        }, 750);
                        break;
                    }
                }
            });
            return onlineUsers;
        };

        /** @typedef {{MALE: string, FEMALE: string, ANY: string}} GendersType */
        /**
         * @readonly
         * @enum {string}
         * @type {GendersType}
         */
        Params.prototype.genders = {MALE: "male", FEMALE: "female", ANY: "any"};
        Object.freeze(Params.prototype.genders);

        /**
         * Represents a search using the page "/search.php".
         * The parameters restrict the search ...
         * @param {string} [nick] - to users whose username to start with a given string
         * @param {GendersType} [gender=any] - to users with a given gender
         * @param {boolean} [isOnline=true] - to users currently online
         * @param {boolean} [isReal=false] - to users with a real tag
         * @param {boolean} [hasPicture=false] - to users with a picture uploaded to their profile
         * @param {boolean} [isPremium=false] - to users with premium
         * @param {boolean} [isSuper=false] - to users with super premium
         * @constructor
         */
        var Constructor = function (nick, gender, isOnline, isReal, hasPicture, isPremium, isSuper) {
            gender = gender || Params.prototype.genders.ANY;
            isOnline |= (typeof isOnline === "undefined");
            /**
             * parameter restricting the results of the search
             * @type {Params}
             */
            this.queryObject = new Params(nick, gender, isOnline, hasPicture, isPremium, isReal, isSuper);
            /**
             * List with users resulting from the last executed search
             * @type {{}<string|number,User>[]}
             */
            this.users = {};
        };
        Constructor.prototype = {
            constructor: Constructor,
            /** executes the search */
            execute: function () {
                this.users = doSearch(this.queryObject.toString());
            }
        };
        return Constructor;
    })();
}