Grand Rapids Griffins Alternate Jersey Concept 2018

I thought I’d retired from the Grand Rapids Griffins’ annual jersey design contest but, as this year’s edition rolled around, I realized that I wanted to revisit my 2016 entry and try to tweak a couple things that bothered me from it.

In 2016, I didn’t like that the crest on my submission and the shoulder logo both used the same griffin silhouette.  I thought it looked good but was just a little lazy.  Also, I had intended to enter a red jersey in the contest and was convinced to switch to black at the last minute, so I wanted to return to that idea.

Additionally, at the end of the 2014 contest there was a lesson that I had learned and promptly forgot: Minor league sports teams like logos that have their team name included.  I wanted to take that into consideration this time around.

Keeping all of that in mind, I created a new crest for the red version of my 2016 entry.

My initial logo concept for the Grand Rapids Griffins’ 2018 jersey design contest

I changed the shape of the shield just to change things up a little.  Across it is a banner reading “Griffins” in a vaguely old-German font that I think matches the heraldic crest.  Above that is a stalking griffin in silhouette – because the griffin in the shoulder logo is standing on two legs, I wanted this one to be down on all fours (though only three are really seen).

Like the griffin in the shoulder logo, this new griffin features a pair of homages to other logos.  The tail is the tail that was used in the Griffins’ original logo while the wing is based on the Winged Wheel logo of the Red Wings.

I put the new logo on the red jersey design from 2016 and I thought I was good.

My initial concept for a red jersey for the Grand Rapids Griffins’ 2018 jersey design contest

But then I kept thinking.

In 2016, the jersey I actually liked best was the white one, which I wasn’t allowed to submit because the Griffins specifically asked for a dark jersey.  This year, not only did they not make that requirement, but their promotional calendar has dates for fan-designed jerseys to be worn both before and after New Years’; which is when the American Hockey League switches from wearing white jerseys at home to dark ones.

I decided to switch to the white jersey.  Which immediately gave me trouble with the crest again.

I designed the new crest to work best on red.  It’s primarily black with a heavy white outline.  That outline disappears on a white jersey, so what to do?

I tried out several different color swaps.  Added outlines.  Took the opportunity to play with the sleeve numbers a little, then immediately abandon that idea.

In the end, I decided to go with a logo that mixed elements from my previous submissions with some of my new ideas.

My final logo concept for the Grand Rapids Griffins’ 2018 jersey design contest

The extra outline on the shield helps set it off on the white jersey while not outlining the “banner” keeps the logo from getting too heavy.

My final white jersey concepts for the Grand Rapids Griffins’ 2018 jersey design contest

And those are combined with the previously-created shoulder logo, which sits opposite the logo of the Griffins’ parent club, the Detroit Red Wings.

My alternate/shoulder logo concept for the Grand Rapids Griffins’ 2018 jersey design contest

One final note is that I use a template that doesn’t allow for a laced collar to be displayed.  I would expect one to be used, as shown here.

My final white jersey concepts for the Grand Rapids Griffins’ 2018 jersey design contest, in a template showing how it would look on a player.

View All Images as Gallery

Kalamazoo Wings Jersey Concept 2018

Aside from the Grand Rapids Griffins, I don’t normally enter jersey design contests.  In general, I don’t like “fan designed” jersey contests because rarely does an actual fan of the team win.

I made an exception this year, though, for the Kalamazoo Wings.  I can’t rightfully call myself a Kalamazoo fan given their rivalry with the Toledo Walleye, who are affiliated with the Grand Rapids Griffins and Detroit Red Wings, who I actually am a fan of.

The K-Wings do hold a special place in nostalgia for me, though.  They were the visitors in the first hockey game I ever went to, a Thanksgiving game against the Muskegon Lumberjacks when both teams were in the old International Hockey League and I was about five years old.

So I decided to give the Wings’ contest a shot and, in all honesty, it didn’t go in the direction I expected it to at all.

My initial thought, since all existing K-Wings marks were off-limits, was to turn to mythology.  “Hermes had a winged helmet, didn’t he?”  In addition to winged sandals, yes he did.  So I set about trying to do something like that.

In my head, I was thinking something kind of like the old Ottawa Senators logo, with the team name in a partial roundel broken by the wings of the helmet.  It turns out that “Kalamazoo Wings” is a really hard name to work with without getting letters upside down at some point on the circle.

Also, in all of the logos I’ve worked on in the past, I’ve never tried to draw a face.  As I continued working, I got closer to what I wanted.  I abandoned Hermes and his traveller’s cap and went for a more Nordic full helmet, but the contest deadline came up quickly and I had a conference and a short vacation planned.  It just wasn’t going to come together.

Kalamazoo Wings logo concepts based on Hermes’ winged helmet.

In parallel with the helmet-based logo, I was putting together an alternate logo based around a K in a shield with wings.  I liked the general shape right from the start but tried a variety of fonts for the K, colors, etc.  That process actually went well, and partway through I realized that I had a couple different options I could run with.

Concepts for a Kalamazoo Wings alternate logo, with a K in a winged shield

As I ran out of time, I decided that I needed to switch gears. I had a solid logo in my intended alternate and I had a tertiary that could become a secondary in a pinch (which I was in).  I had a jersey design already in place from a previous Griffins contest so I pulled the plug on continued sketching and called it done.

My 2018 Kalamazoo Wings jersey concept

The crest, as previously mentioned, is a K inside a shield with wings.  In gold and blue, I think it has a bit of an art deco vibe.

That shield is repeated without the wings on the shoulder logo, where it’s adorned with a pair of crossed hockey sticks and the letters KWHC – Kalamazoo Wings Hockey Club.

The jersey itself features a red body and blue sleeves.  I wanted to do contrasting-color sleeves as they give the jersey the “winged” look of the Detroit Red Wings’ away jersey, something I think is appropriate for another team named the Wings.

It’s not what I was aiming for but I think it looks good.  It’s probably not different enough from the K-Wings current design to win the contest, which is why I’m disappointed that I ran out of time to work on the winged helmet idea, which I think was pretty unique.

We’ll see how it goes, though.


View All Images as Gallery

Making Work Slack More Fun: PHPBot and YakBot

I’ve been toying around with Slackbots a bit lately.  Back in January I wrote about publishing a Git log to Slack via one.  They’re dirt simple to implement and can be really useful.

