Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
config.yaml
people.json
publish.sh
32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,11 @@ Scrawl works on directories structured like:
The output files will be added to the same directory for simpler hosting of
all the things.

Now, we need to setup the nicknames of all the people in the group.
So, copy `people.json.example` to `www/people.json` and fill in names,
homepages, and nicknames as needed.
There are **two** important setup files:
- `config.yaml` (see `config.yaml.example`)
- `people.json` (see `people.json.example`)

To edit logs in a Web page, it's recommended to install the `http-server` node
package and use that (for now):

```sh
$ npm i -g http-server
$ http-server www/
```

Once that's done, you can visit `http://localhost:8080/` to paste IRC logs, see
the output, and copy/paste the HTML (etc) wherever you need it.
Once those two files are configured to your contentment, see below.

## Command Line Usage

Expand Down Expand Up @@ -96,6 +87,21 @@ The WordPress, Google, and Twitter related switches also require some custom
environment variables to be setup. For examples of those, see the
[publish.sh.example](publish.sh.example).

## Web-based editor

You can edit logs in a small Web app. To do so, it's recommended to install the
`http-server` node package and use that (for now):

```sh
$ npm i -g http-server
$ http-server www/
```

Once that's done, you can visit `http://localhost:8080/` to paste IRC logs, see
the output, and copy/paste the HTML (etc) wherever you need it.

For this to work you'll need to put `people.json` in your `www/` folder.

## Wrapping bash scripts

If you're on a machine that has bash available, there are a couple useful tools
Expand Down
49 changes: 49 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
# required - or URLs are not produce-able
minutes_base_url: https://json-ld.github.io/minutes/

group: JSON-LD CG Telecon # default: Telecon
people: www/people.json # default: www/people.json
partials: www/_partials/ # default: www/_partials/

# The `content` and `minutes_base_url` Mustache variables MUST are pre-escaped
# so use {{{content}}} (3 curley braces) to properly unescape them for text.

email: # required for -e
from: gregg@greggkellogg.net # required!
to: JSON-LD CG <public-linked-json@w3.org> # required!
# Mustache template - vars: gDate
subject: "[MINUTES] W3C JSON-LD CG Call - {{gDate}} 12pm ET"
# Mustache template - vars: content, gDate, scribe, minutes_base_url
body: |-
Thanks to {{scribe}} for scribing this week! The minutes
for this week's JSON-LD CG telecon are now available:
{{{minutes_base_url}}}{{gDate}}/

Full text of the discussion follows for W3C archival purposes.
Audio from the meeting is available as well (link provided below).

----------------------------------------------------------------
{{{content}}}
gplus: # required for -g
# Mustache template - vars: content, formattedItems, gDate, minutes_base_url
body: |-
*JSON-LD CG Meeting Summary for {{gDate}}*

We discussed {{formattedItems}}.

{{{content}}}

Full transcript and audio logs are available here:

{{{minutes_base_url}}}{{gDate}}/

#w3c #json-ld
twitter: # required for -t
# Mustache template - vars: group, message, gDate, minutes_base_url
body: |-
JSON-LD CG discusses {{message}}:
{{{minutes_base_url}}}{{gDate}}/ #w3c #json-ld
wordpress: # required for -w
# Mustache template - vars: gDate
title: "JSON-LD CG Meeting Minutes for {{gDate}}"
148 changes: 118 additions & 30 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,114 @@ var email = require('emailjs');
var fs = require('fs');
var path = require('path');
var program = require('commander');
const Mustache = require('mustache');
var scrawl = require('./www/scrawl');
var Twitter = require('twitter');
var wp = require('wordpress');
const yaml = require('js-yaml');

program
.version('0.4.0')
// the setup switches
.option('-c, --config <file>', 'The YAML configuration file.')
.option('-d, --directory <directory>', 'The directory to process.')
// the do something switches
.option('-m, --html', 'If set, write the minutes to an index.html file')
.option('-w, --wordpress', 'If set, publish the minutes to the blog')
.option('-e, --email', 'If set, publish the minutes to the mailing list')
.option('-t, --twitter', 'If set, publish the minutes to Twitter')
.option('-g, --google', 'If set, publish the minutes to G+')
.option('-i, --index', 'Build meeting index')
// the tweak the cli switch
.option('-q, --quiet', 'Don\'t print status information to the console')
.parse(process.argv);

var base_dir = __dirname;
var config = {};
if (program.config) {
try {
config = yaml.safeLoad(fs.readFileSync(program.config, 'utf8'));
// paths in the config file are relative to the config files location
base_dir = path.resolve(path.dirname(program.config));
} catch (e) {
console.error(e.message);
}
}

if(!program.directory) {
console.error('Error: You must specify a directory to process');
program.outputHelp();
process.exit(1);
}

