Advent 2022: 12. Beeminding Word Counts for Public or Private Writing or Nebulous Projects

Today’s Advent Calendar post is about beeminding word counts, which can be used for tracking a writing project, or journalling, or even planning and actioning a nebulous project that can’t be easily tracked any other way.

The simplest way is to do your writing online (e.g., on a web page, a Google Doc, or a file in an online storage site like Dropbox) and create a “URLminder” goal. You provide the goal with the URL for your writing, and the goal tracks the word count automatically. The page/file needs to be accessible without a login by anyone who has the link, otherwise the goal won’t be able to read it. Services like Google Docs and Dropbox provide URLs that are (nearly?) impossible to guess to give some measure of privacy. For more details, see the URLminder help page, the How To Beemind Nebulous Goals blog post, and the How to beemind nebulous projects like doing your taxes or fixing a neurosis forum thread. The latter two are worth a read for potential inspiration even if you can’t think of a use for this right now.

But what if you don’t want to do your writing online or don’t want to use a publicly-accessible URL? A simple Do More or Odometer goal can be used where you manually enter the word count after each writing session. A Do More may be best if you use a different web page / file for each day’s writing. An Odometer may be best if all your writing is in a single page / file.

But it’s tedious to get the word count and enter the data into Beeminder yourself ever day! If you’re willing to do your writing online in a private location (e.g., a journalling website that requires you to log in), then you can use this bookmarklet to get the word count and submit it to a goal:

javascript: (function() { GOAL = ""; USER = ""; AUTH_TOKEN=""; COMMENT="submitted by bookmarklet"; let text = window.getSelection().toString(); let active = document.activeElement; if (active) { let activeTagName = active.tagName.toLowerCase(); if ((activeTagName == 'textarea') && (typeof active.selectionStart == 'number')) { text = active.value.slice(active.selectionStart, active.selectionEnd); } } if (!text) {alert('Please select some text then click the bookmarklet again.'); return;} let wordCount = text.split(/\s+/).length; let tempNode = document.createElement('textarea'); let insertedElement = document.createElement('div'); insertedElement.setAttribute('id', 'myinsertedelement'); let html = '<form id="myinsertedform" target="_blank" method="POST" action="https://www.beeminder.com/api/v1/users/me/goals/' + GOAL + '/datapoints.json">' + '<input type="hidden" name="auth_token" value="' + AUTH_TOKEN + '">' + '<input type="hidden" name="value" value="' + wordCount + '">' + '<input type="hidden" name="comment" value="' + COMMENT + '">' + '<input type="submit">' + '</form>'; document.body.appendChild(insertedElement); document.getElementById('myinsertedelement').innerHTML = html; document.getElementById('myinsertedform').submit(); if (USER) { window.setTimeout(function() { window.open('https://www.beeminder.com/' + USER + '/' + GOAL + '#data'); }, 3000); } })();

Here’s how to set that up:

  1. Create a Do More or Odometer goal. Give it whatever goalname you want, but for the sake of this example, we’ll say it’s “writing”.
  2. Copy the code above to your clipboard - i.e., the entire line that starts with javascript. The easiest way to copy it is to triple-click it, which should select the whole line (it’s long!) and then do Ctrl-C.
  3. Use your browser’s bookmark manager to create a new, empty bookmark. I suggest putting it on your bookmark toolbar for easy access.
  4. Give the bookmark a name - anything you like.
  5. Paste the javascript code from your clipboard into the bookmark’s URL field.
  6. Save the bookmark. You’ll be coming back to it in a minute to add some more data.
  7. In Beeminder, go to your Account Settings > Apps & API page and look for the “Personal Auth Token”. It will look something like AbC12-abcd1234efgHIJ. Copy it to the clipboard. For more info about your Token, see yesterday’s Advent post.
  8. Go back into your bookmark’s edit screen. It should look something like the first screenshot that you’ll see below these steps.
  9. Paste your Personal Auth Token from your clipboard into the double quotes just after AUTH_TOKEN.
  10. Put your goalname into the double quotes just after GOAL.
  11. Put your Beeminder username into the double quotes just after USER. This part is optional, but if you do this, then the bookmark will be able to open your goal page after it has added data to it, so that you can check the data. If you don’t want it to do that, you can leave USER blank.
  12. Your bookmark should now look something like the second screenshot below.
  13. Save the bookmark.
  14. Go to your online writing page and select all the text that you want to count. I recommend selecting the entire page with Ctrl-A, even if it contains some words that aren’t part of your writing - it’s a lot easier than manually selecting just your own words, and if the surrounding text stays the same over time, it won’t make much long-term difference to your word count.
  15. When all the text is selected, click the bookmark. The word count will be submitted to your goal. You’ll see a new page open with some technical data in it - that’s just a summary of what was submitted and you can close it straight away.
  16. The bookmarklet will wait about 3 seconds to give time for the submission to go through and it will then open your goal page so that you can check that the data is correct. (As mentioned above, you need to fill in the USER part of the bookmark to make that happen.) The first time you use the bookmark, you’ll probably need to tell your browser that it’s okay for your online writing website to open a pop-up window.
screenshot of bookmark before editing

Screenshot from 2022-12-12 21-16-04

screenshot of bookmark after editing

Screenshot from 2022-12-12 20-21-01

Hopefully that all went smoothly for you! Post a comment if it didn’t!