They can also be fun.  I’ve added a couple bots in our office Slack just for posting randomness, running as cron jobs.  I figured I’d take a look at them here.


A couple weeks ago one of my coworkers posted a link to the documentation for a seemingly-random PHP function in our dev Slack channel.  It wasn’t random, it just pertained to an offline conversation, but we still made some cracks about how he was doing it to spread awareness of the function.

At nearly the same time, he and I both said, “That sounds like a great idea for a Slackbot.”  So I made it, and it looks like this:

<?php
$funcs = get_defined_functions();

do {
   $index = array_rand($funcs['internal']);
   $url = 'http://php.net/manual/en/function.' . str_replace('_', '-', $funcs['internal'][$index]) . '.php';
   $headers = @get_headers($url);

   if (strpos(implode(';', $headers), 'rel=shorturl') === false) {
      unset($url);
      sleep(2);
   } else {
      $attachments = [];
      $fields = [];

      $html = preg_replace("|\n[ ]*|is", '', file_get_contents($url));

      $doc = new DOMDocument();
      libxml_use_internal_errors(true);
      $doc->loadHTML($html);
      libxml_clear_errors();

      $xpath = new DOMXpath($doc);

      // "HEADER" INFORMATION
      $el = $xpath->query("//*/div[@class='refentry']")->item(0);
      $name = $xpath->query("./div[@class='refnamediv']/h1", $el)->item(0)->textContent;
      $verinfo = $xpath->query("./div[@class='refnamediv']/p[@class='verinfo']", $el)->item(0)->textContent;
      $refpurpose = $xpath->query("./div[@class='refnamediv']/p[@class='refpurpose']", $el)->item(0)->textContent;

      if (!$name) {
         sleep(2);
         continue;
      }

      // DESCRIPTION
      $el = $xpath->query("//*/div[@class='refsect1 description']")->item(0);
      $title = $xpath->query("./h3", $el)->item(0)->textContent;
      $example = $xpath->query("./div", $el)->item(0)->textContent;
      $description = $xpath->query("./p", $el)->item(0)->textContent;
      $note = $xpath->query("./blockquote", $el)->item(0)->textContent;

      $text = '';
      $text .= '```' . $example . '```' . PHP_EOL;
      $text .= $description . PHP_EOL;
      if ($note) $text .= '>>>' . $note . PHP_EOL;

      $fields[] = [
         'title' => $title,
         'value' => trim($text)
      ];

      $attachments[] = [
         'title' => $name,
         'title_link' => $url,
         'text' => $verinfo . PHP_EOL . $refpurpose,
         'fields' => $fields,
         'mrkdwn_in' => ['fields'],
      ];

      $msg = [
         'text' => '',
         'attachments' => $attachments,
         'username' => 'PHPBot',
         'icon_url' => 'https://example.com/slackbots/avatars/phpbot.png'
      ];
   }
} while (!$msg);

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, 'https://hooks.slack.com/services/TTTTTTTTTT/BBBBBBBBBB/ddddddddddddddddddddddd');
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

As you can see, it’s a little more advanced than just posting a link to a PHP.net URL.  If PHP.net had metadata on its pages that allowed for a nice-looking Slack unfurl, I might have just done that.  Instead I decided to pull content from the page we’re linking to and make the Slack post a little prettier with it.

So how does that all work?

<?php
$funcs = get_defined_functions();

do {

Simple thing here.  Get the defined PHP functions and then start a do…while loop.  By using get_defined_functions() I’m limiting what our PHPBot can link to but I figured there are enough functions available there that it doesn’t really matter.

$index = array_rand($funcs['internal']);
$url = 'http://php.net/manual/en/function.' . str_replace('_', '-', $funcs['internal'][$index]) . '.php';
$headers = @get_headers($url);

if (strpos(implode(';', $headers), 'rel=shorturl') === false) {
   unset($url);
   sleep(2);
} else {

We jump into our do…while loop, where we grab a random function from our array of functions and build what should be a PHP.net documentation URL from the name.  For some reason, not every function has documentation (or at least not that matches this template), which is why we’re in a do…while loop.

We get the headers for that URL and if no shorturl is defined there, we know we don’t have a valid function documentation page.  In that case, we sleep (I like to sleep in situations like this) and unset the URL, then we’ll take another run at the do…while loop.

If we do have a shorturl, we get into the heavy lifting.

$attachments = [];
$fields = [];

$html = preg_replace("|\n[ ]*|is", '', file_get_contents($url));

$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML($html);
libxml_clear_errors();

$xpath = new DOMXpath($doc);

We initialize a couple arrays that we’ll use for building our Slack message, then we pull in the HTML from the documentation page and strip out line breaks and any spaces that might start a line.  We do that stripping so that the text we use in our message later on looks better.

Then we load that HTML into a DOMDocument and fire up XPath for it so we can target the elements we want a little easier.

// "HEADER" INFORMATION
$el = $xpath->query("//*/div[@class='refentry']")->item(0);
$name = $xpath->query("./div[@class='refnamediv']/h1", $el)->item(0)->textContent;
$verinfo = $xpath->query("./div[@class='refnamediv']/p[@class='verinfo']", $el)->item(0)->textContent;
$refpurpose = $xpath->query("./div[@class='refnamediv']/p[@class='refpurpose']", $el)->item(0)->textContent;

if (!$name) {
   sleep(2);
   continue;
}

We grab the element of the page that contains all of the information about the function by targeting the refentry class.

Inside that element is a div with class refnamediv, which contains an H1 with the function name, a paragraph classed as verinfo with information about what versions of PHP support the function, and a paragraph defining the function classed as refpurpose.  We grab each of these as we’ll use them in our message.

If – for some reason – we didn’t get a function name, we sleep (like I said, I like to sleep in these situations) and then head back to the start of our do…while loop.

// DESCRIPTION
$el = $xpath->query("//*/div[@class='refsect1 description']")->item(0);
$title = $xpath->query("./h3", $el)->item(0)->textContent;
$example = $xpath->query("./div", $el)->item(0)->textContent;
$description = $xpath->query("./p", $el)->item(0)->textContent;
$note = $xpath->query("./blockquote", $el)->item(0)->textContent;

$text = '';
$text .= '```' . $example . '```' . PHP_EOL;
$text .= $description . PHP_EOL;
if ($note) $text .= '>>>' . $note . PHP_EOL;

Having advanced this far, we target the “Description” section of the function documentation page, which has a class of refsect1 description.

Inside that div is an H3 that serves as the section title, a div that contains an example use of the function, a paragraph with a description, and an optional blockquote with notes about the function.  We target each of these and pull that content in, then we use them to build the test of our message.

It should be noted that we use markup to make sure that the example is displayed as a code block while the note(s) (if present) are displayed as a quote, mimicking their appearance on PHP.net.

       $fields[] = [
         'title' => $title,
         'value' => trim($text)
      ];

      $attachments[] = [
         'title' => $name,
         'title_link' => $url,
         'text' => $verinfo . PHP_EOL . $refpurpose,
         'fields' => $fields,
         'mrkdwn_in' => ['fields'],
      ];

      $msg = [
         'text' => '',
         'attachments' => $attachments,
         'username' => 'PHPBot',
         'icon_url' => 'https://example.com/slackbots/avatars/phpbot.png'
      ];
   }
} while (!$msg);