if (!program.html && !program.wordpress && !program.email && !program.twitter
&& !program.google && !program.index) {
console.error('Error: Nothing to do...');
program.outputHelp();
process.exit(1);
}

// setup global variables
const dstDir = path.resolve(path.join(program.directory));
const logFile = path.resolve(dstDir, 'irc.log');
const audioFile = path.resolve(dstDir, 'audio.ogg');
const indexFile = path.resolve(dstDir, 'index.html');
const minutesDir = path.join(dstDir, '/..');

const partialsDir = ('partials' in config)
? path.join(base_dir, config.partials)
: path.join(base_dir, 'www/_partials/');
const peoplePath = ('people' in config)
? path.join(base_dir, config.people)
: path.join(base_dir, 'www/people.json');

var htmlHeader = fs.readFileSync(
__dirname + '/www/_partials/header.html', {encoding: 'utf8'});
path.join(partialsDir, 'header.html'), {encoding: 'utf8'});
var htmlFooter = fs.readFileSync(
__dirname + '/www/_partials/footer.html', {encoding: 'utf8'});
var peopleJson = fs.readFileSync(
__dirname + '/www/people.json', {encoding: 'utf8'});
path.join(partialsDir, 'footer.html'), {encoding: 'utf8'});
var peopleJson = fs.readFileSync(peoplePath, {encoding: 'utf8'});
var gLogData = '';
var haveAudio = false;
var gDate = path.basename(dstDir);
gDate = gDate.match(/([0-9]{4}-[0-9]{2}-[0-9]{2})/)[1];

// configure scrawl
scrawl.group = 'JSON-LD CG Telecon';
scrawl.group = config.group || 'Telecon';
scrawl.people = JSON.parse(peopleJson);

// we can't make any URLs without this...so fail...
if (!('minutes_base_url' in config)) {
console.error('Error: The `minutes_base_url` was not set in the config');
process.exit(1);
}
// Location of date-based minutes folders; MUST end in a forward slash
scrawl.minutes_base_url = config.minutes_base_url;

// Mustache template - vars: gDate, formattedItems, content, minutes_base_url
const GPLUS_BODY = ('gplus' in config && 'body' in config.gplus)
? config.gplus.body
: `*Meeting Summary for {{gDate}}*

We discussed {{formattedItems}}.

{{{content}}}

Full transcript and audio logs are available here:

{{{minutes_base_url}}}{{gDate}}/
`;

// Mustache template - vars: group, message, gDate, minutes_base_url
const TWITTER_BODY = ('twitter' in config && 'body' in config.twitter)
? config.twitter.body
: `{{group}} discusses {{message}}:
{{{minutes_base_url}}}{{gDate}}/`;

// Mustache template - vars: gDate
const WORDPRESS_TITLE = ('wordpress' in config && 'title' in config.wordpress)
? config.wordpress.title
: 'Meeting Minutes for {{gDate}}';

