Showing posts with label project. Show all posts
Showing posts with label project. Show all posts

Thursday, March 10, 2016

Static website generation with Hugo & Gulp

It has again been a while since I posted. I've been working on some projects but nothing I really felt like blogging about until now. One of the projects I was working on introduced me to Hugo, which is a static website generation engine that takes metadata and templates and generates a set of HTML pages from them so that it's ready to be uploaded to a simple web server such as S3. On another project, I used Gulp for the first time to automate some repetitive build tasks such as minification and also to run BrowserSync so that I could make changes and have them appear automatically in-browser after I saved them. For my latest project, I want to combine the two so that I can generate a Hugo site and then post-process it, and also have that processed site appear in-browser whenever I make any changes. I'm happy with how this has turned out and I didn't see any blog posts about how to do this so I figured it was a good time for a post.

The Goal

By the end of this post, what we will have is:

  • Hugo generating index and content pages based on a simple template and some simple content, and updating that whenever the content or template changes.
  • ES6 code will be automatically transpiled to JS.
  • JS, HTML, and CSS will be automatically minified when building for production.
  • It will be possible to run a browser and have it automatically refresh whenever any of the HTML, JS, or CSS changes (and is automatically processed).

The project is up on GitHub if you want to see the finished product, but I'll spend the rest of the blog post going through the process step-by-step.

Hugo

The first step is to install and set up Hugo. They have a great quickstart guide on their site, so it's best to just go through that if you haven't already. What you should end up with is a basic site with an about page and a post, and you should be able to spin up a simple server that will watch for changes and automatically update the site. However we want to go another step and process that with Gulp, so we won't be using the Hugo server. We do want to use Hugo to watch for changes and generate the updated site, and for that we use the command:

hugo -w -s .\hugo-site -d ..\hugo-generated --disableRSS

This will (-w)atch the (-s)ource folder, which I put into a hugo-site subfolder, and generate it into the (-d)estination of another top-level subfolder hugo-generated. I've also disabled the generation of RSS feeds and haven't used a theme since I used a simple layout instead, but you can add or change these options as needed.

It's a bit annoying to have to remember this command, and we're going to be using npm anyway, so we might as well make a package.json file and add the command as a script, so we just need to remember "npm run hugo".

{
  "private": true,
  "engines": {
    "node": ">=0.12.0"
  },
  "scripts": {
    "hugo": "hugo -w -s .\\hugo-site -d ..\\hugo-generated --disableRSS"
  }
}

Gulp First Steps

Now that we have a Hugo generated folder, what we want to do is use Gulp to pick up any files from there, transform them as needed, and output them to a final distribution folder I'll name gulp-dist. It might be possible to instead make a Gulp task to run Hugo and do everything in one go, but this way is simpler and it lets me see exactly what both Hugo and Gulp are doing in case of any problems.

The first thing we'll get Gulp to do is to simply copy the hugo files to the new gulp-dist folder, and make some helper scripts to clean up the gulp-dist and hugo-generated folders. We'll clean the gulp-dist folder before each build, but leave the cleaning of hugo-generated to be on-demand since we would need to manually trigger a Hugo generation after we do that. So what we need to do is install Gulp and the del library using npm.

npm install --save-dev gulp del

And then make a small gulpfile.js with the build and clean tasks.

var gulp = require('gulp');
var del = require('del');

var hugoBase = './hugo-generated';
var distBase = './gulp-dist';

gulp.task('clean', function() {
 del([distBase + '/**/*']);
});

gulp.task('clean-hugo', function() {
 del([hugoBase + '/**/*']);
});

gulp.task('build', function() {
 return gulp.src(hugoBase + '/**/*')
            .pipe(gulp.dest(distBase));
});

gulp.task('default', ['clean'], function() {
 gulp.start('build');
});

I won't spend too much time explaining how the gulp scripts work - hopefully it's clear enough by itself but if not you can dig into the Gulp docs. Basically though, the clean tasks are just deleting files, the build task is just copying files, and the default task first runs clean then build. This means we can just run "gulp" instead of "gulp clean" then "gulp build".

BrowserSync

Now let's get Gulp to do something useful - let's make a task that will open a browser and automatically copy the source files and then refresh the browser whenever any changes are made. To do this, we need to use BrowserSync and file watchers.

npm install --save-dev gulp del
// ...
var browserSync = require('browser-sync').create();

gulp.task('serve', ['build'], function() {
 browserSync.init({
  notify: false,
  server: {
   baseDir: distBase
  },
  reloadDelay: 1000,
  reloadDebounce: 1000
 });

 gulp.watch(hugoBase + "/**/*", ['build']);
 gulp.watch(distBase + "/**/*").on('change', browserSync.reload);
});
// ...

