Snuze

Getting Started: Your First Snuze Script

Ready to jump in and get started? This section walks through the process of installing Snuze and using it to write a basic script that interacts with Reddit's API. When you're finished, you'll have a script that periodically checks your favorite low-volume subreddit for new posts, and sends you an email alert if it finds any.

A quick terminology note: in Reddit parlance, "link" and "post" and "submission" all refer to the same thing. This guide calls them posts while the Snuze code calls them links; the terms are interchangeable.

Contents

Your Environment

Before getting underway, here are some things you're going to need:

[user@host ~]$ php -v
PHP 7.3.9 (cli) (built: Sep  1 2019 12:47:11) ( ZTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.9, Copyright (c) 1998-2018 Zend Technologies

Installing Snuze

Snuze source code is available directly from GitHub and from Packagist.

It's strongly encouraged that you use Composer to install Snuze. This is the only supported installation method.

Create and change to a new directory where you want to work; then run composer require --dev snuze/snuze to pull in Snuze:

[user@host ~]$ mkdir mybot && cd mybot
[user@host ~/mybot]$ composer require --dev snuze/snuze
Using version ^0.8.0 for snuze/snuze
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
 - Installing parseword/pickset (v1.0.2): Loading from cache
 - Installing snuze/snuze (v0.8.0): Downloading (100%)
Writing lock file
Generating autoload files

The --dev is critical, as the current release version is < 1.0. This will install Snuze inside of Composer's standard vendor directory structure, with the autoloader built for you.

Writing Your Script

Recall that we're making a script to periodically check a subreddit for new posts and notify you when it finds some. To that end, think of a subreddit you're interested in, which only gets a handful of posts each day or week. The example code will use /r/CentOS, the subreddit about CentOS Linux.

The strategy

The logic for this script will be:

We'll track the script's last-run timestamp in a text file in the same directory as the script itself.

Create your script file

Start by creating a new empty PHP file in your working directory, let's say sub-poller.php, and opening it in your editor or IDE.

Load and configure Snuze

Before you can use Snuze to interact with Reddit, you have to tell it how to authenticate with the API, and provide any other config options you want to use. For this example, we'll set the mandatory authentication parameters, and leave default settings for everything else.

The first part of any Snuze bot or script will look something like this:

<?php
require_once __DIR__ . '/vendor/autoload.php';

/* Set authentication and configuration parameters */
$config = [
   'auth.client_id'     => 'your-client-id-from-reddit',
   'auth.client_secret' => 'your-secret-from-reddit',
   'auth.username'      => 'your-reddit-username',
   'auth.password'      => 'your-reddit-password',
   'auth.user_agent'    => 'php:YourAwesomeBot 1.2.3 (by /u/your-reddit-username)',
];

/* Pass the config array to a SnuzeFactory to get a Snuze object */
$snuze = (new \snuze\SnuzeFactory($config))->getSnuze();

This bit of code imports Composer's autoloader, creates an array with the config options, and then uses a SnuzeFactory to get an instance of Snuze. Copy and paste this code into your script. Edit the first 4 values in the $config array to align with your Reddit account and app details. Then, for the user agent, make up a bot name and version number, and include your Reddit username in the string.

Get the last-run timestamp

Prior to fetching data from Reddit, let's figure out when the script last ran, so any posts older than that time can be ignored. Reddit uses the unix epoch as a timestamp, so we'll compare against that.

Add this code to your script:

/* Get the unix epoch at which this script was last executed */
$lastrun = (int) @file_get_contents('sub-poller-last-run.txt');

At this point, the script has never run (and that file doesn't exist yet). The first time the script runs, $lastrun will default to 0 and every post will be treated as "new." On subsequent runs, only truly new posts will generate an alert.

Fetch the newest posts from the subreddit

Now that you have a configured Snuze object, you can use it to interact with the Reddit API. It's time to get some data!

Snuze methods that retrieve data from Reddit have names beginning with fetch. There are methods to fetch posts ("links") from a subreddit in all of the sort orders: hot, top, controversial, etc. We'll use fetchLinksNew() to get a collection of the most recent posts. Since the target subreddit doesn't get much traffic, it should be sufficient to grab the most recent 25 posts.

Add this code to your script, specifying a different subreddit if you want:

/* Fetch the newest 25 posts from /r/CentOS */
$posts = $snuze->fetchLinksNew('centos', 25);

This one method call has a lot going on behind the scenes. Snuze will log you in to the API, get an authentication token that's valid for the next hour, store the token in a small SQLite data file in your script's directory, request the most recent 25 posts from /r/CentOS, turn them into Link objects, place those into a LinkListing, and finally return the LinkListing to you.

Check for unseen posts

Calling fetchLinksNew() returned a LinkListing object. This is a collection that holds Link objects, each of which represents a Reddit post. The LinkListing is iterable, so you can call foreach() on it and loop over its constituents.

Let's check for links that were posted since the last time the script was run. If any are found, some information about them will be added to an array. That array will be used to build a notification email.

Add this code to your script:

/* An array to hold any unseen posts */
$newPosts = [];
/* Iterate over the posts retrieved from the Reddit API */
foreach ($posts as $p) {
   /* Was this post was created after the last-run timestamp? */
   if ($p->getCreated() > $lastrun) {
       /* Add this post to the array */
       echo "Found a new post: {$p->getTitle()}" . PHP_EOL;
       $newPosts[] = "<a href='https://reddit.com{$p->getPermalink()}'>{$p->getTitle()}</a>";
   }
}

Here, three methods from the Link object were used:

Now, the script needs to send an email alert if it found anything new. Add this code to your script:

/* If there were unseen posts, send an email notification */
if (!empty($newPosts)) {
   $email = 'your-email@example.com';
   $subject = 'New posts were found in /r/CentOS';
   $body = 'Some new posts were found. Check them out: ' . PHP_EOL . PHP_EOL;
   $body .= join(PHP_EOL, $newPosts);
   mail($email, $subject, $body, 'From: ' . $email);
}

Update the last-run timestamp

Finally the script needs to update its last-run timestamp. The next time it runs, it'll use the new timestamp when locating unseen posts. Add this code to the end of your script:

/* Update the last-run timestamp */
file_put_contents('sub-poller-last-run.txt', time());

The completed script

You should now have a completed script called sub-poller.php that looks like this, with your own authentication information in the $config array, and perhaps a subreddit other than /r/CentOS:

<?php
require_once __DIR__ . '/vendor/autoload.php';

/* Set authentication and configuration parameters */
$config = [
   'auth.client_id'     => 'your-client-id-from-reddit',
   'auth.client_secret' => 'your-secret-from-reddit',
   'auth.username'      => 'your-reddit-username',
   'auth.password'      => 'your-reddit-password',
   'auth.user_agent'    => 'php:YourAwesomeBot 1.2.3 (by /u/your-reddit-username)',
];

/* Pass the config array to a SnuzeFactory to get a Snuze object */
$snuze = (new \snuze\SnuzeFactory($config))->getSnuze();

/* Get the unix epoch at which this script was last executed */
$lastrun = (int) @file_get_contents('sub-poller-last-run.txt');

/* Fetch the newest 25 posts from /r/CentOS */
$posts = $snuze->fetchLinksNew('centos', 25);

/* An array to hold any unseen posts */
$newPosts = [];
/* Iterate over the posts retrieved from the Reddit API */
foreach ($posts as $p) {
   /* Was this post was created after the last-run timestamp? */
   if ($p->getCreated() > $lastrun) {
       /* Add this post to the array */
       echo "Found a new post: {$p->getTitle()}" . PHP_EOL;
       $newPosts[] = "<a href='https://reddit.com{$p->getPermalink()}'>{$p->getTitle()}</a>";
   }
}

/* If there were unseen posts, send an email notification */
if (!empty($newPosts)) {
   $email = 'your-email@example.com';
   $subject = 'New posts were found in /r/CentOS';
   $body = 'Some new posts were found. Check them out: ' . PHP_EOL . PHP_EOL;
   $body .= join(PHP_EOL, $newPosts);
   mail($email, $subject, $body, 'From: ' . $email);
}

/* Update the last-run timestamp */
file_put_contents('sub-poller-last-run.txt', time());

Run Your Script

It's time to test the script and make sure it works. From your shell, run:

[user@host ~/mybot]$ php sub-poller.php

You should see some output indicating "Found a new post: ..." and, assuming your PHP environment is configured to send email, you'll get a new email message about the posts.

Now, immediately run the script again. This time you shouldn't see any output, unless someone happened to make a new post in the brief interval between the two executions of the script.

Scheduling

A script like this isn't very useful unless it runs periodically by itself, so you should probably schedule it. On a Linux system, you can do this by adding a cron job. Here's a sample cron entry to run your script every 5 minutes:

*/5 * * * * (cd ~/mybot && php sub-poller.php >/dev/null)

Don't worry about this being too frequent. Reddit grants you 600 API requests every 10 minutes, and this script only generates one request.

Did you encounter problems?

If Snuze gave you some errors, or if the output wasn't what you were expecting, feel free to visit /r/snuze and ask for help!