From 554bad00e5ea19c8b55de15003a4ab9e0b6753a7 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 09:02:18 -0400 Subject: [PATCH 01/12] Modernize variable declarations. --- www/scrawl.js | 196 ++++++++++++++++++++++++-------------------------- 1 file changed, 95 insertions(+), 101 deletions(-) diff --git a/www/scrawl.js b/www/scrawl.js index 08c306e..3cbf9b1 100644 --- a/www/scrawl.js +++ b/www/scrawl.js @@ -5,32 +5,32 @@ */ (function() { /* Standard regular expressions to use when matching lines */ - var commentRx = /^\[?(\S*|\w+ \S+)\]\s+<([^>]*)>\s+(.*)$/; - var scribeRx = /^(scribe|scribenick):.*$/i; - var meetingRx = /^meeting:\s(.*)$/i; - var totalPresentRx = /^total present:\s(.*)$/i; - var dateRx = /^date:\s(.*)$/i; - var chairRx = /^chair:.*$/i; - var audioRx = /^audio:\s?(.*)$/i; - var proposalRx = /^(proposal|proposed):.*$/i; - var presentRx = /^present[:+](.*)$/i; - var resolutionRx = /^(resolution|resolved): ?(.*)$/i; - var useCaseRx = /^(use case|usecase):\s?(.*)$/i; - var topicRx = /^topic:\s*(.*)$/i; - var actionRx = /^action:\s*(.*)$/i; - var voipRx = /^voip.*$/i; - var toVoipRx = /^voip.{0,4}:.*$/i; - var rrsAgentRx = /^RRSAgent.*$/i; - var queueRx = /^q[+-?]\s.*|^q[+-?].*|^ack\s+.*|^ack$/i; - var voteRx = /^[+-][01]\s.*|[+-][01]$/i; - var agendaRx = /^agenda:\s*((https?):.*)$/i; - var urlRx = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/; + const commentRx = /^\[?(\S*|\w+ \S+)\]\s+<([^>]*)>\s+(.*)$/; + const scribeRx = /^(scribe|scribenick):.*$/i; + const meetingRx = /^meeting:\s(.*)$/i; + const totalPresentRx = /^total present:\s(.*)$/i; + const dateRx = /^date:\s(.*)$/i; + const chairRx = /^chair:.*$/i; + const audioRx = /^audio:\s?(.*)$/i; + const proposalRx = /^(proposal|proposed):.*$/i; + const presentRx = /^present[:+](.*)$/i; + const resolutionRx = /^(resolution|resolved): ?(.*)$/i; + const useCaseRx = /^(use case|usecase):\s?(.*)$/i; + const topicRx = /^topic:\s*(.*)$/i; + const actionRx = /^action:\s*(.*)$/i; + const voipRx = /^voip.*$/i; + const toVoipRx = /^voip.{0,4}:.*$/i; + const rrsAgentRx = /^RRSAgent.*$/i; + const queueRx = /^q[+-?]\s.*|^q[+-?].*|^ack\s+.*|^ack$/i; + const voteRx = /^[+-][01]\s.*|[+-][01]$/i; + const agendaRx = /^agenda:\s*((https?):.*)$/i; + const urlRx = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/; // Compatability code to make this work in both node.js and the browser - var scrawl = {}; - var nodejs = false; + const scrawl = {}; + let nodejs = false; if(typeof module !== 'undefined' && typeof module.exports !== 'undefined') { - var Entities = require('html-entities').XmlEntities; + const Entities = require('html-entities').XmlEntities; var entities = new Entities(); module.exports = scrawl; nodejs = true; @@ -57,7 +57,7 @@ if (!str) { return str; } - var regex = '.{1,' + width + '}(\\s|$)' + + const regex = '.{1,' + width + '}(\\s|$)' + (cut ? '|.{' +width+ '}|.+$' : '|\\S+?(\\s|$)'); return str.match(new RegExp(regex, 'g')).join(brk); @@ -65,11 +65,11 @@ scrawl.generateAliases = function() { - var rval = {}; + const rval = {}; - for(var p in scrawl.people) + for(const p in scrawl.people) { - var person = scrawl.people[p]; + const person = scrawl.people[p]; var names = p.split(' '); // append any aliases to the list of known names @@ -79,10 +79,9 @@ } // Add the aliases and names if they don't already exist in the aliases - for(var n in names) + for(const n in names) { - var alias = names[n]; - alias = alias.toLowerCase(); + const alias = names[n].toLowerCase(); if(alias.length > 2 && !(alias in rval)) { rval[alias] = p; @@ -95,7 +94,7 @@ scrawl.htmlencode = function(text) { - var modified = ''; + let modified; if(nodejs) { modified = entities.encodeNonUTF(text); @@ -111,7 +110,7 @@ scrawl.topic = function(msg, id, textMode) { - var rval = ''; + let rval = ''; if(textMode === 'html') { @@ -128,7 +127,7 @@ scrawl.action = function(msg, id, textMode) { - var rval = ''; + let rval = ''; if(textMode === 'html') { @@ -145,7 +144,7 @@ scrawl.information = function(msg, textMode) { - var rval = ''; + let rval = ''; if(textMode === 'html') { @@ -162,7 +161,7 @@ scrawl.proposal = function(msg, textMode) { - var rval = ''; + let rval = ''; if(textMode === 'html') { @@ -180,7 +179,7 @@ scrawl.resolution = function(msg, id, textMode) { - var rval = ''; + let rval = ''; if(textMode === 'html') { @@ -199,7 +198,7 @@ scrawl.usecase = function(msg, textMode) { - var rval = ''; + let rval = ''; if(textMode === 'html') { @@ -218,7 +217,7 @@ scrawl.scribe = function(msg, textMode, person, assist) { - var rval = ''; + let rval = ''; // capitalize the first letter of the message if it doesn't start with http if(!(/^(\s)*https?:\/\//.test(msg))) { @@ -273,7 +272,7 @@ scrawl.scribeContinuation = function(msg, textMode) { - var rval = ''; + let rval = ''; if(textMode === 'html') { @@ -298,7 +297,7 @@ scrawl.error = function(msg, textMode) { - var rval = ''; + let rval = ''; if(textMode === 'html') { @@ -323,19 +322,18 @@ scrawl.processLine = function(context, aliases, line, textMode) { - var rval = ''; - var match = commentRx.exec(line); + let rval = ''; + const match = commentRx.exec(line); if(match) { - var time = match[1]; - var nick = match[2].toLowerCase(); - var msg = match[3]; + const nick = match[2].toLowerCase(); + const msg = match[3]; // check for a scribe line if(msg.search(scribeRx) !== -1) { - var scribe = msg.split(':')[1].replace(' ', ''); + const scribe = msg.split(':')[1].replace(' ', ''); scribe = scribe.toLowerCase(); if(scribe in aliases) { @@ -356,8 +354,8 @@ var chairs = msg.split(':')[1].split(','); context.chair = []; - for(var i = 0; i < chairs.length; i++) { - var chair = chairs[i].replace(' ', '').toLowerCase(); + for(let i = 0; i < chairs.length; i++) { + const chair = chairs[i].replace(' ', '').toLowerCase(); if(chair in aliases) { context.chair.push(aliases[chair]); @@ -368,23 +366,23 @@ // check for meeting line else if(msg.search(meetingRx) !== -1) { - var meeting = msg.match(meetingRx)[1]; + const meeting = msg.match(meetingRx)[1]; context.group = meeting; } // check for present line else if(msg.search(presentRx) !== -1) { - var present = msg.match(presentRx)[1].toLowerCase(); - var people = present.split(','); + const present = msg.match(presentRx)[1].toLowerCase(); + const people = present.split(','); // try to find the person by full name, last name, and then first name - for(var i = 0; i < people.length; i++) { + for(let i = 0; i < people.length; i++) { if (!people[i]) { scrawl.present(context, aliases[nick]); } else { - var person = people[i].replace(/^\s/, '').replace(/\s$/, ''); - var lastName = person.split(' ')[1]; - var firstName = person.split(' ')[0]; + const person = people[i].replace(/^\s/, '').replace(/\s$/, ''); + const lastName = person.split(' ')[1]; + const firstName = person.split(' ')[0]; if(person in aliases) { scrawl.present(context, aliases[person]); } else if(lastName in aliases) { @@ -403,39 +401,39 @@ // check for date line else if(msg.search(dateRx) !== -1) { - var date = msg.match(dateRx)[1]; + const date = msg.match(dateRx)[1]; context.date = new Date(date); } // check for topic line else if(msg.search(topicRx) !== -1) { - var topic = msg.match(topicRx)[1]; + const topic = msg.match(topicRx)[1]; context.topics = context.topics.concat(topic); rval = scrawl.topic(topic, context.topics.length, textMode); } // check for action line else if(msg.search(actionRx) !== -1) { - var action = msg.match(actionRx)[1]; + const action = msg.match(actionRx)[1]; context.actions = context.actions.concat(action); rval = scrawl.action(action, context.actions.length, textMode); } // check for Agenda line else if(msg.search(agendaRx) !== -1) { - var agenda = msg.match(agendaRx)[1]; + const agenda = msg.match(agendaRx)[1]; context.agenda = agenda; } // check for proposal line else if(msg.search(proposalRx) !== -1) { - var proposal = msg.split(':')[1]; + const proposal = msg.split(':')[1]; rval = scrawl.proposal(proposal, textMode); } // check for resolution line else if(msg.search(resolutionRx) !== -1) { - var resolution = msg.match(resolutionRx)[2]; + const resolution = msg.match(resolutionRx)[2]; context.resolutions = context.resolutions.concat(resolution); rval = scrawl.resolution( resolution, context.resolutions.length, textMode); @@ -443,7 +441,7 @@ // check for use case line else if(msg.search(useCaseRx) !== -1) { - var usecase = msg.match(useCaseRx)[2]; + const usecase = msg.match(useCaseRx)[2]; rval = scrawl.usecase(usecase, textMode); } else if(msg.search(totalPresentRx) !== -1) @@ -478,13 +476,13 @@ } else if(msg.indexOf(':') !== -1) { - var alias = msg.split(':', 1)[0].replace(' ', '').toLowerCase(); + const alias = msg.split(':', 1)[0].replace(' ', '').toLowerCase(); if(alias in aliases) { // the line is a comment made by somebody else that was // scribed - var cleanedMessage = msg.split(':').splice(1).join(':'); + const cleanedMessage = msg.split(':').splice(1).join(':'); scrawl.present(context, aliases[alias]); rval = scrawl.scribe( @@ -508,12 +506,12 @@ { if(msg.indexOf(':') !== -1) { - var alias = msg.split(':', 1)[0].replace(' ', '').toLowerCase(); + const alias = msg.split(':', 1)[0].replace(' ', '').toLowerCase(); if(alias in aliases) { // the line is a scribe assist - var cleanedMessage = msg.split(':').splice(1).join(':'); + const cleanedMessage = msg.split(':').splice(1).join(':'); scrawl.present(context, aliases[alias]); rval = scrawl.scribe(cleanedMessage, textMode, @@ -558,25 +556,25 @@ scrawl.generateSummary = function(context, textMode) { - var rval = ''; - var time = context.date || new Date(); - var month = '' + (time.getMonth() + 1) - var day = '' + time.getDate() - var group = context.group; - var agenda = context.agenda; - var audio = 'audio.ogg'; - var chair = context.chair; - var scribe = context.scribe.filter(function(item, i, arr) { + let rval = ''; + let time = context.date || new Date(); + let month = '' + (time.getMonth() + 1) + let day = '' + time.getDate() + const group = context.group; + const agenda = context.agenda; + const audio = 'audio.ogg'; + const chair = context.chair; + const scribe = context.scribe.filter(function(item, i, arr) { return arr.indexOf(item) === i; }); - var topics = context.topics; - var resolutions = context.resolutions; - var actions = context.actions; - var present = []; + const topics = context.topics; + const resolutions = context.resolutions; + const actions = context.actions; + const present = []; // build the list of people present - for(var name in context.present) { - var person = scrawl.people[name] + for(const name in context.present) { + const person = scrawl.people[name] person['name'] = name; present.push(person) } @@ -613,7 +611,7 @@ rval += '
Topics
    '; for(i in topics) { - var topicNumber = parseInt(i) + 1; + const topicNumber = parseInt(i) + 1; rval += '
  1. ' + topics[i] + ''; } @@ -625,7 +623,7 @@ rval += '
    Resolutions
      '; for(i in resolutions) { - var resolutionNumber = parseInt(i) + 1; + const resolutionNumber = parseInt(i) + 1; rval += '
    1. ' + resolutions[i] + ''; } @@ -637,7 +635,7 @@ rval += '
      Action Items
        '; for(i in actions) { - var actionNumber = parseInt(i) + 1; + const actionNumber = parseInt(i) + 1; rval += '
      1. ' + actions[i] + ''; } @@ -645,9 +643,9 @@ } // generate the list of people present - var peoplePresent = '' - for(var i = 0; i < present.length; i++) { - var person = present[i]; + let peoplePresent = '' + for(let i = 0; i < present.length; i++) { + const person = present[i]; if(i > 0) { peoplePresent += ', '; @@ -682,8 +680,8 @@ else { // generate the list of people present - var peoplePresent = '' - for(var i = 0; i < present.length; i++) { + let peoplePresent = '' + for(let i = 0; i < present.length; i++) { var person = present[i]; if(i > 0) { @@ -706,7 +704,7 @@ rval += 'Topics:\n'; for(i in topics) { - var topicNumber = parseInt(i) + 1; + const topicNumber = parseInt(i) + 1; rval += scrawl.wordwrap( ' ' + topicNumber + '. ' + topics[i], 65, '\n ') + '\n'; @@ -718,7 +716,7 @@ rval += 'Resolutions:\n'; for(i in resolutions) { - var resolutionNumber = parseInt(i) + 1; + const resolutionNumber = parseInt(i) + 1; rval += scrawl.wordwrap( ' ' + resolutionNumber + '. ' + resolutions[i], 65, '\n ') + '\n'; @@ -730,7 +728,7 @@ rval += 'Action Items:\n'; for(i in actions) { - var actionNumber = parseInt(i) + 1; + const actionNumber = parseInt(i) + 1; rval += scrawl.wordwrap( ' ' + actionNumber + '. ' + actions[i], 65, '\n ') + '\n'; @@ -755,11 +753,9 @@ scrawl.generateMinutes = function(ircLog, textMode, date, haveAudio) { - var rval = ''; - var minutes = ''; - var summary = ''; - var ircLines = ircLog.split(/\r?\n/); - var aliases = scrawl.generateAliases(); + let minutes = ''; + const ircLines = ircLog.split(/\r?\n/); + const aliases = scrawl.generateAliases(); scrawl.counter = 0; // TODO: expose this better? @@ -794,14 +790,12 @@ } // generate the meeting summary - summary = scrawl.generateSummary(context, textMode); + const summary = scrawl.generateSummary(context, textMode); // fix spacing around proposals, actions, and resolutions minutes = minutes.replace(/\n\n\n/gm, '\n\n'); // create the final log output - rval = summary + minutes; - - return rval; + return summary + minutes; } })(); From b8ab35d36ed6f2529070c6fc9989bbe2722dc71f Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 10:53:49 -0400 Subject: [PATCH 02/12] Remove unused underscore dependency. --- index.js | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/index.js b/index.js index 10972fe..e0b1417 100755 --- a/index.js +++ b/index.js @@ -1,6 +1,5 @@ #!/usr/bin/env node -var _ = require('underscore'); var async = require('async'); var email = require('emailjs'); var fs = require('fs'); diff --git a/package.json b/package.json index 6c576e1..af0f96c 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "mustache": "^2.3.0", "prompt": "^1.0.0", "twitter": "~0.2.5", - "underscore": "~1.5.0", "wordpress": "~1.4.1" } } From 6ab035ca04b02b0f8d0e993dc3860988f64b0315 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 10:58:28 -0400 Subject: [PATCH 03/12] Remove WordPress publishing. Unused by other forks, so no need to keep it. --- README.md | 3 +- config.yaml.example | 3 -- index.js | 102 +------------------------------------------- package-lock.json | 43 +------------------ package.json | 3 +- 5 files changed, 4 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index c0d56cf..e503e93 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,6 @@ There are several more options available. -V, --version output the version number -d, --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+ @@ -83,7 +82,7 @@ There are several more options available. ``` -The WordPress, Google, and Twitter related switches also require some custom +The 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). diff --git a/config.yaml.example b/config.yaml.example index e4ce01e..7543ae7 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -44,6 +44,3 @@ twitter: # required for -t 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}}" diff --git a/index.js b/index.js index e0b1417..f64b46b 100755 --- a/index.js +++ b/index.js @@ -8,7 +8,6 @@ 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 @@ -18,7 +17,6 @@ program .option('-d, --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+') @@ -45,7 +43,7 @@ if(!program.directory) { process.exit(1); } -if (!program.html && !program.wordpress && !program.email && !program.twitter +if (!program.html && !program.email && !program.twitter && !program.google && !program.index) { console.error('Error: Nothing to do...'); program.outputHelp(); @@ -108,64 +106,7 @@ const TWITTER_BODY = ('twitter' in config && 'body' in config.twitter) : `{{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('
        '), wpSummary.indexOf('
        ') + 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>/g, ''); - wpSummary += '

        Detailed minutes and recorded audio for this call are ' + - 'available in the archive.

        '; - - // 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, @@ -504,47 +445,6 @@ Full text of the discussion follows for archival purposes. } 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) { diff --git a/package-lock.json b/package-lock.json index 5c89d89..eda8ab5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,9 +19,7 @@ "moment": "~2.19.3", "mustache": "^2.3.0", "prompt": "^1.0.0", - "twitter": "~0.2.5", - "underscore": "~1.5.0", - "wordpress": "~1.4.1" + "twitter": "~0.2.5" }, "bin": { "scrawl": "index.js" @@ -521,11 +519,6 @@ "oauth": ">=0.8.4" } }, - "node_modules/underscore": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.5.2.tgz", - "integrity": "sha512-yejOFsRnTJs0N9CK5Apzf6maDO2djxGoLLrlZlvGs2o9ZQuhIhDL18rtFyy4FBIbOkzA6+4hDgXbgz5EvDQCXQ==" - }, "node_modules/utile": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", @@ -585,40 +578,6 @@ "node": ">= 0.4.0" } }, - "node_modules/wordpress": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/wordpress/-/wordpress-1.4.1.tgz", - "integrity": "sha512-U2zADxCSyyYcpgc5i7ipiDzNx6/e0zq2ldWyqTqr8n88Nj+iHd5JT/WavZkIQ+x0b9QlBv9lHoXyrqxdbckIrw==", - "dependencies": { - "xmlrpc": "1.3.2" - } - }, - "node_modules/wordpress/node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/wordpress/node_modules/xmlbuilder": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", - "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/wordpress/node_modules/xmlrpc": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz", - "integrity": "sha1-JrLqNHhI0Ciqx+dRS1NRl23j6D0=", - "dependencies": { - "sax": "1.2.x", - "xmlbuilder": "8.2.x" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.0.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index af0f96c..88f243c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "moment": "~2.19.3", "mustache": "^2.3.0", "prompt": "^1.0.0", - "twitter": "~0.2.5", - "wordpress": "~1.4.1" + "twitter": "~0.2.5" } } From 59956c92a332889dfe129485d3d7569f4df9b282 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 11:19:45 -0400 Subject: [PATCH 04/12] Remove antique Twitter integration. Unused and likely no longer works with X. --- README.md | 3 +-- config.yaml.example | 5 ---- index.js | 54 +----------------------------------------- package-lock.json | 49 +------------------------------------- package.json | 3 +-- publishing.cfg.example | 4 ---- 6 files changed, 4 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index e503e93..f98ebc5 100644 --- a/README.md +++ b/README.md @@ -75,14 +75,13 @@ There are several more options available. -d, --directory The directory to process. -m, --html If set, write the minutes to an index.html file -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 Google and Twitter related switches also require some custom +The Google related switch also requires some custom environment variables to be setup. For examples of those, see the [publish.sh.example](publish.sh.example). diff --git a/config.yaml.example b/config.yaml.example index 7543ae7..a88f44b 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -39,8 +39,3 @@ gplus: # required for -g {{{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 diff --git a/index.js b/index.js index f64b46b..d796fb1 100755 --- a/index.js +++ b/index.js @@ -7,7 +7,6 @@ var path = require('path'); var program = require('commander'); const Mustache = require('mustache'); var scrawl = require('./www/scrawl'); -var Twitter = require('twitter'); const yaml = require('js-yaml'); program @@ -18,7 +17,6 @@ program // the do something switches .option('-m, --html', 'If set, write the minutes to an index.html file') .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 @@ -43,8 +41,7 @@ if(!program.directory) { process.exit(1); } -if (!program.html && !program.email && !program.twitter - && !program.google && !program.index) { +if (!program.html && !program.email && !program.google && !program.index) { console.error('Error: Nothing to do...'); program.outputHelp(); process.exit(1); @@ -396,55 +393,6 @@ Full text of the discussion follows for archival purposes. } 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(err) { // check to ensure there were no errors if(err) { diff --git a/package-lock.json b/package-lock.json index eda8ab5..e2624a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,7 @@ "mime": "^2.3.1", "moment": "~2.19.3", "mustache": "^2.3.0", - "prompt": "^1.0.0", - "twitter": "~0.2.5" + "prompt": "^1.0.0" }, "bin": { "scrawl": "index.js" @@ -82,18 +81,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "node_modules/cookies": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.1.tgz", - "integrity": "sha1-fIphX1SBxhq58WyDNzG8uPZjuZs=", - "dependencies": { - "depd": "~1.1.1", - "keygrip": "~1.0.2" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cycle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", @@ -107,14 +94,6 @@ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=" }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/emailjs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emailjs/-/emailjs-2.1.0.tgz", @@ -273,14 +252,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/keygrip": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.2.tgz", - "integrity": "sha1-rTKXxVcGneqLz+ek+kkbdcXd65E=", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/keypress": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", @@ -396,11 +367,6 @@ "ncp": "bin/ncp" } }, - "node_modules/oauth": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", - "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -506,19 +472,6 @@ "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", "deprecated": "no longer maintained" }, - "node_modules/twitter": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/twitter/-/twitter-0.2.13.tgz", - "integrity": "sha1-KcwBmV9aQV99X3guLGo2uCAMs4w=", - "engines": [ - "node >=0.2.0" - ], - "dependencies": { - "cookies": ">=0.1.6", - "keygrip": ">=0.1.7", - "oauth": ">=0.8.4" - } - }, "node_modules/utile": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", diff --git a/package.json b/package.json index 88f243c..4371052 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "mime": "^2.3.1", "moment": "~2.19.3", "mustache": "^2.3.0", - "prompt": "^1.0.0", - "twitter": "~0.2.5" + "prompt": "^1.0.0" } } diff --git a/publishing.cfg.example b/publishing.cfg.example index 5f71568..bb91d61 100644 --- a/publishing.cfg.example +++ b/publishing.cfg.example @@ -12,9 +12,5 @@ export SCRAWL_EMAIL_SERVER= # Optional port and SSL. Default SSL is false # export SCRAWL_EMAIL_PORT= # export SCRAWL_EMAIL_SSL= -export SCRAWL_TWITTER_CONSUMER_KEY= -export SCRAWL_TWITTER_SECRET= -export SCRAWL_TWITTER_TOKEN_KEY= -export SCRAWL_TWITTER_TOKEN_SECRET= export SCRAWL_LINKEDIN_CLIENT_ID= export SCRAWL_LINKEDIN_CLIENT_SECRET= From 607ccda87e3b0213688b61e3fac18551295635af Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 11:24:05 -0400 Subject: [PATCH 05/12] Remove Google+ code; shutdown in 2019. --- README.md | 5 ++-- config.yaml.example | 14 ----------- index.js | 57 +-------------------------------------------- 3 files changed, 3 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index f98ebc5..00df8d4 100644 --- a/README.md +++ b/README.md @@ -75,14 +75,13 @@ There are several more options available. -d, --directory The directory to process. -m, --html If set, write the minutes to an index.html file -e, --email If set, publish the minutes to the mailing list - -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 Google related switch also requires some custom -environment variables to be setup. For examples of those, see the +Please note, there are custom environment variables which may need to be setup. +For examples of those, see the [publish.sh.example](publish.sh.example). ## Web-based editor diff --git a/config.yaml.example b/config.yaml.example index a88f44b..a137483 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -25,17 +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 diff --git a/index.js b/index.js index d796fb1..0fb0d95 100755 --- a/index.js +++ b/index.js @@ -17,7 +17,6 @@ program // the do something switches .option('-m, --html', 'If set, write the minutes to an index.html file') .option('-e, --email', 'If set, publish the minutes to the mailing list') - .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') @@ -41,7 +40,7 @@ if(!program.directory) { process.exit(1); } -if (!program.html && !program.email && !program.google && !program.index) { +if (!program.html && !program.email && !program.index) { console.error('Error: Nothing to do...'); program.outputHelp(); process.exit(1); @@ -83,26 +82,6 @@ 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}}/`; - /************************* Utility Functions *********************************/ function sendEmail(username, password, hostname, content, callback) { var server = email.server.connect({ @@ -359,40 +338,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(err) { // check to ensure there were no errors if(err) { From fdaac3166b8fadf5bb3ebc43bd17cb5f3a11ea3d Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 12:25:20 -0400 Subject: [PATCH 06/12] Bring in latest JSON-LD fork code. * Much better handling of people and bot data. * Changes to HTML output. * Replacement handling. The output now matches what is in place on https://json-ld.org/minutes/ --- index.js | 7 +- www/scrawl.js | 384 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 262 insertions(+), 129 deletions(-) diff --git a/index.js b/index.js index 0fb0d95..d825a8e 100755 --- a/index.js +++ b/index.js @@ -190,7 +190,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(/(?]*)>\s+(.*)$/; - const scribeRx = /^(scribe|scribenick):.*$/i; + const commentRx = /^\[?(\S*)\]?\s+<([^>]*)>\s+(.*)$/; + const scribeRx = /^(scribe|scribenick)[:+\-](.*)$/i; const meetingRx = /^meeting:\s(.*)$/i; const totalPresentRx = /^total present:\s(.*)$/i; const dateRx = /^date:\s(.*)$/i; - const chairRx = /^chair:.*$/i; + const chairRx = /^chair[:+\-].*$/i; const audioRx = /^audio:\s?(.*)$/i; const proposalRx = /^(proposal|proposed):.*$/i; - const presentRx = /^present[:+](.*)$/i; + const presentRx = /^present[:+\-](.*)$/i; + const regretsRx = /^regrets[:+\-](.*)$/i; const resolutionRx = /^(resolution|resolved): ?(.*)$/i; const useCaseRx = /^(use case|usecase):\s?(.*)$/i; - const topicRx = /^topic:\s*(.*)$/i; + const agendumRx = /^agendum \d+\s+\-\- (.*) \-\-/i + const topicRx = /^((?:sub)?topic):\s*(.*)$/i; const actionRx = /^action:\s*(.*)$/i; const voipRx = /^voip.*$/i; const toVoipRx = /^voip.{0,4}:.*$/i; - const rrsAgentRx = /^RRSAgent.*$/i; - const queueRx = /^q[+-?]\s.*|^q[+-?].*|^ack\s+.*|^ack$/i; + const botRx = /^(rrsagent|zakim|agendabot).*$/i; + const allowedBotRx = /^(ghurlbot|gb).*$/i; + const junkRx = / has (joined|left|changed)/i; + const queueRx = /^qq?[+-?]\s.*|^qq?[+-?].*|^ack\s+.*|^ack$/i; const voteRx = /^[+-][01]\s.*|[+-][01]$/i; const agendaRx = /^agenda:\s*((https?):.*)$/i; const urlRx = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/; + // Based on https://github.com/w3c/scribe2/blob/51a2e428fb1d6edf1fe1d1eba756c81d9b109cdd/scribe.perl#L820 + var replaceRx = /^ *(s|i)(\/|\|)(.*?)\2(.*?)(?:\2([gG*])? *)?$/; + // Compatability code to make this work in both node.js and the browser const scrawl = {}; let nodejs = false; @@ -63,6 +70,48 @@ return str.match(new RegExp(regex, 'g')).join(brk); }; + // Record diffent categories for people + scrawl.processPeople = function(context, category, nick, line, remove = false) + { + const lower = line.toLowerCase(); + let [, cmd, op, rest] = lower.match(/(\w+)([:+\-])\s*(.*)$/); + + if(!(category in context)) { + context[category] = []; + } + + // category: resets the list + if(op === ':' && remove === true) { + context[category] = []; + } + + // line has form of (category)[:+=] nick or first_last + var whoall = rest + .split(',') + .map(n => n.trim()) + .filter(e => !!e); + + // If no who, use nick + if(whoall.length === 0) {whoall = [nick]} + + for(const who of whoall) { + if(!(who in context.aliases)) { + // Create an alias record, presuming this is First_Last name + context.aliases[who] = who.split('_').join(' '); + } + let name = context.aliases[who] + + if(op === '-' && remove === true) { + // Remove from category + context[category] = context[category].filter(i => i !== name); + } else if(!(context[category].includes(name))){ + // Add to category + context[category].push(name); + } + } + } + + // FIXME: integrate changes from CCG, considering overlap with processPeople scrawl.generateAliases = function() { const rval = {}; @@ -114,12 +163,18 @@ if(textMode === 'html') { - rval = '

        Topic: ' + - scrawl.htmlencode(msg) + '

        \n'; + const [h, t] = Number.isInteger(id) ? ['h1', 'Topic'] : ['h2', 'Subtopic']; + rval = '<' + h + ' onmouseout="$(\'#link-topic-' + id + '\').hide()" ' + + 'onmouseover="$(\'#link-topic-' + id + '\').show()" ' + + 'id="topic-' + id + '" class="topic">\n'; + rval += t + ': ' + + scrawl.htmlencode(msg) + '\n'; + rval += '\n'; } else { - rval = '\nTopic: ' + msg + '\n\n'; + rval = '\n' + t + ' ' + msg + '\n\n'; } return rval; @@ -183,9 +238,13 @@ if(textMode === 'html') { - rval = '
        ' + - 'RESOLUTION: ' + - scrawl.htmlencode(msg) + '
        \n'; + rval = '
        \n'; + rval += 'RESOLUTION: ' + + scrawl.htmlencode(msg) + '\n'; + rval += '
        \n'; } else { @@ -229,7 +288,9 @@ if(textMode === 'html') { scrawl.counter += 1; - rval = '\n'; + '" style="display:none;" href="#'+ scrawl.counter + '">✪
        \n'; } else { @@ -291,7 +352,7 @@ { if(person !== undefined) { - context.present[person] = true; + scrawl.processPeople(context, 'present', person, 'present+', false); } }; @@ -320,48 +381,88 @@ scrawl.htmlFooter = footer; }; - scrawl.processLine = function(context, aliases, line, textMode) + scrawl.preprocessLine = function(context, lines, lineNumber) + { + const line = lines[lineNumber]; + const match = commentRx.exec(line); + if(!match) + { + return; + } + const [_, time, nick, msg] = match; + + // check for a substitution + const replaceMatch = replaceRx.exec(msg); + if(replaceMatch) + { + const [_, cmd, delim, old, replacement, modifier] = replaceMatch; + if (cmd !== 's') + { + console.error(`command not supported on line ${lineNumber}: ${line}`); + return; + } + const maxReplaces = modifier === 'g' || modifier === 'G' ? Infinity : 1; + const endLineN = modifier === 'G' ? lines.length-1 : lineNumber-1; + let numReplaces = 0; + for(let i = endLineN; i >= 0; i--) + { + const line = lines[i]; + const newLine = line.replace(old, replacement); + if(line !== newLine) + { + lines[i] = newLine; + console.log('Replacing', JSON.stringify(old), 'with', JSON.stringify(replacement), ' on line', i) + if(++numReplaces >= maxReplaces) + { + break; + } + } + } + lines[lineNumber] = ''; + } + } + + scrawl.processLine = function(context, line, textMode) { let rval = ''; const match = commentRx.exec(line); - if(match) + if(!match) { - const nick = match[2].toLowerCase(); - const msg = match[3]; + return ''; + } + const [_, time, _nick, msg] = match; + const nick = _nick.toLowerCase(); + const nickName = context.aliases[nick]; // check for a scribe line if(msg.search(scribeRx) !== -1) { - const scribe = msg.split(':')[1].replace(' ', ''); - scribe = scribe.toLowerCase(); - if(scribe in aliases) + if(nick === 'transcriber') { + if(!(nick in context.aliases)) { - if(!context.hasOwnProperty('scribe')) { - context.scribe = []; - } + context.aliases[nick] = 'Transcriber' + } + context.scribe.push('Transcriber') + context.scribenick.push('Transcriber') + rval = scrawl.information('Our Robot Overlords are scribing.'); + } else { + // 'scribe' collects all scribes in the meeting + scrawl.processPeople(context, 'scribe', nick, msg, false); - context.scribenick = scribe; - context.scribe.push(aliases[scribe]); - scrawl.present(context, aliases[scribe]); + // 'scribenick' maintains list of current scribes + scrawl.processPeople(context, 'scribenick', nick, msg, true); + + if(!(msg.includes('scribe-'))) { rval = scrawl.information( context.scribe[context.scribe.length-1] + ' is scribing.', textMode); } + } } else if(msg.search(chairRx) !== -1) { - var chairs = msg.split(':')[1].split(','); - - context.chair = []; - for(let i = 0; i < chairs.length; i++) { - const chair = chairs[i].replace(' ', '').toLowerCase(); - if(chair in aliases) - { - context.chair.push(aliases[chair]); - scrawl.present(context, aliases[chair]); - } - } + scrawl.processPeople(context, 'chair', nick, msg, true); } // check for meeting line else if(msg.search(meetingRx) !== -1) @@ -369,29 +470,15 @@ const meeting = msg.match(meetingRx)[1]; context.group = meeting; } + // check for regrets line + else if(msg.search(regretsRx) !== -1) + { + scrawl.processPeople(context, 'regrets', nick, msg, true); + } // check for present line else if(msg.search(presentRx) !== -1) { - const present = msg.match(presentRx)[1].toLowerCase(); - const people = present.split(','); - - // try to find the person by full name, last name, and then first name - for(let i = 0; i < people.length; i++) { - if (!people[i]) { - scrawl.present(context, aliases[nick]); - } else { - const person = people[i].replace(/^\s/, '').replace(/\s$/, ''); - const lastName = person.split(' ')[1]; - const firstName = person.split(' ')[0]; - if(person in aliases) { - scrawl.present(context, aliases[person]); - } else if(lastName in aliases) { - scrawl.present(context, aliases[lastName]); - } else { - console.log('Could not find alias for', person); - } - } - } + scrawl.processPeople(context, 'present', nick, msg, true); } // check for audio line else if(msg.search(audioRx) !== -1) @@ -407,12 +494,26 @@ // check for topic line else if(msg.search(topicRx) !== -1) { - const topic = msg.match(topicRx)[1]; + const [_, cmd, topic] = msg.match(topicRx); + if(cmd.toLowerCase() == 'topic') { context.topics = context.topics.concat(topic); + context.subTopicIndex = 0; + rval = scrawl.topic(topic, context.topics.length, textMode); + } else { + // subTopic + context.subTopicIndex += 0.1; + rval = scrawl.topic(topic, context.subTopicIndex + context.topics.length, textMode); + } + } + // Agenda handling: the agendum display should be converted into a bona fide topic + else if(nick === 'zakim' && msg.search(agendumRx) !== -1 ) + { + const topic = msg.match(agendumRx)[1]; + context.topics = context.topics.concat(topic); rval = scrawl.topic(topic, context.topics.length, textMode); } // check for action line - else if(msg.search(actionRx) !== -1) + else if(nick !== 'rrsagent' && msg.search(actionRx) !== -1) { const action = msg.match(actionRx)[1]; context.actions = context.actions.concat(action); @@ -448,11 +549,19 @@ { context.totalPresent = msg.match(totalPresentRx)[1]; } - else if(nick.search(voipRx) !== -1 || msg.search(toVoipRx) !== -1 || - nick.search(rrsAgentRx) !== -1 || msg.search(rrsAgentRx) !== -1 ) + else if(nick.search(botRx) !== -1 || msg.search(botRx) !== -1 ) { // the line is from or is addressed to a channel bot, ignore it } + else if(nick.search(allowedBotRx) !== -1) + { + // add line without other processing + rval = scrawl.scribe(msg, textMode); + } + else if( msg.search(junkRx) !== -1 ) + { + // Other junk lines + } else if(msg.search(queueRx) !== -1) { // the line is queue management, ignore it @@ -460,14 +569,14 @@ // the line is a +1/-1 vote else if(msg.search(voteRx) !== -1) { - if(nick in aliases) + if(nick in context.aliases) { - rval = scrawl.scribe(msg, textMode, aliases[nick]); - scrawl.present(context, aliases[nick]); + rval = scrawl.scribe(msg, textMode, context.aliases[nick]); + //scrawl.present(context, nick); } } - // the line is by the scribe - else if(nick === context.scribenick) + // the line is by a scribe + else if(context.scribenick.includes(nickName)) { if(msg.indexOf('…') === 0 || msg.indexOf('...') === 0) { @@ -478,15 +587,15 @@ { const alias = msg.split(':', 1)[0].replace(' ', '').toLowerCase(); - if(alias in aliases) + if(alias in context.aliases) { // the line is a comment made by somebody else that was // scribed const cleanedMessage = msg.split(':').splice(1).join(':'); - scrawl.present(context, aliases[alias]); + //scrawl.present(context, alias); rval = scrawl.scribe( - cleanedMessage, textMode, aliases[alias]); + cleanedMessage, textMode, context.aliases[alias]); } else { @@ -502,29 +611,29 @@ } } // the line is a comment by somebody else - else if(nick !== context.scribenick) + else if(!context.scribenick.includes(nickName)) { if(msg.indexOf(':') !== -1) { const alias = msg.split(':', 1)[0].replace(' ', '').toLowerCase(); - if(alias in aliases) + if(alias in context.aliases) { // the line is a scribe assist const cleanedMessage = msg.split(':').splice(1).join(':'); - scrawl.present(context, aliases[alias]); + //scrawl.present(context, alias); rval = scrawl.scribe(cleanedMessage, textMode, - aliases[alias], aliases[nick]); + context.aliases[alias], context.aliases[nick]); } else if(alias.indexOf('http') === 0) { - rval = scrawl.scribe(msg, textMode, aliases[nick]); + rval = scrawl.scribe(msg, textMode, context.aliases[nick]); } - else if(aliases.hasOwnProperty(nick)) + else if(nick in context.aliases) { - scrawl.present(context, aliases[nick]); - rval = scrawl.scribe(msg, textMode, aliases[nick]); + //scrawl.present(context, nick); + rval = scrawl.scribe(msg, textMode, context.aliases[nick]); } else { @@ -533,7 +642,7 @@ textMode); } } - else if (!(nick in aliases)) { + else if (!(nick in context.aliases)) { rval = scrawl.error( '(IRC nickname \'' + nick + '\' not recognized)' + line, textMode); @@ -541,14 +650,13 @@ else { // the line is a scribe line by somebody else - scrawl.present(context, aliases[nick]); - rval = scrawl.scribe(msg, textMode, aliases[nick]); + //scrawl.present(context, nick); + rval = scrawl.scribe(msg, textMode, context.aliases[nick]); } } else { rval = scrawl.error('(Strange line format)' + line, textMode); - } } return rval; @@ -564,21 +672,35 @@ const agenda = context.agenda; const audio = 'audio.ogg'; const chair = context.chair; - const scribe = context.scribe.filter(function(item, i, arr) { - return arr.indexOf(item) === i; - }); + const scribe = context.scribe; const topics = context.topics; const resolutions = context.resolutions; const actions = context.actions; const present = []; + const regrets = []; // build the list of people present - for(const name in context.present) { - const person = scrawl.people[name] + for(const name of context.present) { + const person = scrawl.people[name] || {}; person['name'] = name; + if(person.homepage === undefined) { + person.homepage = 'https://json-ld.org/' + } present.push(person) } + // build the list of regrets + if(context.regrets) { + for(const name of context.regrets) { + const person = scrawl.people[name] || {}; + person['name'] = name; + if(person.homepage === undefined) { + person.homepage = 'https://json-ld.org/' + } + regrets.push(person) + } + } + // modify the time if it was specified if(context.date) { time = new Date(context.date) @@ -603,6 +725,28 @@ rval += '

        Minutes for ' + time.getFullYear() + '-' + month + '-' + day +'

        \n'; rval += '
        \n
        \n'; + + // generate the list of people present + peoplePresent = present.map(person => { + return ''+ person.name + '' + }).join(', ') + + if(context.totalPresent) { + peoplePresent += ', ' + context.totalPresent; + } + + // generate the list of regrets + const peopleRegrets = regrets.map(person => { + return ''+ person.name + '' + }).join(', ') + + rval += '
        Present
        ' + peoplePresent + '
        \n'; + if(regrets.length > 0) { + rval += '
        Regrets
        ' + peopleRegrets + '
        \n'; + } + rval += '
        Chair(s)
        ' + chair.join(', ') + '
        \n'; + rval += '
        Scribe(s)
        ' + scribe.join(', ') + '
        \n'; + rval += '
        Agenda
        ' + agenda + '
        \n'; @@ -642,30 +786,6 @@ rval += '
      '; } - // generate the list of people present - let peoplePresent = '' - for(let i = 0; i < present.length; i++) { - const person = present[i]; - - if(i > 0) { - peoplePresent += ', '; - } - - if ('homepage' in person) { - peoplePresent += ''+ - person.name + ''; - } else { - peoplePresent += person.name; - } - } - if(context.totalPresent) { - peoplePresent += ', ' + context.totalPresent; - } - - rval += '
      Organizer
      ' + chair.join(', ') + '
      \n'; - rval += '
      Scribe
      ' + scribe.join(', ') + '
      \n'; - rval += '
      Present
      ' + peoplePresent + '
      \n'; - if(context.audio) { rval += '
      Audio Log
      ' + '\n' + @@ -680,20 +800,15 @@ else { // generate the list of people present - let peoplePresent = '' - for(let i = 0; i < present.length; i++) { - var person = present[i]; - - if(i > 0) { - peoplePresent += ', '; - } + var peoplePresent = present.map(person => person.name).join(', ') - peoplePresent += person.name - } if(context.totalPresent) { peoplePresent += ', ' + context.totalPresent; } + // generate the list of regrets + const peopleRegrets = regrets.map(person => person.name).join(', ') + rval += group; rval += ' Minutes for ' + time.getFullYear() + '-' + month + '-' + day + '\n\n'; @@ -739,6 +854,12 @@ rval += 'Scribe:\n ' + scribe.join(' and ') + '\n'; rval += 'Present:\n ' + scrawl.wordwrap(peoplePresent, 65, '\n ') + '\n'; + + if(regrets.length > 0) { + rval += 'Regrets:\n ' + + scrawl.wordwrap(peopleRegrets, 65, '\n ') + '\n'; + } + if(context.audio) { rval += 'Audio:\n ' + scrawl.minutes_base_url + time.getFullYear() + '-' + @@ -755,7 +876,7 @@ { let minutes = ''; const ircLines = ircLog.split(/\r?\n/); - const aliases = scrawl.generateAliases(); + const aliases = scrawl.generateAliases(ircLog); scrawl.counter = 0; // TODO: expose this better? @@ -769,12 +890,14 @@ { 'group': scrawl.group, 'chair': chair, - 'present': {}, + 'present': [], 'scribe': [], + 'scribenick': [], 'topics': [], 'resolutions': [], 'actions': [], - 'audio': haveAudio + 'audio': haveAudio, + 'aliases': aliases }; if(date) { @@ -782,11 +905,16 @@ context.date.setHours(36); } - // process each IRC log line + // pre-process each IRC log line for(var i = 0; i < ircLines.length; i++) { - var line = ircLines[i]; - minutes += scrawl.processLine(context, aliases, line, textMode); + scrawl.preprocessLine(context, ircLines, i); + } + + // process each IRC log line + for(line of ircLines) + { + minutes += scrawl.processLine(context, line, textMode); } // generate the meeting summary From 8db21a45f1304dc773cae0c9dc4436cc673ae704 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 13:36:08 -0400 Subject: [PATCH 07/12] Add missing publish.sh example; rename examples. Putting the file extension at the end helps editing/viewieng. --- README.md | 8 ++++---- config.yaml.example => config.example.yaml | 0 people.json.example => people.example.json | 0 publish.example.sh | 13 +++++++++++++ publishing.cfg.example => publishing.example.cfg | 0 5 files changed, 17 insertions(+), 4 deletions(-) rename config.yaml.example => config.example.yaml (100%) rename people.json.example => people.example.json (100%) create mode 100644 publish.example.sh rename publishing.cfg.example => publishing.example.cfg (100%) diff --git a/README.md b/README.md index 00df8d4..8db91d0 100644 --- a/README.md +++ b/README.md @@ -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. @@ -82,7 +82,7 @@ There are several more options available. Please note, there are custom environment variables which may need to be setup. For examples of those, see the -[publish.sh.example](publish.sh.example). +[publish.example.sh](publish.example.sh). ## Web-based editor @@ -102,7 +102,7 @@ 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). diff --git a/config.yaml.example b/config.example.yaml similarity index 100% rename from config.yaml.example rename to config.example.yaml diff --git a/people.json.example b/people.example.json similarity index 100% rename from people.json.example rename to people.example.json diff --git a/publish.example.sh b/publish.example.sh new file mode 100644 index 0000000..4fe854d --- /dev/null +++ b/publish.example.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# +# Generates minutes for the directory given + +echo "Generating minutes and social media posts for $1" +DATESTR=`echo $1 | cut -f2 -d/` +MESSAGE="Add text minutes and audio logs for $DATESTR telecon." + +# Generate minutes +nodejs index.js -d $1 -m -i +git add $1/irc.log $1/index.html $1/audio.ogg +git commit $1/irc.log $1/index.html $1/audio.ogg $1/../index.html -m "$MESSAGE" +git push diff --git a/publishing.cfg.example b/publishing.example.cfg similarity index 100% rename from publishing.cfg.example rename to publishing.example.cfg From 7bea93b6766d54310c490282308507a7da8a88a5 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 13:38:29 -0400 Subject: [PATCH 08/12] Revert "Add missing comment-link styles to header" This reverts commit 27f956d8d05ae10b566359a0b108b7d023889082. No evidence of these being in any HTML generated...so not sure what the history was there...but we do not seem to need these now. --- www/_partials/header.html | 7 ------- 1 file changed, 7 deletions(-) diff --git a/www/_partials/header.html b/www/_partials/header.html index 1b8274e..a50aff2 100644 --- a/www/_partials/header.html +++ b/www/_partials/header.html @@ -86,13 +86,6 @@ margin-left: 2em; } -.comment-link { - display: none; -} -.comment:hover .comment-link { - display: inline; -} - div.proposal { padding: 15px; margin: 20px 0px 20px 0px; From 1ea63f20aa40dce533949c0cdb470324cfe490ac Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 13:40:00 -0400 Subject: [PATCH 09/12] Make default header.html match JSON-LD template. --- www/_partials/header.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/www/_partials/header.html b/www/_partials/header.html index a50aff2..56150f9 100644 --- a/www/_partials/header.html +++ b/www/_partials/header.html @@ -77,12 +77,10 @@ div.comment { font-family: 'Droid Serif', serif; margin-top: 0.5em; - font-size: 1.25em; } div.comment-continuation { font-family: 'Droid Serif', serif; - font-size: 1.25em; margin-left: 2em; } @@ -116,7 +114,7 @@

      The W3C JSON-LD Community Group

      -

      JSON-LD Syntax and API

      Go Back


      +

      W3C Logo

      \ No newline at end of file From 335008ae48a8d43bd21f314dcf613b5a3eae9f73 Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 15:53:09 -0400 Subject: [PATCH 10/12] Add `changes.log` additions to `irc.log`. This allows for adding additional modifications to the `irc.log` file (modifying chairs, scribes, etc.) without altering the raw `irc.log` data. --- index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index d825a8e..094bbe6 100755 --- a/index.js +++ b/index.js @@ -49,6 +49,7 @@ if (!program.html && !program.email && !program.index) { // 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, '/..'); @@ -130,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 From d0e37da91fb2dc03402c1f2f20179e7c588b0cff Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 16:48:30 -0400 Subject: [PATCH 11/12] Add info about IRC log changes. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 8db91d0..f7f0d00 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,18 @@ 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 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 From 88b81b0f378036c00b3c02820f22b432ee5e764b Mon Sep 17 00:00:00 2001 From: Benjamin Young Date: Wed, 24 Sep 2025 16:48:43 -0400 Subject: [PATCH 12/12] Release v0.6.0. --- index.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 094bbe6..c95a433 100755 --- a/index.js +++ b/index.js @@ -10,7 +10,7 @@ var scrawl = require('./www/scrawl'); const yaml = require('js-yaml'); program - .version('0.5.0') + .version('0.6.0') // the setup switches .option('-c, --config ', 'The YAML configuration file.') .option('-d, --directory ', 'The directory to process.') diff --git a/package.json b/package.json index 4371052..f7357fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scrawl", - "version": "0.5.0", + "version": "0.6.0", "description": "IRC log processing tool for scribes to assist in creating minutes for W3C group.", "keywords": [ "IRC",