When we run "gulp serve", a browser should be automatically opened, and if we still have the Hugo generator running in the background, we can make changes to the Hugo content, see Hugo generate that into hugo-generated, then see Gulp copy that to gulp-dist and refresh the browser. Magical! The BrowserSync options we are using are notify, which removes an annoying popup on the browser, server, which says where to serve from, and reloadDelay/reloadDebounce, which helps to avoid multiple refreshes when Hugo regenerates all the files. The watcher on hugo-generated tells Gulp to re-build whenever that changes, and the watcher on gulp-dist tells BrowserSync to refresh the browser whenever that changes.

ES6 Transpiling

The next thing I wanted to do was to have Gulp convert ES6 code into plain JavaScript, so that I can use the new features but the code continues to work on all browsers. To do this we'll use Babel, and before writing any new code, we can change the gulpfile itself to ES6. So install babel and the ES6 (a.k.a. ES2015) preset:

npm install --save-dev babel-core babel-preset-es2015

Make a simple .babelrc file to tell Babel we want to transpile ES6.

{ "presets": ["es2015"] }

And then rename gulpfile.js to gulpfile.babel.js and convert it to ES6.

'use strict';

import gulp from 'gulp';
import del from 'del';
import bs from 'browser-sync';

let browserSync = bs.create();
let hugoBase = './hugo-generated';
let distBase = './gulp-dist';

gulp.task('clean', () => {
 del([distBase + '/**/*']);
});

gulp.task('clean-hugo', () => {
 del([hugoBase + '/**/*']);
});

gulp.task('build', () => {
 return gulp.src(hugoBase + '/**/*')
            .pipe(gulp.dest(distBase));
});

gulp.task('serve', ['build'], () => {
 browserSync.init({
  notify: false,
  server: {
   baseDir: distBase
  },
  reloadDelay: 1000,
  reloadDebounce: 1000
 });

 gulp.watch(hugoBase + "/**/*", ['build']);
 gulp.watch(distBase + "/**/*").on('change', browserSync.reload);
});

gulp.task('default', ['clean'], () => {
 gulp.start('build');
});

Hopefully everything is working exactly as it was before, but now we're in a better position to write some ES6 and both transpile that to JS and generate sourcemaps at the same time. We'll make a simple JS file under hugo-generated/scripts and add that to the layouts so it's automatically included in all generated HTML.

let test = "testing";
setTimeout(() => console.log(test), 1000);

Now we need to install the Gulp plugins that will allow us to transpile and generate sourcemaps.

npm install --save-dev gulp-sourcemaps gulp-babel

And update the gulpfile to do the transpiling and sourcemap generation. At the same time we'll make the file selectors more specific so we can tell Gulp to process each type of file differently.

// ...

import sourcemaps from 'gulp-sourcemaps';
import babel from 'gulp-babel';

gulp.task('html', () => {
 return gulp.src(hugoBase + '/**/*.html')
            .pipe(gulp.dest(distBase));
});

gulp.task('extras', () => {
 return gulp.src(hugoBase + '/sitemap.xml')
            .pipe(gulp.dest(distBase));
});

gulp.task('styles', () => {
 return gulp.src(hugoBase + '/styles/**/*.css')
            .pipe(gulp.dest(distBase + '/styles'));
});

gulp.task('scripts', () => {
 return gulp.src(hugoBase + '/scripts/**/*.js')
            .pipe(sourcemaps.init())
            .pipe(babel())
            .pipe(sourcemaps.write('.'))
            .pipe(gulp.dest(distBase + '/scripts'));

});

gulp.task('build', ['html', 'scripts', 'styles', 'extras']);

gulp.task('serve', ['build'], () => {
 browserSync.init({ ... });

 gulp.watch(hugoBase + "/**/*.html", ['html']);
 gulp.watch(hugoBase + "/scripts/**/*.js", ['scripts']);
 gulp.watch(hugoBase + "/styles/**/*.css", ['styles']);
 gulp.watch([distBase + "/**/*.html", distBase + "/scripts/**/*.js", distBase + "/**/*.js"]).on('change', browserSync.reload);
});

// ...

And that's all we need to do! You should now be able to see Gulp taking the ES6 code from hugo-generated and using it to create JS and mapping files in the gulp-dist folder.

Linting

Another thing that's useful to have in the world of JavaScript is linting, so that you can have a bit more confidence that the code you're writing is good quality and free of any obvious bugs. This is another thing that is very easy to do when using Gulp. We'll be using ESLint, and all we need to do is install it and create a simple Gulp task.

npm install --save-dev gulp-eslint
// ...