With all of the information we need acquired, we build our message.

The description section title and our formatted text from it are added as a Slack attachment field.  That attachment gets the function name as a title (linked to the documentation URL) and it’s text is the version and purpose text we grabbed early on.

We post this all as user “PHPBot” with a hosted avatar.  These two steps aren’t necessary as you can define your incoming webhook’s name and avatar, but we’re reusing a webhook for multiple purposes and, as such, define these for each.

Then we hit the end of our do…while loop.  We’ve assembled our message so we can move on.

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, 'https://hooks.slack.com/services/TTTTTTTTTT/BBBBBBBBBB/ddddddddddddddddddddddd');
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

Lastly, we actually send that message.  It’s just a cURL post to the Slack webhook URL (obscured here) and ends up posting a message that looks like this:

 

PHPBot example

As I said, there’s nothing too complex here.  If anything, this is probably over-complicated.  But it gives us something to talk about at 2:05 every day.


Last week another coworker posted to our developer channel asking that we all send a random photo of a Yak to one of our non-dev coworkers.  My immediate thought was that this was begging to be made into a Slackbot, and thus the YakBot was created.

<?php
$api_key = 'AbCdEfGhIjKlMnOpQrS1TuVw2xYzAbC3DeFgHiJk';
$search_engine_id = '012345678901234567890:abc0defghijk';

$channels = [
   '#developer-channel',
   'U197UV7HV',
   'U1989NZ12',
   'U198JH8GL',
   'U99EYR89S',
   'U198L05JG',
   'U198GH6H7',
   'U74JZ1KAY',
   'UAPFKFAHG',
   'U4U7VSABA',
];

$index = rand(1, 91);

$url = 'https://www.googleapis.com/customsearch/v1?q=yak&cx=' . $search_engine_id . '&imgSize=medium&safe=high&searchType=image&key=' . $api_key . '&start=' . $index;
$json = file_get_contents($url);
$data = json_decode($json);

$image_url = $data->items[0]->link;

$msg = [
   'text' => '',
   'attachments' => [
      [
         'image_url' => $image_url
      ]
   ],
   'username' => 'YakBot',
   'icon_url' => 'https://example.com/slackbots/avatars/yakbot.png'
];

$recipients = [];
foreach ($channels AS $channel) {
   $rand = rand(0, 9);
   if (!$rand) {
      $recipients[] = $channel;
   }
}

foreach ($recipients AS $recipient) {
   $msg['channel'] = $recipient;

   // send message
   $c = curl_init();
   curl_setopt($c, CURLOPT_URL, 'https://hooks.slack.com/services/TTTTTTTTTT/BBBBBBBBBB/ddddddddddddddddddddddd');
   curl_setopt($c, CURLOPT_POST, 1);
   curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
   curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($c, CURLOPT_HEADER, 0);
   curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
   curl_exec($c);
   curl_close($c);
}

We bring in the Google search API for this one in addition to the Slack API but overall it’s a bit simpler because there’s less of a message to build.

<?php
$api_key = 'AbCdEfGhIjKlMnOpQrS1TuVw2xYzAbC3DeFgHiJk';
$search_engine_id = '012345678901234567890:abc0defghijk';

$channels = [
   '#developer-channel',
   'U197UV7HV',
   'U1989NZ12',
   'U198JH8GL',
   'U99EYR89S',
   'U198L05JG',
   'U198GH6H7',
   'U74JZ1KAY',
   'UAPFKFAHG',
   'U4U7VSABA',
];

$index = rand(1, 91);

The first thing we do is define our Google search API key and the ID of the custom search engine that we’re using.  Then we build an array of possible recipients of our yak.  The recipients include our developer channel as well as individual user IDs, to whom the message would be sent in the form of a direct message from Slackbot.

Next we select a random number between 1 and 91.  This is because the Google search API won’t let you request a start point higher than 91 for some reason.  The search results return ten items, which means the most results you can get is 100.  We only need one and one 1/91 is close enough to 1/100 so I do the randomizing up front and then do the lookup and take the first response.

$url = 'https://www.googleapis.com/customsearch/v1?q=yak&cx=' . $search_engine_id . '&imgSize=medium&safe=high&searchType=image&key=' . $api_key . '&start=' . $index;
$json = file_get_contents($url);
$data = json_decode($json);

$image_url = $data->items[0]->link;

Here we actually do that lookup.  We get JSON back and decode it to pull out the first item listed.

$msg = [
   'text' => '',
   'attachments' => [
      [
         'image_url' => $image_url
      ]
   ],
   'username' => 'YakBot',
   'icon_url' => 'https://example.com/slackbots/avatars/yakbot.png'
];

Once we have our image URL, we build our message, which is relatively simple compared to the PHPBot message because all we’re doing is posting an attachment with an image.  As I mentioned above, we also define a custom username and avatar, though we don’t need to.

$recipients = [];
foreach ($channels AS $channel) {
   $rand = rand(0, 9);
   if (!$rand) {
      $recipients[] = $channel;
   }
}