If you’d like to see the bookmarklet code in a more readable format, here it is with line breaks and comments. You’re welcome to change it however you want, especially if you post your enhancements here. :slight_smile: JavaScript is not my strong suit so there may be much scope for improvement.

bookmarklet code
// remove all comments below before turn this back into a bookmarklet
javascript: (function() {
	GOAL = "";
	USER = "";
	AUTH_TOKEN="";
	COMMENT="submitted by bookmarklet";

    let text = window.getSelection().toString();  // selected text on the webpage
    let active = document.activeElement;
	if (active) {  // look for selected text in a textarea and use that instead, if any
		let activeTagName = active.tagName.toLowerCase();
		if ((activeTagName == 'textarea') && (typeof active.selectionStart == 'number')) {
			text = active.value.slice(active.selectionStart, active.selectionEnd);
		}
    }
	if (!text) {alert('Please select some text then click the bookmarklet again.'); return;}
	let wordCount = text.split(/\s+/).length;

	let tempNode = document.createElement('textarea');
	let insertedElement = document.createElement('div');
	insertedElement.setAttribute('id', 'myinsertedelement');
	let html = '<form id="myinsertedform" target="_blank" method="POST" action="https://www.beeminder.com/api/v1/users/me/goals/' + GOAL + '/datapoints.json">' +
		'<input type="hidden" name="auth_token" value="' + AUTH_TOKEN + '">' +
		'<input type="hidden" name="value" value="' + wordCount + '">' +
		'<input type="hidden" name="comment" value="' + COMMENT + '">' +
		'<input type="submit">' +
		'</form>';
	document.body.appendChild(insertedElement);
	document.getElementById('myinsertedelement').innerHTML = html;
	document.getElementById('myinsertedform').submit();

	if (USER) {
		window.setTimeout(function() { 
			window.open('https://www.beeminder.com/' + USER + '/' + GOAL + '#data');
		}, 3000); // wait time in milliseconds
	}
})();

Another option is to use a Userscript for counting and submitting words on any private site. Here is one written by @theospears for leetcode.com. If you change it to match to your own leetcode profile page, it will submit your leetcount count to a Beeminder goal every time you visit that page.

sample leetcode userscript
// ==UserScript==
// @name             Leetcode to beeminder
// @match            https://leetcode.com/theospears/
// @version          1.0
// ==/UserScript==

const solvedProblemsWatcher = window.setInterval(() => {
	const solvedProblemsNode = document.evaluate("//div[text()='Solved']/preceding-sibling::div", document.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
	if (solvedProblemsNode.singleNodeValue !== null) {
		window.clearInterval(solvedProblemsWatcher);
		console.log("Solutions:", solvedProblemsNode.singleNodeValue.innerText);

		const date = new Date();
		const dateString = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate();

		const data = new FormData();
		data.append('value', solvedProblemsNode.singleNodeValue.innerText);
		data.append('comment', 'Auto-posted from browser');
		data.append('auth_token', 'XXXXXXXXXXXXXXXXXXXXXXXXXX');
		data.append('requestid', dateString);

		fetch("https://www.beeminder.com/api/v1/users/me/goals/leetcode/datapoints.json", {
			method: 'POST',
			mode: 'no-cors',
			body: data
		});
	}
}, 100);

If you have a userscript for any other site, we’d love to see it!

Do you want to beemind the word count from Microsoft Word? Beeminding Microsoft Word Count may be what you need, assuming Word’s support for macros is the same as it was when that forum post was written. :crossed_fingers:

Do you want to Beemind a nebulous project but you don’t like the idea of using a word count for it? An Advent post later this week will describe another option.

EDIT:
In the comment below, @poisson makes the excellent point that editing a document may not increase its word count. Beeminding changes to a file may work better for you and can be done using git diff (or the normal version of diff if your writing is not in a git repository).

3 Likes

So what I currently do is to beemind “changes” on all manuscripts.

What I do is look at all the git commits I have made until the present (in a script of course) and use some code I found on the internet to compute the total number of word changes—both additions and deletions. Since often what one needs to do is make edits, which will not actually increase the final word count.

The script I have seems to sometimes overestimate the count though, somehow—I’m not sure why, since the “vanilla” git diff is usually very clever about understanding when things haven’t changed. So I have to ratchet the goal a lot.

Important part of the code (in python):

for n, f in enumerate(files):
    commits = subprocess.run(['/usr/local/bin/git', 'rev-list',branches[n]], stdout=subprocess.PIPE,cwd=dirs[n]).stdout.splitlines()
    commits = [commit.decode() for commit in commits]
    for m, commit in enumerate(commits[:-1]):
        commit_prev = commits[m+1]
        stuff = subprocess.run("git diff --word-diff=porcelain " + commit + " " + commit_prev + " -- "+f, stdout=subprocess.PIPE,cwd=dirs[n],stderr = subprocess.PIPE,shell=True).stdout.splitlines()
        for _s in stuff:
            s = _s.decode()
            if s[0] in ["+", "-"] and (len(s) < 3 or not s[2] in ["+","-"]):
                count += len(s.split(" "))

Also, if anyone already has something set up that works well with Obsidian, I’d be interested in seeing it. Of course, the files are just markdown stored in a specific place in my computer, so I guess it should be trivial.

2 Likes

That’s a really good point! Thank you for sharing the script! I’ve updated the top post to mention your comment.

2 Likes