import eslint from 'gulp-eslint'

var lintOptions = {
 extends: 'eslint:recommended',
 rules: {
  quotes: [2, "single"],
  "no-console": 0
 },
 env: {
  "es6": true,
  "browser": true
 }
};

gulp.task('lint', () => {
 return gulp.src([hugoBase + '/scripts/**/*.js'])
            .pipe(eslint(lintOptions))
            .pipe(eslint.format())
            .pipe(eslint.failAfterError());
});

You can leave this task as something completely separate to run when you want, or make it a prerequisite for the build task, or even have separate tasks for development lint options and production lint options. I just have it alongside the clean script and run it when I run the default Gulp script, before doing any other processing.

Minification

The last thing I wanted to do was to have an option to build everything minified, so that it was ready to be uploaded to my webserver. The way I decided to do this was just to have an option I could pass to the "gulp" or "gulp build" commands which specified that I was building for production, while leaving everything else as-is. This means that when I'm developing, everything will stay un-minified, and I'll only do the minification when I'm ready to deploy. To do this, we'll need a few different Gulp plugins:

  • gulp-util allows us to access arguments we pass to the gulp commands (we'll be using --production only)
  • gulp-if allows us to perform processing only if that argument is passed
  • gulp-uglify minifies JavaScript
  • gulp-cssnano minifies CSS
  • gulp-htmlmin minifies HTML

As with most Gulp code in this post, doing this is fairly simple and self-explanatory.

npm install --save-dev gulp-util gulp-if gulp-uglify gulp-cssnano gulp-htmlmin
// ..

import util from 'gulp-util';
import gulpif from 'gulp-if';
import uglify from 'gulp-uglify';
import cssnano from 'gulp-cssnano';
import htmlmin from 'gulp-htmlmin';

gulp.task('html', () => {
 return gulp.src(hugoBase + '/**/*.html')
            .pipe(gulpif(util.env.production, htmlmin({collapseWhitespace: true})))
            .pipe(gulp.dest(distBase));
});

gulp.task('scripts', () => {
 return gulp.src(hugoBase + '/scripts/**/*.js')
            .pipe(sourcemaps.init())
            .pipe(babel())
            .pipe(gulpif(!util.env.production, sourcemaps.write('.')))
            .pipe(gulpif(util.env.production, uglify()))
            .pipe(gulp.dest(distBase + '/scripts'));
});

gulp.task('styles', () => {
 return gulp.src(hugoBase + '/styles/**/*.css')
            .pipe(gulpif(util.env.production, cssnano()))
            .pipe(gulp.dest(distBase + '/styles'));
});


// ...

Now all we need to do is run "gulp --production" and our dist folder will be cleaned and then populated with fully minified JS/HTML/CSS!

Conclusion

And that's it! We have done everything we set out to do, and it was pretty easy. Gulp is pretty great like that. Hopefully this post has helped someone out there, and remember that the full code is available on GitHub in case anyone wants to use it as a starting point.

Sunday, September 21, 2014

Long Time No Post

Wow, it's been a while since I've posted anything. I have been keeping busy though. I haven't started on any major new projects, but I've made some pretty big changes to some of my existing projects so I might as well write something about them.

Firstly, the thing that's been taking up most of my spare programming time has been a rewrite of Scrobble Along. I decided to rewrite it mainly because the old code was written in an old version of TypeScript in Visual Studio, and after I installed the new TypeScript compiler my Visual Studio code stopped compiling. Rather than try to port over the TypeScript I started making hotfixes to the generated JavaScript and everything got really messy really quickly. I also really didn't like that the scraping part which gets all the tracks that are playing on the various radio stations was mixed in with the frontend website code. Also all programmers love starting a project from scratch, and I was pretty keen to work on another project that could use AngularJS. Now I have two separate codebases, the scraper runs on a Linode server I have, and it just loads all the stations about every 30 seconds and grabs the tracks from their various HTML and JSON sources, then scrobbles the tracks to the station's profile and to anyone who is scrobbling along. The frontend website is running on Heroku, and all it does is load information about the stations, and adds record to my database when someone starts or stops scrobbling along. It's much saner code now and I still really love AngularJS so I'm much happier about the project now. I did have to make one other major change, and that was to make the website pull the latest track details from my database rather than trying to load it from the last.fm API. For some reason, when I re-wrote the code it started taking a whole lot longer to load all the station details from last.fm, so it was taking a good 30 seconds for everything to show up. Right now it's loading all profile images, recent tracks, etc from my database so it's a whole lot faster than it used to be. I've got the code on two separate github repos, but I made a simple repo that has both of them as submodules here, so feel free to check out the code for more details.

The second thing I've been spending some time on has been updates to Trashbot. I've been experimenting with ways that I can try to combat bots that are just taking everything from as soon as it is dumped in. My plan was to limit the number of trades for each account to taking something like 10 items per day, but that would be impossible until I was able to quickly look up the trade details. As a first step I wanted to make a record of all the details available in MongoDB, under 3 tables. The first one would be a summary of each user, containing the total number of trade requests, items taken and donated, friend requests etc. The second one would be granular details for each trade, including the item, time, user, etc. The third one would be a record of the number of trades each user had made in each day and how many items they took and gave. Once that was set up I was going to look up the daily trades before accepting anything, and if a user had already taken too much I was going to make the bot refuse the trade. I've got the details being saved to MongoDB now, but I'm 90% sure I'm not actually going to refuse trades, as when I look at the details, there really are not many individuals who are taking the majority of the items. I think just occasionally looking though the details and banning a few people is the best option, any systematic method I come up with will just end up as an arms race against any "takerbots" that are out there. It was an interesting process to get all the details to be recorded though. I ended up writing a simple REST server that would update MongoDB when various POST requests were sent to it, e.g. I have something like /trade/userid/tradeid/itemid/taken which will update all the tables with one more trade item taken by a particular user on a particular day. Doing it this way meant that I was able to record the trade from both the bot which is accepting trade requests, and the CasperJS script that is accepting the trade offers. Again, the code is up here on GitHub if anyone is interested.

The final thing I'll mention was a relatively small weekend project I did for a battle created on the /r/webdevbattles subreddit. The challenge was to build an elevator simulation, and it piqued my interest because it was similar to a job interview question I got which I did not to very well on. Most of the people competing were focusing on the "frontend" part of the problem, e.g. by making CSS to show elevators moving up and down, but I wanted to focus more on the "engine" part of the problem, as that was what the interview question was about. I ended up writing a simulation that represented passengers and elevators as individual state machines, which would operate independently. The passengers waited on a floor, requested their destination, then waited around constantly checking for an open elevator that was going in the right direction, and at that point they would get on and wait for the doors to open on their destination floor before getting out. The elevators had a set of target floors and they just moved to those floors and opened their doors, waited until no-one had entered for a while, then headed to their next floor and opened their doors, etc. A central "brain" was in charge of reacting to floor requests from passengers and assigning those floors to one of the several elevators. I think it was a good idea, but I was limiting myself to a weekend's worth of work, so it's not really working 100% right now and elevators have a habit of bouncing between floors indefinitely and never reaching their target floors. I'm pretty sure the elevators need some sort of floor queue rather than just using a set of floors that they need to end up on at some point, but I'm not really planning to test that theory any time soon. Once again, I used AngularJS to visualize the engine data, and was very impressed with what I was able to get working in a pretty short amount of time. The code is up here.

And that's it! I'm not quite sure what I'm going to work on next, it's been a while since I've been in a situation where there isn't a project I know I "should" be working on, so I think I'll have to think up something new.

Saturday, January 25, 2014

Automatically Accepting last.fm Friends

After a bit of a break from working on my projects I've finally got some spare time again so it's time to get back on the saddle. Number 1 on the "kind of annoying thing I should fix" list is related to ScrobbleAlong. I've added a bunch of stations since I first launched the site and I'm now running about 30 different last.fm accounts for those stations. Fans of the stations want to be friends with them on last.fm, and I'm happy about that but there is no API for accepting friends so about once a week I've just been logging into all the accounts and accepting all the friends. Being a software developer, I wanted to find a way to automate this boring and repetitive task.

My first thought was to use PhantomJS, a headless browser which I've used somewhat successfully to accept trade offers for my Steam Trash Bot which allows you to write JavaScript code to visit a website and do various DOM manipulations on it. After a bit of experimentation I realized that it was very fiddly and hard to do sequences of actions, and some web searching revealed that CasperJS was better for what I wanted to do. CasperJS is a wrapper around PhantomJS that allows you to easily write a sequence of navigation and manipulation steps - exactly what I wanted to do!

The sequence of steps I wanted to go through were, for each account I have, log in, go to the friend requests page, accept all the friends, then log out. Logging in and logging out was fairly easy, I just needed to tell CasperJS to go to the log in page, fill out and submit the form, then submit the logout form, the only trick was that I had to tell it to wait for the redirection after the form submission. Accepting friends was another story since there is no easy way of getting CasperJS to do something for each result of a selector, but as usual, StackOverflow had an answer that pointed me in the right direction. The trick is to come up with a CSS selector that will find the first unaccepted request, then click the accept button and wait until it is accepted, then try again until no more unaccepted requests are found. In last.fm, when you click the accept button, the div it is in gets hidden but the HTML is still there, so most selectors will not be able to tell if it's been accepted or not. Thankfully and slightly confusingly, one thing does change with the request, the action of the form changes from nothing to "/ajax/inbox/friendrequest", so the selector "form:not([action='/ajax/inbox/friendrequest']) input[name='accept']" can be used to find unaccepted friend requests.

Putting all this together I've written a nice little script that will save me literally minutes every week. Just think of what I can do with all those minutes!

Sunday, August 4, 2013

Online Steam Group Chat App

I've just finished off another project, so it's time for another blog post. This time my main goal was to get some experience with AngularJS, something I have been reading a lot about which seems like a pretty cool bit of tech. When I was trying to think of something to make, I remembered that in the early days of my Steam chat bot, I made a little website that would allow people to send a message to the chat room through the bot. It was just a bit of fun at the time but I thought I could expand on that and make a web interface into a group chat room, something that Steam has not yet provided for some reason, even though you can do user-to-user chat through their site. This would also give me an opportunity to use Socket.IO, something I'd used previously but not really well.

This took me a bit longer than I was expecting for a "simple AngularJS project" but at the end of the project I've learnt a lot and I've made a nice website that at least a few people should find useful. It looks and acts pretty much exactly like the Steam client which I'm pretty happy about, and thanks to Foundation it handles mobile/tablet resolutions quite nicely. I've put the code up over at GitHub, it should be fairly easy to set up for any Steam group so go and have a look if you're interested. Here is a blurry view of what it ended up looking like on the desktop (if the screen gets small enough, the chat member section gets hidden and you can click a button to switch between the chat view and the member view):



Instead of going into implementation details I'll just quickly go through the different technologies I used and explain them one by one.

The first thing I should mention is that I again made use of the excellent node-steam node library, which is what allowed me to mirror the chat messages and send messages to the chat room. I had considered using my chat bot, but in the end I really didn't need any of the features it provided and I decided to just use the vanilla library. There isn't much to go into for my usage of the library, I basically just used the provided functions to get and send messages, and to find out when user statuses changed so that I could update the list of users in the chat room.

For client-side code I used AngularJS, which as I mentioned was the main reason for starting work on this project. I was very impressed with Angular, whenever anything changed on the Steam side (a new message, a user joined or left, or a user's state changed), all I needed to do was update my model and the UI was magically updated. I also really liked the separation of controller/filter/directive/services. Although I didn't really make use of any services (except a pre-made Socket.IO one), it was great to be able to make a filter that built a URL for a Steam profile image given a set of hex numbers, or a directive that represented the display of a users information, and have that completely separated out from my main app code so I could just concentrate on the task at hand. I feel like I still have a lot to learn about Angular, and I'm pretty sure a lot of what I did wasn't the "correct" Angular way of doing things, but even just scratching the surface of what Angular has to offer has been very interesting.

For server-side code I used the express framework, which I've used for a few different projects before. Since I was working on a single-page app without too much back-end logic (besides things I'll mention separately), there isn't too much to go into with my usage of express. One thing I'll say about the server-side implementation in general is that I really should have used TypeScript. The reason I didn't was because I wanted to use plain JavaScript for the front-end, and I thought it might get a bit confusing to switch between the two. It was good to get a bit more experience with JavaScript, but I really feel like some sort of type checking is crucial to coding, and TypeScript would have been a good safety net to use.

The communication between server and client was handled mainly by Socket.IO. When someone visits the site, they have a socket set up for them which goes through some authentication. To be able to chat they need to be signed in through Steam, so the Socket.IO authentication has access to the user's Steam ID. I use this to make sure that the user should be able to see the chat room, and if they are I set up a bunch of socket listeners so that I can notify the client of any chat room changes, or other state changes such as notifications that the chat has been muted or that the proxy user has disconnected for some reason. One thing I noticed was that a lot of this code was just boilerplate, since I had a model of sorts on the server side and an Angular model on the client side, and most of the Socket.IO code was used to synchronize these two models. I was going to try to think of a way to build some sort of system that would automatically listen for any changes on the server side and send that down to the client side via sockets, but it was looking like it was going to take too much time. Now that I've finished the project I think I may revisit that, as it seems like something that would be very useful for anyone making an Angular app that needs to be notified of state changes from the server side.

For the website layout I decided to use Foundation. I was originally going to use Bootstrap, but I wasn't going to use any of their styles and I had heard that Foundation was better as a "clean slate" solution. Also admittedly I just read about Foundation and it looked like something new and interesting. In the end I'm pretty happy with the way it worked out, it is a bit simpler than Bootstrap and it had a few features that came in very handy, particularly Top Bar which handled my menu needs really well, and the Reveal modal dialog which was great to use as a state messaging system since I needed something that would block the normal use of the site and provide a message in a very obvious way. One thing I wasn't quite able to work out was a non-hacky way of providing a mobile and full-screen interface to the chat room. For normal website usage I wanted the chat room and member details to be shown side-by-side and for mobile use I wanted the user to be able to switch between the two by pressing a button. In the end I solved this by having two completely separate divs, one that is shown for desktop use and one that is shown for mobile use. This wasn't too bad since I made use of Angular scope and directives to separate out code that would otherwise have been duplicated, but I could tell that there must be a better way of doing it. Of course, this isn't a problem with Foundation, just my lack of experience with it.

One final thing I'll mention is the authentication solution I came up with. This is one thing that went through a few different iterations but I think the solution I ended up with works really well. The first part of authentication was simple enough, Steam provides an Open ID login library in their Web API, so all I needed to do was use an Open ID node library and I could get access to a user's Steam ID without any risks. The second part of authentication was trying to figure out if a particular Steam ID should be able to see the chat room. I had originally thought of having a whitelist, so a user would have to request access and I'd have to go in look at the user's profile page and approve if it looked OK, but I decided that was too much work. After this I thought I'd just make it a rule that a user would have to be in the chat room the first time they used the site, and that would automatically add them to the whitelist so they'd be able to log in without being in chat from then on, but that seemed a bit too confusing for users. After a bit more digging around in Steam's web API, I found out that they provided a way to see the groups a particular steam ID belongs to, so I decided to just use that. Any time a user logs in, I check to make sure they are part of the group, and allow them access if they are. This is completely transparent to the users and it keeps out randoms from the site. The only problem is that someone can be kicked/banned from the chat room and they'd still be able to use the site, but that rarely if ever happens in the steam group I'm using the site for so it's not a big concern.

So that's another project and rambling blog post done. Hope either of them come in handy for someone else at some point!

Sunday, June 9, 2013

Steam Chat Bot

I'm going to keep this ball rolling and actually write a blog post about a project before moving on to my next one. I just finished this one yesterday so it's still very fresh in my mind, but I don't think there is too much to say about it so this post will be shorter than my last one.

For this project I made a chat bot for a Steam group chat room that I've been going to for a while. I got the idea to make this because some sort of bot started joining the chat room a while ago, I'm still not sure what its purpose is, but almost every day it will come into the chat room, say "hi" and then leave a few minutes later. After seeing that happen for a while, I was linked to another bot called Dota 2 Dispenser which was made as a way for people to share the Dota 2 invites that hang around uselessly in their inventory. Seeing that the Dota bot uses a Node.js library, I thought back to days I used to go to a few IRC chat rooms and I remembered that there were a few pretty hilarious bots around so I figured that making a chat bot for Steam would be a neat little project to work on. It started as just a bot that says a few in-jokes randomly, but it has expanded a bit as I found a few really useful Node.js modules. Right now it automatically responds to various messages, it is an interface into Cleverbot so that it can communicate in some sort of intelligent way, it searches YouTube or Wolfram Alpha and spits out the result to the chat room, and it automatically posts links to a Tumblr page.

The code is up on GitHub and the package is published on npm, so take a look if you're interested. The basic outline of the code is that there is a bot class that handles the connection to Steam and intercepts the messages that are provided by the steam node package. This bot has a set of trigger objects, which are notified of things like chat messages and they can choose to respond by calling a "send message" function on the bot. Each of the triggers had its own set of challenges, but many of them are basically just a wrapper around another node package, e.g. cleverbot-node, wolfram, tumblr.js, and youtube-feeds. I think the most interesting part of the implementation is the factory I made to create the triggers, but I'll leave the details of that to another post since I couldn't find another good description of how to do this online (although I'm sure it's been done before).

I decided to use plain JavaScript and Sublime Text as an editor for this project, and the main lesson I learnt was that I much prefer the TypeScript & Visual Studio combination. I was writing code a bit faster since I didn't need to worry about syntax as much, but this came at the expense of having annoying bugs pop up. After I had spent an hour trying to work out why something wasn't working, only to discover that I had misspelled a variable name, I started wishing I was writing TypeScript again. I did like using Sublime Text, but its code completion functionality can't compete with Visual Studio's Intellisense.

So that's another project done! It feels pretty good to actually be finishing projects now rather than spending months on them and then leaving them unfinished.

Saturday, June 8, 2013

ScrobbleAlong

Once again I've moved on to a new project and stopped thinking about my old one, but now that I've wrapped up the new one I guess I really should come back and write something about the old one. This is mainly going to be an "after action report" so that I have an opportunity to go through the technology choices I made and think about what worked and what didn't. I'll go into a bit of technical details but I'm not really 100% happy with the code so I'm not going to put that up anywhere. But first ...


What Is It?


ScrobbleAlong is a website I made which does two (closely related) things. It's a background process that continuously polls the "now playing" feeds of a few radio stations I like and scrobbles all the tracks to last.fm.  It also has a frontend that lets other last.fm users select a radio station that they are listening to so that the songs can also be scrobbled to their account. It's a nifty little tool for people who like to scrobble absolutely everything they listen to, and it's also a nice way to see what various radio stations are playing. If I ever want to see what the latest popular tracks are back home in Australia, I can load up Triple J's last.fm page and check the most played tracks for the last few weeks.

The tool actually has a pretty long history, it started a few years ago as a Python script that just did the radio station scrobbling, but I was looking for something to do as a Node.js project, so I decided to update it and add the "scrobble along" functionality. I'm pretty happy with the way it turned out, if I did it again I would probably do a few things differently, but as a first "proper" Node.js project I think it worked out pretty well.


How Does It Work?


Scrobbling for the stations is handled by a task that runs every 15 seconds, which calls an update function for every station. This update function does a HTTP request for a URL that contains the details for the currently playing song. A parser takes the body of the request and extracts out the artist and song names, and this is compared against the last time the URL was queried. If the song changed since the last request, and it was playing for a long enough time (>30 seconds), the last song is scrobbled. If the new song is valid (e.g. something is actually playing), a "now playing" request is sent to last.fm. One fiddly thing here is that there is no way to tell how long the song will play for, so we tell last.fm that the song is 40 seconds long, and update it again in 30 seconds if it's still playing. 

Scrobbling for users (scrobbling along) is handled mainly using a nice last.fm Node.js module which supports callbacks that are fired when a song has just been scrobbled or a song has started playing. Using this it is fairly easy to have a list of users attached to each station which can be updated whenever the station is updated. Again, some cleverness needs to be applied to avoid problems related to an unknown song length, otherwise only a single now playing notice is sent, so it looks like the user only listens to each song for 30 seconds, then stops listening until the next song starts. For the scrobble along case, this is handled using an interval that fires every 30 seconds and updates the now playing details, which is killed when the song is eventually scrobbled.

The front-end only needs to update the backend storage (MongoDB in my case), which contains a list of users and the station that they are listening to. The stations are presented in a pretty nice way thanks to the Isotope library which works pretty well with Bootstrap to make a nice responsive design. I also chucked a little bit of socket.io in there so that each station "block" automatically updates its display of the latest played tracks.


Lessons Learnt


I used a whole lot of technologies that I'd never used before when writing Scrobble Along, so I'll quickly go through them with some short impressions on what I thought of them.


TypeScript


I've now written a pretty significant project in both TypeScript and plain JavaScript, and I've got to say I really like the safety net and cleaner code that TypeScript provides. I can be a bit annoying sometimes, especially when I was using a definition file that wasn't quite right and I had to keep updating the definition before the app would compile without errors. I also wasted a lot of time writing definitions for modules that didn't have any, which I probably wouldn't bother with again since the time spent was not really worth it. I think in the future I'll continue to use TypeScript but I'll make liberal use of the "any" keyword for modules that don't have any definitions.


Visual Studio


Visual Studio and TypeScript are a great combination. The Intellisense and code navigation options are brilliant. It's a shame that it's not possible to use the debugging tools, but I'm pretty certain I'm going to be using VS for all my TypeScript projects.


Node.js


This was my first attempt at using Node.js and I was very impressed. There are pre-made modules for basically anything you can think of, it reminds me of Python development in that sense. It's also a very rapid development process which is always a good thing. Once I got over my irrational hatred of everything JavaScript and realized that it's not all bad, I really started to enjoy it.


Jade


Using Jade instead of HTML is one thing I'm not sure I'll do again. On one hand, it is more compact and it's nice to be able to do things like looping and conditionals, but on the other hand it's a layer of abstraction around HTML that can be a bit confusing. Maybe my only problem was that Visual Studio doesn't handle Jade files nearly as well as it handles HTML, so I might stick with it but try using Sublime Text to edit it rather than Visual Studio.


MongoDB


I used MongoDB for my data storage, and it's been a great first NoDB experience. It's really easy to use, easy to set up locally, and powerful enough for my needs while still being simple to use. There is also a site called MongoLab that has a generous free tier that I'm using for my production data, so I'm keeping my operational costs at the best price point of $0.


Socket.io


I must admit that I really didn't use Socket.io the best way, mainly due to my inexperience and the fact that by the time I got up to using it I just wanted to get the project finished as soon as I could. Even though I used it really badly, it still worked pretty well for me, and I'm sure I'll find more uses for it now that I almost understand how to use it.


Isotope


This worked out really well for me, but only because I was doing something that was exactly what it was designed for. If I am ever making something that is a grid of things that needs to be able to be rearranged in a responsive way I'm sure I'll use it again.


Bootstrap


I have a love-hate relationship with Bootstrap, it's great when it works but it seems to force pages to be designed in a particular way that doesn't allow much flexibility. I'm sure this is because I'm not a designer and I'm just sticking with the defaults, but I still think I'll try to look into alternatives next time I'm designing a site. I must admit it is really easy to use if you just stick to the defaults though.

The End


So that's another project done! It's a bit buggy, but it works and at least a few other people are using it, so I'm counting it as a success.

Saturday, April 27, 2013

Caption Captain

Before I write a post about my most recent project, I thought it would be a good idea to write about the thing I did before that. It had a much smaller scope, and I made it so long ago now that I'm in danger of forgetting absolutely everything what I did.

Before I start the explanation, I should just provide a link to the site - http://captioncapta.in. Glad that's out of the way! So Caption Captain is a game that picks one image and three titles at random from reddit, and the goal is to pick the title that the image was submitted with. With a subreddit like Reaction GIFs this is a pretty fun game and an entertaining way of browsing the latest submissions. The game also works with other subreddits, but obviously the ones that are not image-heavy will not work too well, and the captions are sometimes pretty obvious for subreddits like funny or gifs.

The main reason I wanted to make this was both to have a bit of practice with Dojo and Bootstrap, and to see what I could do with a site when I limited myself to having a minimal infrastructure - I wanted to implement everything in pure HTML and JavaScript and have no server-side logic. That means no database, no server to access remote URLs, and no URL routing. The site could be served as a single HTML file, but I split it up a bit for clarity. It's currently running on Amazon S3 so it has minimal cost and maintenance.

This post will probably be light on the code, but I've put up all the source on GitHub so you can follow along by going there.

The first problem I encountered was due to my technology choices. Bootstrap (the JavaScript part at least) is generally written to be used alongside jQuery instead of Dojo. I would also prefer to use jQuery, but I knew I had to use Dojo for another thing I was working on, so I wanted to get at least a little bit of practice with it. Thankfully, some kind soul has done the hard work and re-written Bootstrap's JavaScript modules in a format that works well with Dojo and they creatively named it Dojo Bootstrap. All I wanted to use was Bootstrap's Modal functionality, and with Dojo Bootstrap this was as easy as require-ing the Modal module and putting the right tags into the HTML.

The next problem to solve was thinking about how to get subreddit data. Usually I'd just write some code into the server to query reddit, then return that as part of a site or ajax request, but since I didn't want to actually use a server this was not possible. Luckily, reddit's JSON API supports the usage of JSONP, which means we can call it on the client side and use the response inside a JavaScript function. Using this I made a function that would attempt to get the next image and set of captions, and if there were not left it used JSONP to get some more images and captions from reddit before returning one of them. All of the image loading logic is in imageLoader.js but to summarize - getNextImage is the main function, this will return an image if we have it, otherwise it calls _loadImages. _loadImages calls a set of query functions asynchronously, one for a few different sort orders of the subreddit, and that function (_getDataFromReddit) is a fairly generic JSONP call. Once all of these queries finish, the results are filtered to make sure that the link is an image and that its score isn't too low, then it's separated into a set of images and captions that are ready for use.

The rest of the code and HTML (mainly in index.html and controller.js) is pretty standard scaffolding code so I won't go through most of it, but the last thing I wanted to mention was the solution to the problem of URL routing when not using a server. I wanted to support the selection of different subreddits, but the usual solution of having a unique URL for each subreddit and using URL routes to detect the subreddit would not work in a plain HTML page. However, JavaScript does have access to the full URL, including query string, so all we need to do is redirect to index.html with e.g. ?subreddit=reactiongifs, detect that on page load, and the problem is solved.

So that's it, the main thing I learnt is that I don't really like using Dojo as much as jQuery, at least for the "utility" functionality. I still think it's pretty good for UI work, and the rest of it has improved a lot recently, but it's hard to beat jQuery for things like AJAX and async calls. I also quite enjoyed the challenge of making a single-HTML page, and if I ever think of something else that would work in that form I'll have another go at it. It's just lovely to be able to shove something on S3 and forget about it while it keeps running happily in the background.