foreach ($recipients AS $recipient) {
   $msg['channel'] = $recipient;

   // send message
   $c = curl_init();
   curl_setopt($c, CURLOPT_URL, 'https://hooks.slack.com/services/TTTTTTTTTT/BBBBBBBBBB/ddddddddddddddddddddddd');
   curl_setopt($c, CURLOPT_POST, 1);
   curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
   curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($c, CURLOPT_HEADER, 0);
   curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
   curl_exec($c);
   curl_close($c);
}

Finally, we wrap it up by determining who to send our yak to.  We loop through each of our channels and generate a random number between zero and nine.  If the number is zero, we add that channel to the list of recipients.

We do that because it allows for some randomness.  It’d get noisy if we were posting yaks too often or too regularly.

Then we loop through the list of recipients and send the message, defining the channel along the way.  The lucky winners end up getting something like this:

YakBot example

Posting to Slack via Git Hooks

At my day job our codebase is kept in a handful of self-hosted Git repositories. We have a tool that runs nightly, emailing out a digest of all of the previous day’s commits.

It’s kinda cool but I have the tendency to ignore it as a wall of text. I prefer more granular messaging and since we’re also using Slack, I saw an opportunity to do something with a post-receive hook to get a message as changes came in.

Posting from Git to Slack isn’t revolutionary. There are a tons of solutions for this out there. In fact, my original attempt just used a modified version of Chris Eldredge’s shell script, which I grabbed off of GitHub.  However, my Bash-foo is weak and we’re a PHP shop so I decided to write a solution based in PHP (though heavily based on Eldredge as I had that code in front of me).

To fire off the PHP script, the post-receive hook looks like this:

#!/bin/bash
while read oldrev newrev refname
	php git-slack-post.php --oldrev="$oldrev" --newrev="$newrev" --refname="$refname"
done

That’s simplified a bit as the actual hooks use an absolute path to the script but you see that the script accepts the oldrev, newrev, and refname arguments.

As for the PHP script itself, it looks a bit like this (I’ve sanitized some things to remove references to our internal services).

#!/usr/bin/php -f
<?php
// expects to be called as follows:
// php git-slack-post.php --oldrev="1234567890abcdef" --newrev="0987654321fedcba" --refname="refs/foo/bar"

const WEBHOOK_URL = 'https://hooks.slack.com/services/ABC123FDG/456HIJ789/tWeNtYfOuRcHaRaCtErS0024';
const ZEROREV = '0000000000000000000000000000000000000000';

// pull the arguments from the command line
$newrev = $oldrev = $refname = '';
extract(getopt('', array('oldrev:', 'newrev:', 'refname:')));

// determine change type based on what revisions are available
if ($oldrev === ZEROREV) {
	$change_type = 'create';
} elseif ($newrev === ZEROREV) {
	$change_type = 'delete';
} else {
	$change_type = 'update';
}

$newrev_type = trim(`git cat-file -t {$newrev}`);
$oldrev_type = trim(`git cat-file -t {$oldrev}`);

$rev = $rev_type = '';
if (in_array($change_type, array('create', 'update'))) {
	$rev = $oldrev;
	$rev_type = $oldrev_type;
} elseif ($change_type === 'delete') {
	$rev = $newrev;
	$rev_type = $newrev_type;
}

if ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'tag')) {
	$refname_type = 'annotated tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/heads/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'branch';
	$short_refname = str_replace('refs/heads/', '', $refname);
} elseif ((strpos($refname, 'refs/remotes/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tracking branch';
	$short_refname = str_replace('refs/remotes/', '', $refname);

	echo '*** Push-update of tracking branch, ' . $refname;
	echo '***  - no notification generated.';

	exit;
} else {
	// this shouldn't be possible
	echo '*** Unknown type of update to ' . $refname . ' (' . $rev_type . ')';
	echo '***  - no notification generated.';

	exit;
}

// get the repo name
// only get the final directory name, cut ".git" off the end
$repo = substr(basename(realpath(getcwd())), 0, -4);

// get the user
$process_user = posix_getpwuid(posix_geteuid());
$user = $process_user['name'];

$header = '[' . $repo . '/' . $short_refname . '] ';
switch ($change_type) {
	case 'create':
		$header .= 'New ' . $refname_type . ' has been created';
		break;
	case 'delete':
		$header .= ucwords($refname_type) . ' has been deleted';
		break;
	case 'update':
		$commits = intval(trim(`git log --pretty=oneline {$oldrev}..{$newrev}|wc -l`));
		$header .= $commits . ' new commit' . (($commits > 1) ? 's' : '') . ' pushed by ' . $user;
		break;
	default:
		// this shouldn't be possible
		echo '*** Unknown type of update to ' . $refname . '(' . $rev_type . ')';
		echo '***  - notifications will probably screw up.';
		break;
}

$start = ($change_type === 'update') ? $oldrev : 'HEAD';
$end = $newrev;

// get git data
$data = `git log --pretty=format:"%an&&&&&%h&&&&&%s&&&&&%b@@@@@" {$start}..{$end}`;

$attachments = array();
foreach (explode('@@@@@', $data) AS $item) {
	if (!trim($item)) {
		continue;
	}
	list($author, $hash, $comment, $body) = explode('&&&&&', $item);

	$text = trim('`' . $hash . '` ' . $comment . ' - *' . trim($author) . '*' . "\n\n" . $body);
	$fallback = trim($hash . ' - ' . $comment . ' - ' . trim($author) . "\n\n" . $body);

	// link jira tasks
	$text = preg_replace('#ABC-(\d+)#is', '<https://jira.example.com/browse/ABC-$1|ABC-$1>', $text);

	$text = preg_replace('#\[ABC\][ ]?\((\d+)\)#is', '<https://jira.example.com/browse/ABC-$1|$0>', $text);

	// link support bugs/tickets
	if (preg_match('#(TICKET|BUG|SUPPORT)[\-| ](\d+)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.php?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

	if (preg_match('#\[HOTFIX\][ ]?\((\d+)\)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.mh?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

	$attachments[] = array(
		'text'      => $text,
		'fallback'  => $fallback,
		'color'     => '#1881d0',
		'mrkdwn_in' => array('text'),
	);
}

// build message
$msg = array('text' => $header, 'attachments' => $attachments);

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, WEBHOOK_URL);
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

We get details about what’s being pushed and build a message out of all of that. Simple enough. So lets break that down a little bit.

const WEBHOOK_URL = 'https://hooks.slack.com/services/ABC123FDG/456HIJ789/tWeNtYfOuRcHaRaCtErS0024';
const ZEROREV = '0000000000000000000000000000000000000000';

// pull the arguments from the command line
$newrev = $oldrev = $refname = '';
extract(getopt('', array('oldrev:', 'newrev:', 'refname:')));

We start by defining our Slack webhook URL (which can be set up at https://my.slack.com/services/new/incoming-webhook) and what an “empty” revision number looks like. Then we pull in the oldrev, newrev, and refname that should have been passed into the script as arguments.

// determine change type based on what revisions are available
if ($oldrev === ZEROREV) {
	$change_type = 'create';
} elseif ($newrev === ZEROREV) {
	$change_type = 'delete';
} else {
	$change_type = 'update';
}

$newrev_type = trim(`git cat-file -t {$newrev}`);
$oldrev_type = trim(`git cat-file -t {$oldrev}`);

If the old revision is empty, it means we’re creating something. If the new revision is empty, it means we’re deleting something. Otherwise it’s an update of something that already existed and continues to do so.

Whatever the change type, we get more information about the old and new revisions by using the backtick operator to run the get cat-file -t command for each revision number.

$rev = $rev_type = '';
if (in_array($change_type, array('create', 'update'))) {
	$rev = $oldrev;
	$rev_type = $oldrev_type;
} elseif ($change_type === 'delete') {
	$rev = $newrev;
	$rev_type = $newrev_type;
}

If the change type is a create or an update, we’ll use the old revision data to reference things going forward. If it’s a delete we’ll use the new revision data.

if ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/tags/') === 0) AND ($rev_type === 'tag')) {
	$refname_type = 'annotated tag';
	$short_refname = str_replace('refs/tags/', '', $refname);
} elseif ((strpos($refname, 'refs/heads/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'branch';
	$short_refname = str_replace('refs/heads/', '', $refname);
} elseif ((strpos($refname, 'refs/remotes/') === 0) AND ($rev_type === 'commit')) {
	$refname_type = 'tracking branch';
	$short_refname = str_replace('refs/remotes/', '', $refname);

	echo '*** Push-update of tracking branch, ' . $refname;
	echo '***  - no notification generated.';

	exit;
} else {
	// this shouldn't be possible
	echo '*** Unknown type of update to ' . $refname . ' (' . $rev_type . ')';
	echo '***  - no notification generated.';

	exit;
}

This is just a bunch of logic that looks at the refname and the revision type and determines exactly what you’ve pushed. If we can’t figure out what it is, we exit with an error.

// get the repo name
// only get the final directory name, cut ".git" off the end
$repo = substr(basename(realpath(getcwd())), 0, -4);

// get the user
$process_user = posix_getpwuid(posix_geteuid());
$user = $process_user['name'];

We determine the repo name based on the path the hook is running from and we get the user it’s running as so we know who did the push we’re about to notify people of.

$header = '[' . $repo . '/' . $short_refname . '] ';
switch ($change_type) {
	case 'create':
		$header .= 'New ' . $refname_type . ' has been created';
		break;
	case 'delete':
		$header .= ucwords($refname_type) . ' has been deleted';
		break;
	case 'update':
		$commits = intval(trim(`git log --pretty=oneline {$oldrev}..{$newrev}|wc -l`));
		$header .= $commits . ' new commit' . (($commits > 1) ? 's' : '') . ' pushed by ' . $user;
		break;
	default:
		// this shouldn't be possible
		echo '*** Unknown type of update to ' . $refname . '(' . $rev_type . ')';
		echo '***  - notifications will probably screw up.';
		break;
}

Now we start building the message that will be posted to Slack. The message begins in the form of “[reponame/branchname]. If it’s a create or delete, we then note what was created or deleted. If it’s a commit, we note the number of commits and who they were pushed by.

$start = ($change_type === 'update') ? $oldrev : 'HEAD';
$end = $newrev;

// get git data
$data = `git log --pretty=format:"%an&&&&&%h&&&&&%s&&&&&%b@@@@@" {$start}..{$end}`;

$attachments = array();
foreach (explode('@@@@@', $data) AS $item) {
	if (!trim($item)) {
		continue;
	}
	list($author, $hash, $comment, $body) = explode('&&&&&', $item);

We’re going to start building a series of messages (what Slack calls “attachments”) detailing items from the Git log pertinent to this push. We use the git log command and define our format. We get the author name with %an, the hash with %h, the commit message with %s and the commit body with %b. Those are all separated by five ampersands, with each item separated by five at signs. We use those goofy separators so we can split on them later, as it’s unlikely anyone enters those in text.

	$text = trim('`' . $hash . '` ' . $comment . ' - *' . trim($author) . '*' . "\n\n" . $body);
	$fallback = trim($hash . ' - ' . $comment . ' - ' . trim($author) . "\n\n" . $body);

Here we actually build our message. The text property of a Slack attachment can be markdown, so we pretty it up a little bit. The fallback property is plaintext so it doesn’t get that formatting. The result is the hash, then the commit message, then the author. If there is a longer commit message, it gets added after that.

	// link jira tasks
	$text = preg_replace('#ABC-(\d+)#is', '<https://jira.example.com/browse/ABC-$1|ABC-$1>', $text);

	$text = preg_replace('#\[ABC\][ ]?\((\d+)\)#is', '<https://jira.example.com/browse/ABC-$1|$0>', $text);

	// link support bugs/tickets
	if (preg_match('#(TICKET|BUG|SUPPORT)[\-| ](\d+)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.php?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

	if (preg_match('#\[HOTFIX\][ ]?\((\d+)\)#is', $text, $match)) {
		$replace = '<https://support.example.com/passthru.mh?item=' . $match[0] . '|' . $match[0] . '>';
		$text = str_replace($match[0], $replace, $text);
	}

We’re not done with that text yet, though. We have a loosely-followed naming convention for our commits and we can use that to link back to other systems that might have more information about the commit. Anything that was a Jira task should start with the task number in the format “[ABC-1234]” but sometimes it’s “(ABC-1234)” or “[ABC] (1234)” or “[ABC](1234)” so we account for all of those. Similarly, references to our ticket system sometimes use “TICKET” or “BUG” or “SUPPORT” and sometimes have a space or a dash and sometimes use “HOTFIX” and… You get the idea. There are probably better regular expressions to use here but these work. So we find references to our Jira cards and link back to them, then find references to our support system and link back to it, where there’s a script that will do some additional parsing to figure out where to go.

	$attachments[] = array(
		'text'      => $text,
		'fallback'  => $fallback,
		'color'     => '#1881d0',
		'mrkdwn_in' => array('text'),
	);

With all of that done, we build an array for this attachment, defining our text, our fallback text, a color to display alongside the attachment, and confirming that there is markdown in our text field.

}

// build message
$msg = array('text' => $header, 'attachments' => $attachments);

// send message
$c = curl_init();
curl_setopt($c, CURLOPT_URL, WEBHOOK_URL);
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($msg));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($c, CURLOPT_HEADER, 0);
curl_setopt($c, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_exec($c);
curl_close($c);

Once we’re done looping through our data from the git log and building our attachments, we put together the message and send it off via Curl. The message is just an array, which then gets JSON-encoded and posted to our webhook URL.

It’s fire-and-forget, so we don’t make any note if the webhook doesn’t respond or anything like that.

For a short time we had an additional message attachment that included diff data but we decided we didn’t want our code getting posted to Slack so we removed it.

As I said, there are tons of solutions for this out there, this is just one more.

Reinventing the Wheel: Cache-Busting via Automated File Versioning

I have a tendency to like to reinvent the wheel in my development, just to prove that I can.

I’ve gotten better about it over the years.  I replaced my custom-built blog engine with WordPress on DetroitHockey.Net, for example.  I still roll my own classes for interacting with the Trello and Twitter APIs, though.

Lately I’ve been looking to solve asset caching issues.  If I make a change to main.js, for example, how do I make sure my users get the updated file?  This is something that I’m sure has been solved, but I wanted to give it a shot myself.

We can’t force a user to know that a file has changed and re-download it.  We can tell them how long to cache a file for, and the browser may or may not respect that, but we can’t interrupt that with a new file.

What we can do is version the file.  That’s not a huge deal.  Drop a suffix onto the file name (main.js becomes main-v2.js).  The problem is I’m lazy.  I don’t want to have to rename the file and update all of the places it’s used.  I want that to happen automatically if I happened to change the file.

Conveniently, my build process already had a point where it acted only if a CSS or Javascript file had changed.

I use BitBucket for my source control and BitBucket Pipelines for publishing.  There’s a step in my publish plan that sends a curl request to a listener on my server.  The listener takes a look at each CSS and JS file and minifies it, but it only saves the newly-minified file if it’s different from what’s already on the server.

So I’ve got a point where I know if the file has changed, what do I do with it?

I added a configuration file writable by that listener.  It’s an array stored as JSON tracking a version number for each asset file.  I then updated the references to those files in my site templates to make sure to use the version number from the configuration file is used.

The last step was to make the listener actually write to the configuration file.  The listener, as I said, checks to see if the newly-minified version of main.js (for example) is different from the existing minified version of main.js.  If it is, the existing file is deleted, the configuration file is updated with a new version number based on the time, and the new file is saved with that number.

I don’t have to rename anything.  I don’t have to change any links.  I don’t have to ask anyone to hard refresh.  So far, it works out pretty well.

Griffins Jersey Contest: Sour Grapes Again

Several years ago I embraced the sour grapes I had about losing the Grand Rapids Griffins’ jersey design contest and wrote about it.  Since I’ve been ranting about it all day on Twitter, I figured it was time to do that again.

I’ve repeatedly said that I put more thought than the Griffins intend into my entries in their contests.  This year was no different, as I was intrigued by the idea of what exactly makes a 1980s hockey jersey design.  I won’t rehash that all here, I included it all in my post about my entry.

I researched, I designed, I wrote about all of that because I’m genuinely curious.  Also I wanted to win, but I never expected to because there are way better designers than me out there.

So I did all of this research and I designed and I wrote and I published.  Genuinely curious about that question, I found no one willing to engage in discussion.  No one responded to me here.  Discussion at Uni-Watch, the contest host, seemed to center around not knowing what the Griffins actually wanted, not what a fauxback should actually look like.  The Griffins themselves provided no direction.

My design made it through a round of voting and was named as one of twelve finalists.  I posted a review of the finalists and the biggest piece of feedback was that the best designs didn’t make it to the finals.  While true, it doesn’t answer the question.

Then this morning the contest winner was announced and it’s something so far from what I consider 80s hockey jersey design that I’m completely at a loss.

It’s a nicely-rendered jersey, for sure.  But I can point to five reasons I don’t think it’s a proper 80s fauxback.  It also hits my previously-noted nerve about logos that only describe what they’re for literally, as there is no griffin on that jersey.

And when the world’s foremost expert in sports identities says you got it wrong, it probably means something.

So we have what the Griffins were looking for from their 80s fauxback contest but it’s so far from what I would expect that I still feel the need for discussion.  I want to know what the designer took his inspiration from.  I want to know what about that design the Griffins were particularly struck by.  But the Griffins aren’t giving details and the designer didn’t write a blog post about it like I did.

There’s this question out there I’m really curious about and I feel like the response is deafening silence.  It’s driving me nuts.

Griffins Jersey Design Contest – 80s Edition

As I’ve noted in the past, I have a love-hate relationship with the Grand Rapids Griffins’ annual jersey design contest.  I have the tendency to put more thought than they probably intend into my own entries and, while I was a finalist last year, I don’t think I ever come close to winning.

This year I was going to skip out on the endeavor entirely until they added an impossible new wrinkle to the contest: Design a 1980s “fauxback” jersey.

The Griffins were founded in 1996, hence the “fauxback” requirement.  The idea is to come up with a look that represents a Griffins team that existed in that decade.  Which raises an interesting question: What makes an 80s hockey jersey?

The shoulder yokes of the Minnesota North Stars, New Jersey Devils, and Buffalo Sabres all jumped out at me, though all but New Jersey’s found their origins in the 70s.  Likewise the 70s birthed the over-the-shoulder stripes of the Winnipeg Jets, Toronto Maple Leafs, and a handful of All-Star teams.

I thought about modern teams that throwback to the 80s and how their identities have changed since then.  The Edmonton Oilers’ logo is virtually untouched but changed from royal blue and orange to navy blue and copper (then back to royal blue and orange, and now navy and orange).  The Calgary Flames were bright red and yellow in the 80s, darkening their red and adding black in the 90s.  The Los Angeles Kings went from purple and yellow to black and silver.  The Devils went from red and green to red and black.

What makes an 80s hockey jersey?  It’s not so much the striping pattern or the logo…  It’s the colors.  The bright colors of 1980s hockey design were virtually abandoned in the 1990s.  In addition to the previously-mentioned changes, the 90s saw the North Stars go from bright green and yellow to forest green and metallic gold.  Like the Oilers, the New York Islanders abandoned royal blue and orange (only to – also like the Oilers – eventually return to it).  The Hartford Whalers gave up green and royal blue for green, silver, and navy.

Which is a problem for this contest because the Griffins explicitly stated that entrants should use the team’s current colors, with the jersey base color being red or black.

By my count, in the 1980s across the National Hockey League, the American Hockey League, and the International Hockey League, only the Chicago Blackhawks (and some of their affiliates) used a red, black, and white combination.  The metallic silver the Griffins now use was unseen in the NHL until the 90s, though the Kings added grey in 1988.  Teams in the 80s used yellow, not the gold in Grand Rapids’ current identity.

As such, I don’t think it’s possible to have an “80s fauxback” that uses the Griffins’ current colors.  That didn’t stop me from trying, though.

My first sketch used the Winnipeg/Toronto-style over-the-shoulder striping and a Edmonton Oilers-like logo, with a griffin silhouette over the word “Griffins” in a circle.  I also dropped gold and silver from the color scheme to simplify things.  The logo felt forced, though, and the striping seemed too modern, so I scrapped that idea.

Next I ignored the logo and tried a striping pattern based on the Buffalo Sabres.  The shoulders featured a black yoke with white and gold outlines. The sleeves and waist had a black/white/gold/white/black stripe set.  I actually like this idea a bit but, again, there was nothing that made me think “80s” so I moved on.

Just to see if it led to anything, I cloned the North Stars’ 1988 jersey set, swapping green for red and yellow for gold.  This led me to believe that the gold just wasn’t going to work and I needed to go back to red, white, and black.

Starting with a red jersey, I added thick stripes in white and black – separated by a thin red stripe – to the sleeves and waist.  I then included a yoke in black with a white outline.  I realized I had something similar to the Devils’ 1982 home uniform and decided it was 80s enough to move forward.  I also decided that between the North Stars, the New York Rangers, various NHL All-Star teams, and some of the 1988 Olympic teams, a drop-shadowed number represented the 80s pretty well, too.

For the logo, the Moncton Hawks jumped out at me, with their bird head and abstract wings inside a circle.  I tried going in that direction with a stylized wing attached to a griffin head with an outstretched claw, all merged together with the team’s name as a wordmark.  It didn’t look like a griffin to me so I decided to go in a different direction.

Next I went simple.  A griffin silhouette in a circle with “Grand Rapids Griffins” around it.  Specifically, this was based off of an old Muskegon Mohawks jersey that I couldn’t figure out the exact year for.  The wing came from the Detroit Red Wings’ logo while the tail was from the Griffins’ original logo as a pair of homages.  It looked more 1960s than 1980s, though, so I went back to the abstract griffin idea.

I removed the circle and added in the bottom half of the griffin’s body. This gave me an opportunity to reuse the tail from the Griffins’ original logo, as with the silhouette logo. Then I switched up the griffin’s head to look more like an eagle and less like a dragon.  At this point, I had my crest, and I decided not to worry about shoulder logos because, by and large, they weren’t used in the 80s.

While I’ve submitted the red, black, and white design, I still don’t think it’s 80s enough.  The colors make it look 90s to me.

I tried simplifying the design to just red and white and, while I think it looks more 80s, I don’t think it looks as good.  In the end, the design has to look good enough to win.

But I come back to the idea that, by forcing the modern colors, the Griffins have unintentionally made any submission less 1980s.  I tried the team’s colors from before their rebrand two years ago – red, white, and blue – and feel that it’s a design that screams 80s.  I just can’t use it.

Wrong colors or not, as I said at the start, I don’t expect to win this thing anyway.  Based on last year, the voters seem swayed by submissions that look like they came out of a catalogue or a video game, not flat designs in the template I use.  I don’t get to submit all the thought I put into it. But it will be interesting to see what the voters think an 80s jersey is.


Update 8/21/2017, 8:50 PM: My design is up for vote today, which gets me thinking about my design more.  I now think I should have included a circle behind the logo, as I originally attempted.  It helps make it look more 80s than 90s.

Though it’s too late for the contest, I’ve updated the red jersey I submitted to include the circle, added it to the blue variant that I think the team should actually wear, and created white versions of each.


View All Images as Gallery

Rebranding FantasyHockeySim.com

I launched FantasyHockeySim.com as a spinoff of DetroitHockey.Net last summer and the visual elements of it were a rush job.  Getting the site out the door was my priority, so I stole design elements for FHS from DH.N and put together a logo that didn’t say “fantasy hockey” at all.

Awhile ago I ranted about the Detroit Red Wings’ “Hockeytown” logo and how the only way it said “Hockeytown” was literally, with the text splashed across it.  I’d done the same thing with the FantasyHockeySim.com logo, as crossed sticks said “hockey” but the only way it said “fantasy hockey” was via the FHS acronym across the front.  Even then, it looked more like the logo for a high school hockey team than for simulated fantasy hockey software.

While stuck on a development project, I decided to take a crack at a new FHS logo.

Because I like shield-based logos far too much, my first pass centered around different shields.  Eventually I put together one that I really liked the look of and started building alternate logos around it.

I showed the “final” set (the shield logo and “promotional” versions featuring additional text) around and realized I hadn’t solved the problem I was trying to handle in the first place.  The logos still only said “fantasy hockey” literally, and even then it was only the promotional ones.

I stepped back from it and didn’t think about it for awhile until an idea came to me during my drive into work a couple weeks later.

Representing hockey in a logo is easy.  Sticks, pucks, all sorts of imagery is available.  How do you represent “simulation” though?  Well, simulation means computers and code and, even to a layman, X/HTML’s angle brackets are recognizable as code.  So I wrapped a pair of crossed hockey sticks in angle brackets and went from there.

The first issue I hit with the logo was that the crossed sticks looked a bit like an X, so I changed their position and added lines representing tape to the blades.  After that, I decided to give up on my attempt at a monochrome logo, changing the color of the angle brackets to help separate them from the sticks (with the added effect of appearing as syntax coloring).

As I worked on a primary version of the logo, I also created an alternate version and a “promotional” version.  The promotional logo features the “bracket” logo inside a roundel containing the “Simulated Fantasy Hockey” descriptor and the site name while the alternate is simplified version of that, without the text.

The biggest issue with the alternate and promotional logos was making sure the broken inner circle of the roundel didn’t appear like it was supposed to be attached to the angle brackets.  To handle that, I shrank down the width of that inner circle and made the break in it wider.  Changing the color of the brackets completed the effect.

The FHS site still needs a redesign to get away from borrowing so much from DH.N but at least now the logo is original and descriptive.

On Journalism, Credit, and Perception

I am not a journalist.

At least that’s how I see it.

I spent a lot of time over the last year – DetroitHockey.Net‘s 20th season – thinking about what kind of site I wanted to run and what I wanted to write.  I decided that I don’t want to force myself to be unbiased.  I don’t want to sit in the pressbox, I want to sit in the stands.  I want to write about what I find fun.

That doesn’t mean I won’t hold myself to a certain standard.  I expect my readers to hold me to that, too.  It also doesn’t mean I don’t want to write serious pieces or topical pieces.  It just means I want to be more picky about what I write and when.

That said, today I was reminded that no matter how seriously I take myself, I can’t make those who would be my peers take me seriously.

Yesterday I published a post to DetroitHockey.Net that was the result of a not-inconsiderable amount of research and data tracking.  After weeks of compiling information about domain registrations, I thought I had discovered the name of the new Las Vegas NHL team, which isn’t set to be announced for another month.  Or at least a possible name.

There wasn’t much of a reception.  The Red Wings fanbase doesn’t really care about the Las Vegas team name and the Vegas fans don’t seem to really trust a Red Wings blog.  Disappointing, but not unexpected.

Until this morning when an article from the Las Vegas Review-Journal was published, featuring all of the same points as my post (though some of them were slightly off, as if paraphrased by someone without full technical understanding of the details).

It was clear that my work was the basis of that article.  The original post did not credit me.  Since then, the following line has been added:

DetroitHockey.net first reported the new domain name Thursday morning.

It’s credit, and it’s probably all I can ask for, but let’s take a look at how other outlets have picked up the news…

Yahoo:

On Thursday, the Las Vegas Review-Journal reported the name could be Desert Knights.

Fox Sports:

According to the Las Vegas Review-Journal…

CBS Sports:

According to the Review-Journal…

Somewhat hilariously, NBC Sports credited the Review-Journal with breaking the story, then proceeded to quote the part of the article that credits DH.N.

How is it that all of the major outlets came to name the LVRJ as the source even as that paper named DH.N?  Why did we not see Fox Sports credit the Review-Journal only for NBC Sports to credit Fox Sports?

Well, lets take a look at the first couple paragraphs from the Fox Sports piece.

The NHL’s Las Vegas expansion team may finally be leaning towards a decision on its name, if recent domain registrations are any indication. Those domains point to the Sin City hockey club being dubbed the Las Vegas Desert Knights.

According to the Las Vegas Review-Journal, Moniker Privacy Services — the same company that procured the NHL’s own website domain — has privately registered the rights to lasvegasdesertknights.com, vegasdesertknights.com and desertknightshockey.com. When asked about those registrations and the potential team name, team owner Bill Foley told the Las Vegas Review-Journal that he had no comment on the matter.

Fox Sports opens with a summary, immediately credits the LVRJ, and goes into exactly what information they got from that source.

The Review-Journal, however, opened as follows:

Bill Foley may get his wish on his hockey team being called the Knights. Just with a modifier.

Last week domain names were registered that might be an indicator that the NHL team scheduled to begin play in 2017 could be called the Las Vegas Desert Knights.

Last week the domains lasvegasdesertknights.com, vegasdesertknights.com and desertknightshockey.com were privately registered to Moniker Privacy Services, which is the same company that procured the domain name to NHL.com.

DetroitHockey.net first reported the new domain name Thursday morning.

In this case, a summary was provided, then all of the information, then a retroactive credit to the source.  Ignoring the fact that said credit wasn’t even in the original version of the LVRJ story, it’s not hard to see that Fox Sports put the source front and center while the Review-Journal slid it in as an aside.

The LVRJ writer, Justin Emerson, apologized via Twitter after being called out.  However, it’s clear that the paper got what they wanted out of this.  They made no effort to correct the people who were crediting them with the find.  Not their job to do so, I suppose.

So – after some prodding – the Las Vegas Review-Journal gave DetroitHockey.Net credit.  They also got all the credit.


From a technical perspective, I find it hilarious to see all of these sportswriters trying to paraphrase how domain registration works.  Near universally, Moniker has gone from the registrar for all of the related domains to the registrant.

Look for the technically accurate information and you’ll find the original source.

Thoughts on Hockey Sweaters

I think about hockey sweaters a lot.  I’ve collected them since I was a kid.  I doodle jersey concepts for fun.  I study their history.

Of the ones I owned growing up, my favorite was my Steve Yzerman Team Canada jersey from the 1998 Olympics.  White with the alternate captain’s A on it.  Pro-weight, with that thick Bauer mesh over the body. Red dazzle material over the sleeves and shoulders with a loose mesh for venting down the side.

I always loved the feel of that sweater.  The weight of the mesh.  The giant Team Canada crest.  Wearing it felt like wearing armor.

Though I’m a Detroit fan, no Red Wings jersey has felt like that.  The mesh of an authentic jersey always feels good but the Winged Wheel is too small, its proportions are off.  It’s a thing attached to the front of the sweater, not a part of it.

I think about the submissions I’ve done for the Grand Rapids Griffins’ jersey design contest and I realize how much those are influenced by that Team Canada jersey.  I created them to be like armor.  Heraldic imagery.  And a big damn shield right on the front.