/************************* Utility Functions *********************************/
function postToWordpress(username, password, content, callback) {
var client = wp.createClient({
Expand All @@ -64,12 +128,12 @@ function postToWordpress(username, password, content, callback) {
wpSummary = wpSummary.substring(
wpSummary.indexOf('<dl>'), wpSummary.indexOf('</dl>') + 5);
wpSummary = wpSummary.replace(/href=\"#/g,
'href="https://json-ld.github.io/minutes/' + gDate + '/#');
'href="' + scrawl.minutes_base_url + gDate + '/#');
wpSummary = wpSummary.replace(/href=\"audio/g,
'href="https://json-ld.github.io/minutes/' + gDate + '/audio');
'href="' + scrawl.minutes_base_url + gDate + '/audio');
wpSummary = wpSummary.replace(/<div><audio[\s\S]*\/audio><\/div>/g, '');
wpSummary += '<p>Detailed minutes and recorded audio for this call are ' +
'<a href="https://json-ld.github.io/minutes/' + gDate +
'<a href="' + scrawl.minutes_base_url + gDate +
'/">available in the archive</a>.</p>';

// calculate the proper post date
Expand Down Expand Up @@ -114,18 +178,18 @@ function sendEmail(username, password, hostname, content, callback) {
// send the message
server.send({
text: content,
from: 'gregg@greggkellogg.net',
from: EMAIL_FROM,
//from: username + '@' + hostname,
to: 'JSON-LD CG <public-linked-json@w3.org>',
subject: '[MINUTES] W3C JSON-LD CG Call - ' + gDate + ' 12pm ET'
to: EMAIL_TO,
subject: Mustache.render(EMAIL_SUBJECT, {gDate})
}, function(err, message) {
if(err) {
console.log('scrawl:', err);
return callback();
}

if(!program.quiet) {
console.log('scrawl: Sent minutes email to public-linked-json@w3.org');
console.log(`scrawl: Sent minutes email to ${EMAIL_TO}`);
}
callback();
});
Expand Down Expand Up @@ -236,7 +300,7 @@ async.waterfall([ function(callback) {
}

const summaryIntro = fs.readFileSync(
__dirname + '/www/_partials/summary-intro.html', {encoding: 'utf8'});
path.join(base_dir, 'www/_partials/summary-intro.html'), {encoding: 'utf8'});

// write out summary file
var summaryHtml = htmlHeader + '<div id="info">' + summaryIntro;
Expand Down Expand Up @@ -282,17 +346,43 @@ async.waterfall([ function(callback) {
console.log('scrawl: Sending new minutes email.');
}

if (!('email' in config)) {
callback('Error: Email configuration is missing');
return;
} else if (!('from' in config.email) || !('to' in config.email)) {
callback('Error: You must supply a `to` and `from` config value');
return;
}

// see sendEmail()
// TODO: don't use global constants...
const EMAIL_TO = config.email.to;
const EMAIL_FROM = config.email.from;

// Mustache template -- vars: gDate
// TODO: dates are always Eastern Time...maybe the world is round?
// TODO: also the time is still hard coded T_T
const EMAIL_SUBJECT = config.email.subject || '[MINUTES] {{gDate}} 12pm ET';
// Mustache template -- vars: scribe, gDate, content, minutes_base_url, haveAudio
const EMAIL_BODY = config.email.body || `Thanks to {{scribe}} for scribing this week! The minutes
for this week's telecon are now available:

{{{minutes_base_url}}}{{gDate}}/

Full text of the discussion follows for archival purposes.
{{#haveAudio}}Audio from the meeting is available as well (link provided below).{{/haveAudio}}

----------------------------------------------------------------
{{{content}}}`;

// generate the body of the email
var content = scrawl.generateMinutes(gLogData, 'text', gDate, haveAudio);
var scribe = content.match(/Scribe:\n\s(.*)\n/g)[0]
.replace(/\n/g, '').replace('Scribe: ', '');
content = 'Thanks to ' + scribe + ' for scribing this week! The minutes\n' +
'for this week\'s JSON-LD CG telecon are now available:\n\n' +
'https://json-ld.github.io/minutes/'+ gDate + '/\n\n' +
'Full text of the discussion follows for W3C archival purposes.\n' +
'Audio from the meeting is available as well (link provided below).\n\n' +
'----------------------------------------------------------------\n' +
content;
content = Mustache.render(EMAIL_BODY,
{scribe, gDate, content,
minutes_base_url: scrawl.minutes_base_url,
haveAudio});

if(process.env.SCRAWL_EMAIL_USERNAME && process.env.SCRAWL_EMAIL_PASSWORD &&
process.env.SCRAWL_EMAIL_SERVER) {
Expand Down Expand Up @@ -357,11 +447,8 @@ async.waterfall([ function(callback) {
}

// format in a way that is readable on G+
content = '*JSON-LD CG Meeting Summary for ' + gDate + '*\n\n' +
'We discussed ' + formattedItems + '.\n\n' +
content + '\nFull transcript and audio logs are available here:\n\n' +
'https://json-ld.github.io/minutes/' + gDate + '/\n\n' +
'#w3c #json-ld';
content = Mustache.render(GPLUS_BODY, {gDate, formattedItems, content,
minutes_base_url: scrawl.minutes_base_url});

console.log('scrawl: You will need to paste this to your G+ stream:\n');
console.log(content);
Expand Down Expand Up @@ -404,9 +491,10 @@ async.waterfall([ function(callback) {
}
}, function(err, results) {
// construct the tweet
var tweet = 'JSON-LD CG discusses ' +
results.message + ': https://json-ld.github.io/minutes/' +
gDate + '/ #w3c #json-ld';
var tweet = Mustache.render(TWITTER_BODY,
{group: scrawl.group,
message: results.message, gDate,
minutes_base_url: scrawl.minutes_base_url});

// send the tweet
twitter.updateStatus(tweet, function(data) {
Expand All @@ -424,7 +512,7 @@ async.waterfall([ function(callback) {
console.log('scrawl: Creating new blog post.');
}
var content = {
post_title: 'JSON-LD CG Meeting Minutes for ' + gDate,
post_title: Mustache.render(WORDPRESS_TITLE, {gDate}),
post_content: scrawl.generateMinutes(gLogData, 'html', gDate, haveAudio)
};

Expand All @@ -438,7 +526,7 @@ async.waterfall([ function(callback) {
prompt.get({
properties: {
username: {
description: 'Enter the JSON-LD WordPress username',
description: 'Enter the WordPress username',
pattern: /^.{4,}$/,
message: 'The username must be at least 4 characters.',
'default': 'msporny'
Expand Down
Loading