Greasemonkey: leaving user configuration intact while updating - greasemonkey

I've written a user script for Greasemonkey that requires some user configuration. To specify how they want the script to behave, a user needs to set a couple variables.
Right now, the script is set up like this:
// ==UserScript==
// #name My script
// #description A simplified example
// #include http://www.example.com/
// #version 0.0.1
// #updateURL https://www.example.com/myscript.meta.js
// ==/UserScript==
// Configuration
var config1 = "on";
var config2 = "off";
// Programs
[various functions that refer to the configuration variables]
I'd like to be able to update the script using Greasemonkey's automatic updates, while leaving the user's configuration lines intact. Basically, I don't want to force every user to redo their configuration after each update.
Is there a good method for updating a Greasemonkey userscript while leaving some configuration intact?

You may wish to utilise the greasemonkey functions GM_getValue() and GM_setValue(), which will store values that stay that way until they're changed again. The script can set values based on user needs, and get the value where required.
Specific GM functions require a special grant, in the metadata for the userscript:
// #grant GM_getValue
// #grant GM_setValue
Your code with the GM value functions may look like this:
var value = 18;
GM_setValue('dataName', value);
if (GM_getValue('dataName') == 18) ...
When you make updates to the script, instead of having the values set by GM_setValue overwritten, you could first check to see if they have been written:
var value = 18;
if (typeof GM_getValue('dataName') === 'undefined')
GM_setValue('dataName', value);
To enable users to control these settings, you could inject an HTML interface that shows the values of the database entries, and allows them to be set. This approach would then make such settings immune to script updates (as long as the script doesn't overwrite the database values).
Also, this question may also be a beneficial read.

Related

Gitlab, how to change tabulation size on code review

While reviewing the code with gitlab merge request web page, tabulation is 8 spaces.
It makes the code difficult to read. How could I move it to 1 or 2 spaces ?
It seems to be impossible to achieve that by available GitLab settings, but this can be hacked using Greasemonkey/Tampermonkey browser extension:
// ==UserScript==
// #name Custom tab-width
// #version 1
// #grant none
// #include https://your.gitlab.server.com/*/diffs*
// ==/UserScript==
var style = document.createElement("style");
style.type = "text/css";
style.innerHTML = "span.line {tab-size:2; -moz-tab-size:2;}";
document.head.appendChild(style);
or by any other stylish-like extensions which allow you to attach any custom css rules to web pages:
span.line {tab-size:2; -moz-tab-size:2;}

How can I use Node.js to get the value of a child under random USERid and keys

It's a simple concept but yet finding the right answer is ridiculously stressing. Use Firebase Functions w/ Node.js to pull the "itemsExpires" (date) from the Firebase database. The parent node is the userId (random string) and each item is stored under a key node (another random string).. So, here's what the firebase database looks like:
firebase-database-name
+ 82hfijcbwjbjfbergjagbk_USERID
+ "My Stuff"
+ gnjfgsdkfjsdf_ITEMkey
-- "item name": whatever
-- "itemExpires": 05-01-2017
-- "itemType": whatever too
+ an3jeejwiag_ITEMkey
-- "item name": whatever
-- "itemExpires": 06-01-2017
-- "itemType": whatever too
+ zzzndjgabblsbl_ITEMkey
-- "item name": whatever
-- "itemExpires": 07-01-2017
-- "itemType": whatever too
I'm not asking for someone to write the code, a good reference will do but there are so many ways to call data and all I'm finding are the ways to Call using a structured tree and not one with random id's and keynumbers.
*** Basically, my goal here is to run a 3rd party cron job through Firebase Functions that runs through each item entry and checks the expiration date against today's date. This is the wall I'm against.
Bradley I am yet unclear as to what you want to do exactly. I suppose you intend on having multiple users (not just one as in the example) with multiple Items and compare the current date against the expiration date of all items for every user at a specified time (using cron). There are some considerations you should take into account here :
Do you really need cron ? ( or can you solve your problems more easily and natively with a javascript plain setInterval() ? )
How often are you going to check your entire database and how big is that database?
OK. So to explain, the first consideration is just a thought and the logic behind it should be pretty obvious. The second consideration takes some explaining. Since I believe your firebase data will NOT be static and will constantly change you need to find a way to get these changes inside you node script.
If you do not intend on calling your scheduled task too often and the database is not a mammoth (taking ages to load) you could just do the following :
const firebase = require('firebase-admin');
const serviceAccount = require('yourServiceAccountPath.json');
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: "youDatabaseURL"
});
setInteval(function(){ // or cron schedule
firebase.database().ref().once('value',function(snapshot){
let allYourUsers = snapshot.val();
// iterate through them all
// and do what you gotta do
});
},10000); // your interval in milliseconds
However this approach just loads once all your database each time you want to check all items. If you have other data in the database, consider adding users to a seperate path and load just that path. This approach is not recommended if your users are quite many and/or you want them to be checked very often. If such is the case and your data does not change very often you could consider this alternative:
Here you use the on function to update your data as it is edited and set the checking part seperate like so :
const firebase = require('firebase-admin');
const serviceAccount = require('yourServiceAccountPath.json');
firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: "youDatabaseURL"
});
const databaseRef=firebase.database().ref();
let allYourUsers;
let allYourUsersStaticCopy;
databaseRef.on('value',function(snapshot){
allYourUsers = snapshot.val();
});
setInteval(function(){ // or cron schedule
if ( allYourUsers ) { // to ensure that data has loaded at least once
// (startup considerations)
allYourUsersStaticCopy = Object.assign({},allYourUsers);
// iterate through the static copy in order to avoid
// you data changing while you are accesing it
// and do what you gotta do
}
},10000); // your interval in milliseconds
The upside with the second piece of code is that your data is loaded every time there is a change and not every time your check runs. If however your data changes very often (additions,deletions and edits) this approach might not be optimum.
In the case that your script runs often enough, the database is big enough, and the changes are often enough to prevent any of the above to be efficient, you might want to consider the optimum solution : loading your users once and then attaching listeners for the child added,removed and changed to update your existing users object. Thus you receive only changes and not a whole snapshot. If such is the case you can read about the mentioned listeners in the firebase docs and I can squeeze in some extra code. Just let me know.
Hope this long post helps!
Assuming you have Firebase set up properly within your node project, you can do a one time read for your ITEMkey entries. Something like this:
var db = admin.database();
var ref = db.ref("82hfijcbwjbjfbergjagbk_USERID").child("My Stuff");
ref.once("value", function(snapshot) {
var contents = snapshot.val();
// Data returned here will be an object with all children
// nodes under "My Stuff". You can access it by calling
// snapshot.val() like I did above.
}

Best way to reuse a large translation file within Node / Express

I'm new to Node but I figured I'd jump right in and start converting a PHP app into Node/Express. It's a bilingual app that uses gettext with PO/MO files. I found a Node module called node-gettext. I'd rather not convert the PO files into another format right now, so it seems this library is my only option.
So my concern is that right now, before every page render, I'm doing something like this:
exports.home_index = function(req, res)
{
var gettext = require('node-gettext'),
gt = new gettext();
var fs = require('fs');
gt.textdomain('de');
var fileContents = fs.readFileSync('./locale/de.mo');
gt.addTextdomain('de', fileContents);
res.render(
'home/index.ejs',
{ gt: gt }
);
};
I'll also be using the translations in classes, so with how it's set up now I'd have to load the entire translation file again every time I want to translate something in another place.
The translation file is about 50k and I really don't like having to do file operations like this on every page load. In Node/Express, what would be the most efficient way to handle this (aside from a database)? Usually a user won't even be changing their language after the first time (if they're changing it from English).
EDIT:
Ok, I have no idea if this is a good approach, but it at least lets me reuse the translation file in other parts of the app without reloading it everywhere I need to get translated text.
In app.js:
var express = require('express'),
app = express(),
...
gettext = require('node-gettext'),
gt = new gettext();
Then, also in app.js, I create the variable app.locals.gt to contain the gettext/translation object, and I include my middleware function:
app.locals.gt = gt;
app.use(locale());
In my middleware file I have this:
mod
module.exports = function locale()
{
return function(req, res, next)
{
// do stuff here to populate lang variable
var fs = require('fs');
req.app.locals.gt.textdomain(lang);
var fileContents = fs.readFileSync('./locales/' + lang + '.mo');
req.app.locals.gt.addTextdomain(lang, fileContents);
next();
};
};
It doesn't seem like a good idea to assign the loaded translation file to app, since depending on the current request that file will be one of two languages. If I assigned the loaded translation file to app instead of a request variable, can that mix up users' languages?
Anyway, I know there's got to be a better way of doing this.
The simplest option would be to do the following:
Add this in app.js:
var languageDomains = {};
Then modify your Middleware:
module.exports = function locale()
{
return function(req, res, next)
{
// do stuff here to populate lang variable
if ( !req.app.locals.languageDomains[lang] ) {
var fs = require('fs');
var fileContents = fs.readFileSync('./locales/' + lang + '.mo');
req.app.locals.languageDomains[lang] = true;
req.app.locals.gt.addTextdomain(lang, fileContents);
}
req.textdomain = req.app.locals.gt.textdomain(lang);
next();
};
};
By checking if the file has already been loaded you are preventing the action from happening multiple times, and the domain data will stay resident in the server's memory. The downside to the simplicity of this solution is that if you ever change the contents of your .mo files whilst the server is running, the changes wont be taken into account. However, this code could be extended to keep an eye on the mtime of the files, and reload accordingly, or make use of fs.watchFile — if required:
if ( !req.app.locals.languageDomains[lang] ) {
var fs = require('fs'), filename = './locales/' + lang + '.mo';
var fileContents = fs.readFileSync(filename);
fs.watchFile(filename, function (curr, prev) {
req.app.locals.gt.addTextdomain(lang, fs.readFileSync(filename));
});
req.app.locals.languageDomains[lang] = true;
req.app.locals.gt.addTextdomain(lang, fileContents);
}
Warning: It should also be noted that using sync versions of functions outside of server initialisation is not a good idea because it can freeze the thread. You'd be better off changing your sync loading to the async equivalent.
After the above changes, rather than passing gt to your template, you should be able to use req.textdomain instead. It seems that the gettext library supports a number of requests directly on each domain object, which means you hopefully don't need to refer to the global gt object on a per request basis (which will be changing it's default domain on each request):
Each domain supports:
getTranslation
getComment
setComment
setTranslation
deleteTranslation
compilePO
compileMO
Taken from here:
https://github.com/andris9/node-gettext/blob/e193c67fdee439ab9710441ffd9dd96d027317b9/lib/domain.js
update
A little bit of further clarity.
Once the server has loaded the file into memory the first time, it should remain there for all subsequent connections it receives (for any visitor/request) because it is stored globally and wont be garbage collected — unless you remove all references to the data, which would mean gettext would need to have some kind of unload/forget domain method.
Node is different to PHP in that its environment is shared and wraps its own HTTP server (if you are using something like Express), which means it is very easy to remember data globally as it has a constant environment that all the code is executed within. PHP is always executed after the HTTP server has received and dealt with the request (e.g. Apache). Each PHP response is then executed in its own separate run-time, which means you have to rely on databases, sessions and cache stores to share even simple information and most resources.
further optimisations
Obviously with the above you are constantly running translations on each page load. Which means the gettext library will still be using the translation data resident in memory, which will take up processing time. To get around this, it would be best to make sure your URLs have something that makes them unique for each different language i.e. my-page/en/ or my.page.fr or even jp.domain.co.uk/my-page and then enable some kind of full page caching using something like memcached or express-view-cache. However, once you start caching pages you need to make certain there aren't any regions that are user specific, if so, you need to start implement more complicated systems that are sensitive to these areas.
Remember: The golden rule of optimisation, don't do so before you need to... basically meaning I wouldn't worry about page caching until you know it's going to be an issue, but it is always worth bearing in mind what your options are, as it should shape your code design.
update 2
Just to illustrate a bit further on the behaviour of a server running in JavaScript, and how the global behaviour is not just a property of req.app, but in fact any object that is further up the scope chain.
So, as an example, instead of adding var languageDomains = {}; to your app.js, you could instantiate it further up the scope of wherever your middleware is placed. It's best to keep your global entities in one place however, so app.js is the better place, but this is just for illustration.
var languageDomains = {};
module.exports = function locale()
{
/// you can still access languageDomains here, and it will behave
/// globally for the entire server.
languageDomains[lang]
}
So basically, where-as with PHP, the entire code-base is re-executed on each request — so the languageDomains would be instantiated a-new each time — in Node the only part of the code to be re-executed is the code within locale() (because it is triggered as part of a new request). This function will still have a reference to the already existing and defined languageDomains via the scope chain. Because languageDomains is never reset (on a per request basis) it will behave globally.
Concurrent users
Node.js is single threaded. This means that in order for it to be concurrent i.e. handle multiple requests at the "same" time, you have to code your app in such a way that each little part can be executed very quickly and then slip into a waiting state, whilst another part of another request is dealt with.
This is the reason for the asynchronous and callback nature of Node, and the reason to avoid Sync calls whilst your app is running. Any one Sync request could halt or freeze execution of the thread and delay handling for all other requests. The reason why I state this is to give you a better idea of how multiple users might interact with your code (and global objects).
Basically once a request is being dealt with by your server, it is it's only focus, until that particular execution cycle ends i.e. your request handler stops calling other code that needs to run synchronously. Once that happens the next queued item is dealt with (a callback or something), this could be part of another request, or it could be the next part in the current request.

In a Greasemonkey script, how can I target a page by its elements?

I would like to change a page using a Greasemonkey script, but the page is the result of a CGI POST call and thus the URL looks like this:
http://www.example.com/cgi-bin/foo.pl
So obviously I cannot use the #include in the Greasemonkey script, because that URL fits a lot of pages the app can generate.
I could just look for some elements on the page within the script before doing stuff:
if (document.getElementById('someIdentifier')) {
// do changes in here
}
But I was wondering if there is a better, maybe built-in way to do this.
Greasemonkey (and most userscript engines) has no built-in way to target a page by its elements. Your script needs to poll or use mechanisms like Mutation Observers to check for the desired elements.
A handy way to do this is to use the waitForKeyElements() utility, and you still want to use the #include, #exclude, and/or #match directives to narrow down the page firings as much as possible.
Here is a complete Greasemonkey/Tampermonkey script illustrating the process:
// ==UserScript==
// #name _Do something after select posts
// #include http://www.example.com/cgi-bin/foo.pl*
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js
// #require https://gist.github.com/raw/2625891/waitForKeyElements.js
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
function processTargetElement (jNode) {
//***** YOUR CODE HERE *****
jNode.css ("background", "pink");
}
waitForKeyElements ("#someIdentifier", processTargetElement);

Where to initialize extension related data

am a newbie, trying to write some basics extension. For my extension to work i need to initialize some data, so what I did is inside my background.js i declared something like this.
localStorage["frequency"] = 1; //I want one as Default value. This line is not inside any method, its just the first line of the file background.js
Users can goto Options page and change this above variable to any value using the GUI. As soon as the user changes it in UI am updating that value.
Now the problem is to my understanding background.js reloads everytime the machine is restarted. So every time I restart my machine and open Chrome the frequency value is changed back to 1. In order to avoid this where I need to initialize this value?
You could just use a specific default key. So if frequency is not set you would try default-frequency. The default keys are then still set or defined in the background.js.
I like to do that in one step, in a function like this
function storageGet(key,defaultValue){
var item = localstorage.getItem(key);
if(item === null)return defaultValue;
else return item;
}
(According to the specification localstorage must return null if no value has been set.)
So for your case it would look something like
var f = storageGet("frequency",1);
Furthermore you might be interested in checking out the chrome.storage API. It's used similar to localstorage but provides additional functionalities which might be useful for your extension. In particular it supports to synchronize the user data across different chrome browsers.
edit I changed the if statement in regard to apsillers objection. But since the specification says it's ought to be null, I think it makes sense to check for that instead of undefined.
This is another solution:
// background.js
initializeDefaultValues();
function initializeDefaultValues() {
if (localStorage.getItem('default_values_initialized')) {
return;
}
// set default values for your variable here
localStorage.setItem('frequency', 1);
localStorage.setItem('default_values_initialized', true);
}
I think the problem lies with your syntax. To get and set your localStorage values try using this:
// to set
localStorage.setItem("frequency", 1);
// to get
localStorage.getItem("frequency");

Resources