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
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ The output files will be added to the same directory for simpler hosting of
all the things.

There are **two** important setup files:
- `config.yaml` (see `config.yaml.example`)
- `people.json` (see `people.json.example`)
- `config.yaml` (see `config.example.yaml`)
- `people.json` (see `people.example.json`)

Once those two files are configured to your contentment, see below.

Expand Down Expand Up @@ -74,18 +74,15 @@ There are several more options available.
-V, --version output the version number
-d, --directory <directory> The directory to process.
-m, --html If set, write the minutes to an index.html file
-w, --wordpress If set, publish the minutes to the blog
-e, --email If set, publish the minutes to the mailing list
-t, --twitter If set, publish the minutes to Twitter
-g, --google If set, publish the minutes to G+
-i, --index Build meeting index
-q, --quiet Don't print status information to the console

```

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).
Please note, there are custom environment variables which may need to be setup.
For examples of those, see the
[publish.example.sh](publish.example.sh).

## Web-based editor

Expand All @@ -105,10 +102,22 @@ 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
in the `scripts/` folder. To configure them, copy the `publishing.cfg.example`
in the `scripts/` folder. To configure them, copy the `publishing.example.cfg`
to `publishing.cfg`, make your changes, and then run the scripts (which wrap
the node code).

## Modifying IRC logs

If there are corrections to `irc.log` needed, do not make them directly, but
instead create a `changes.log` file and add "fake" IRC log entries for any
corrections/additions.

```irc
00:00:00 <bigbluehat> scribe+
```

These will be appended to the end of `irc.log` and processed alongside the rest.

## Development

During development, you'll want to test with the working copy version of
Expand Down
22 changes: 0 additions & 22 deletions config.yaml.example → config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,3 @@ email: # required for -e

----------------------------------------------------------------
{{{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}}"
230 changes: 18 additions & 212 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
#!/usr/bin/env node

var _ = require('underscore');
var async = require('async');
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.5.0')
.version('0.6.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')
Expand All @@ -46,8 +40,7 @@ if(!program.directory) {
process.exit(1);
}

if (!program.html && !program.wordpress && !program.email && !program.twitter
&& !program.google && !program.index) {
if (!program.html && !program.email && !program.index) {
console.error('Error: Nothing to do...');
program.outputHelp();
process.exit(1);
Expand All @@ -56,6 +49,7 @@ if (!program.html && !program.wordpress && !program.email && !program.twitter
// setup global variables
const dstDir = path.resolve(path.join(program.directory));
const logFile = path.resolve(dstDir, 'irc.log');
const changesLogFile = path.resolve(dstDir, 'changes.log');
const audioFile = path.resolve(dstDir, 'audio.ogg');
const indexFile = path.resolve(dstDir, 'index.html');
const minutesDir = path.join(dstDir, '/..');
Expand Down Expand Up @@ -89,84 +83,7 @@ if (!('minutes_base_url' in config)) {
// 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({
username: username,
password: password,
url: ''
});
// Re-format the HTML for publication to a Wordpress blog
var datetime = new Date(gDate);
datetime.setHours(37);
var wpSummary = content.post_content;
wpSummary = wpSummary.substring(
wpSummary.indexOf('<dl>'), wpSummary.indexOf('</dl>') + 5);
wpSummary = wpSummary.replace(/href=\"#/g,
'href="' + scrawl.minutes_base_url + gDate + '/#');
wpSummary = wpSummary.replace(/href=\"audio/g,
'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="' + scrawl.minutes_base_url + gDate +
'/">available in the archive</a>.</p>';

// calculate the proper post date
var gmtDate = datetime.toISOString();
gmtDate = gmtDate.replace('T', ' ');
gmtDate = gmtDate.replace(/\.[0-9]*Z/, '');

content.post_content = wpSummary;
content.post_date_gmt = gmtDate;
content.terms_names = ['Meetings'];
content.post_name = gDate + '-minutes';
content.custom_fields = [{
s2_meta_field: 'no'
}];

client.newPost(content, function(err, data) {
if(err) {
console.log(err);

console.log('scrawl: You may have to add this information manually:');

console.log('Title:\n' + content.post_title);
console.log('Content:\n' + content.post_content);
console.log('Slug:\n' + content.post_name);
}
else {
console.log(data);
// Do something.
}
callback();
});
}

function sendEmail(username, password, hostname, content, callback) {
var server = email.server.connect({
//user: username,
Expand Down Expand Up @@ -214,7 +131,15 @@ async.waterfall([ function(callback) {
});
}, function(callback) {
// read the IRC log file
fs.readFile(logFile, 'utf8', callback);
let log = fs.readFileSync(logFile, 'utf8');
// read the changes log file if it exists
try {
log += '\n';
log += fs.readFileSync(changesLogFile, 'utf8');
} catch(e) {
// ignore if the file doesn't exist
}
callback(null, log);
}, function(data, callback) {
gLogData = data;
// generate the index.html file
Expand Down Expand Up @@ -274,7 +199,12 @@ async.waterfall([ function(callback) {
topic: [],
resolution: []
};
summary.topic = data.match(/topic: (.*)/ig);
if(data.search(/agendum \d+\s+\-\- (.*) \-\-/i)) {
summary.topic = data.match(/agendum \d+\s+\-\- (.*) \-\-/i);
} else if(data.search(/(?<!sub)topic: (.*)/ig)) {
summary.topic = data.match(/(?<!sub)topic: (.*)/ig);
}
summary.topic = data.match(/(?<!sub)topic: (.*)/ig);
summary.resolution = data.match(/resolved: (.*)/ig);

// strip extraneous information
Expand Down Expand Up @@ -422,130 +352,6 @@ Full text of the discussion follows for archival purposes.
} else {
callback();
}
}, function(callback) {
// format the G+ post for copy-paste
if(program.google) {
if(!program.quiet) {
console.log('scrawl: Composing new G+ message.');
}

// generate the body of the email
var content = scrawl.generateMinutes(gLogData, 'text', gDate, haveAudio);
content = content.match(/Agenda(.|\n)*Organizer:/)[0].replace('Organizer:', '');
var items = content.match(/Topics(.|\n)*(Action|Resolutions|.*)/)[0].match(/[0-9]{1,2}\. (.*)/g);
var formattedItems = '';

// create a brief description of what was discussed
for(var i = 0; i < items.length; i++) {
if(i > 0 && i < items.length - 1) {
formattedItems += ', ';
}
else if(i == items.length - 1) {
formattedItems += ', and ';
}
formattedItems += items[i].replace(/[0-9]{1,2}\. /, '').toLowerCase();
}

// format in a way that is readable on G+
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);
callback();
} else {
callback();
}
}, function(callback) {
// publish the minutes to Twitter
if(program.twitter) {
if(!process.env.SCRAWL_TWITTER_CONSUMER_KEY ||
!process.env.SCRAWL_TWITTER_SECRET ||
!process.env.SCRAWL_TWITTER_TOKEN_KEY ||
!process.env.SCRAWL_TWITTER_TOKEN_SECRET) {
console.log('scrawl: You must set the following environment variables ' +
'for twitter\nposting to work: SCRAWL_TWITTER_CONSUMER_KEY, ' +
'SCRAWL_TWITTER_SECRET,\nSCRAWL_TWITTER_TOKEN_KEY, ' +
'SCRAWL_TWITTER_TOKEN_SECRET.');
return callback();
}
// create the twitter client
var twitter = new Twitter({
consumer_key: process.env.SCRAWL_TWITTER_CONSUMER_KEY,
consumer_secret: process.env.SCRAWL_TWITTER_SECRET,
access_token_key: process.env.SCRAWL_TWITTER_TOKEN_KEY,
access_token_secret: process.env.SCRAWL_TWITTER_TOKEN_SECRET
});

// get the tweet text
console.log('scrawl: Creating new tweet.');
var prompt = require('prompt');
prompt.start();
prompt.get({
properties: {
message: {
description: 'Enter the tweet contents (what was discussed)',
pattern: /^.{4,100}$/,
message: 'The message must be between 4-100 characters.'
}
}
}, function(err, results) {
// construct the tweet
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) {
console.log('scrawl: Tweet sent:', data.text);
callback();
});
});
} else {
callback();
}
}, function(callback) {
// publish the wordpress blog post
if(program.wordpress) {
if(!program.quiet) {
console.log('scrawl: Creating new blog post.');
}
var content = {
post_title: Mustache.render(WORDPRESS_TITLE, {gDate}),
post_content: scrawl.generateMinutes(gLogData, 'html', gDate, haveAudio)
};

if(process.env.SCRAWL_WP_USERNAME && process.env.SCRAWL_WP_PASSWORD) {
postToWordpress(
process.env.SCRAWL_WP_USERNAME, process.env.SCRAWL_WP_PASSWORD,
content, callback);
} else {
var prompt = require('prompt');
prompt.start();
prompt.get({
properties: {
username: {
description: 'Enter the WordPress username',
pattern: /^.{4,}$/,
message: 'The username must be at least 4 characters.',
'default': 'msporny'
},
password: {
description: 'Enter the user\'s password',
pattern: /^.{4,}$/,
message: 'The password must be at least 4 characters.',
hidden: true,
'default': 'password'
}
}
}, function(err, results) {
postToWordpress(results.username, results.password, content, callback);
});
}
} else {
callback();
}
}], function(err) {
// check to ensure there were no errors
if(err) {
Expand Down
Loading