I'm trying to work with this ready-made script to create files in a specific folder upon Calendar Event creation.
The script works well for my needs but, due to my company policies, I need to modify the first part in order to target a specific folder inside a Shared Drive.
Any idea how I could do that? I know that I need to use Drive API instead of DriveApp but so far I couldn't find any example to follow.
Sorry i'm very new to this and missing some basics..
Thanks in advance
Diego
/**
* Checks if the folder for Agenda docs exists, and creates it if it doesn't.
*
* #return {*} Drive folder ID for the app.
*/
function checkFolder() {
const folders = DriveApp.getFoldersByName('Agenda Maker - App');
// Finds the folder if it exists
while (folders.hasNext()) {
let folder = folders.next();
if (
folder.getDescription() ==
'Apps Script App - Do not change this description' &&
folder.getOwner().getEmail() == Session.getActiveUser().getEmail()
) {
return folder.getId();
}
}
// If the folder doesn't exist, creates one
let folder = DriveApp.createFolder('Agenda Maker - App');
folder.setDescription('Apps Script App - Do not change this description');
return folder.getId();
}
/**
* Finds the template agenda doc, or creates one if it doesn't exist.
*/
function getTemplateId(folderId) {
const folder = DriveApp.getFolderById(folderId);
const files = folder.getFilesByName('Agenda TEMPLATE##');
// If there is a file, returns the ID.
while (files.hasNext()) {
const file = files.next();
return file.getId();
}
// Otherwise, creates the agenda template.
// You can adjust the default template here
const doc = DocumentApp.create('Agenda TEMPLATE##');
const body = doc.getBody();
body
.appendParagraph('##Attendees##')
.setHeading(DocumentApp.ParagraphHeading.HEADING1)
.editAsText()
.setBold(true);
body.appendParagraph(' ').editAsText().setBold(false);
body
.appendParagraph('Overview')
.setHeading(DocumentApp.ParagraphHeading.HEADING1)
.editAsText()
.setBold(true);
body.appendParagraph(' ');
body.appendParagraph('- Topic 1: ').editAsText().setBold(true);
body.appendParagraph(' ').editAsText().setBold(false);
body.appendParagraph('- Topic 2: ').editAsText().setBold(true);
body.appendParagraph(' ').editAsText().setBold(false);
body.appendParagraph('- Topic 3: ').editAsText().setBold(true);
body.appendParagraph(' ').editAsText().setBold(false);
body
.appendParagraph('Next Steps')
.setHeading(DocumentApp.ParagraphHeading.HEADING1)
.editAsText()
.setBold(true);
body.appendParagraph('- Takeaway 1: ').editAsText().setBold(true);
body.appendParagraph('- Responsible: ').editAsText().setBold(false);
body.appendParagraph('- Accountable: ');
body.appendParagraph('- Consult: ');
body.appendParagraph('- Inform: ');
body.appendParagraph(' ');
body.appendParagraph('- Takeaway 2: ').editAsText().setBold(true);
body.appendParagraph('- Responsible: ').editAsText().setBold(false);
body.appendParagraph('- Accountable: ');
body.appendParagraph('- Consult: ');
body.appendParagraph('- Inform: ');
body.appendParagraph(' ');
body.appendParagraph('- Takeaway 3: ').editAsText().setBold(true);
body.appendParagraph('- Responsible: ').editAsText().setBold(false);
body.appendParagraph('- Accountable: ');
body.appendParagraph('- Consult: ');
body.appendParagraph('- Inform: ');
doc.saveAndClose();
folder.addFile(DriveApp.getFileById(doc.getId()));
return doc.getId();
}
/**
* When there is a change to the calendar, searches for events that include "#agenda"
* in the decrisption.
*
*/
function onCalendarChange() {
// Gets recent events with the #agenda tag
const now = new Date();
const events = CalendarApp.getEvents(
now,
new Date(now.getTime() + 2 * 60 * 60 * 1000000),
{search: '#agenda'},
);
const folderId = checkFolder();
const templateId = getTemplateId(folderId);
const folder = DriveApp.getFolderById(folderId);
// Loops through any events found
for (i = 0; i < events.length; i++) {
const event = events[i];
// Confirms whether the event has the #agenda tag
let description = event.getDescription();
if (description.search('#agenda') == -1) continue;
// Only works with events created by the owner of this calendar
{
// Creates a new document from the template for an agenda for this event
const newDoc = DriveApp.getFileById(templateId).makeCopy();
newDoc.setName(event.getTitle() + ' - Agenda');
const file = DriveApp.getFileById(newDoc.getId());
folder.addFile(file);
const doc = DocumentApp.openById(newDoc.getId());
const body = doc.getBody();
// !!!MMA!!! Fills in the template with information about the attendees from the
// calendar event
const conf = body.findText('##Attendees##');
if (conf) {
const ref = conf.getStartOffset();
for (let i in event.getGuestList()) {
let guest = event.getGuestList()[i];
body.insertParagraph(ref + 11, guest.getEmail());
}
body.replaceText('##Attendees##', 'Attendees:');
body.replaceText('##Meeting_Name##', event.getTitle());
body.replaceText('##Person_In_Charge##', event.getCreators());
body.replaceText('##Description##', event.getDescription());
body.replaceText('##Location##', event.getLocation());
body.replaceText('##Time##', Utilities.formatDate(event.getStartTime(), Session.getScriptTimeZone(),"dd/MM/yyyy" + " # " + "HH:mm"));
}
// Replaces the tag with a link to the agenda document
const agendaUrl = 'https://docs.google.com/document/d/' + newDoc.getId();
description = description.replace(
'#agenda',
'<a href=' + agendaUrl + '>Link to Meeting Agenda</a>',
);
event.setDescription(description);
// Invites attendees to the Google doc so they automatically receive access to the agenda
newDoc.addEditor(newDoc.getOwner());
for (let i in event.getGuestList()) {
let guest = event.getGuestList()[i];
newDoc.addEditor(guest.getEmail());
}
}
}
return;
}
/**
* Creates an event-driven trigger that fires whenever there's a change to the calendar.
*/
function setUp() {
let email = Session.getActiveUser().getEmail();
ScriptApp.newTrigger("onCalendarChange").forUserCalendar(email).onEventUpdated().create();
}
Related
My goals are to obtain the users nickname by using their ID.
Their ID's are stored as variables which are being collected from a reaction collector.
I have tried a few methods and failed, most of which either return nothing or errors.
The below code returns nothing, the getnames() function is empty. This method was recommended to me buy 2 people from a nodejs discord server which aims to help solve issues, similar to here.
// returns player ID's
function getPlayers() {
let players = [];
players.push(queue.tank[0]); // First (1) in TANK queue
players.push(queue.heal[0]); // First (1) in HEAL queue
players.push(queue.dps[0]); // First (2) in DPS queue
players.push(queue.dps[1]);
return players;
}
// get nick names from ID's
function getnames() {
let players = getPlayers();
let playerNicks = [];
let newPlayer = "";
players.forEach(async player => {
newPlayer = await message.guild.members.fetch(player).then(function (user) {return user.displayName });
playerNicks.push(newPlayer)
return playerNicks;
})}
//formats nicknames into string
function formatnicknames() {
let formatted_string2 = '';
let playerNicks = getnames();
if (playerNicks)
formatted_string2 = `${playerNicks[0]} \n${playerNicks[1]} \n${playerNicks[2]} \n${playerNicks[3]}`;
return formatted_string2;
}
I have also tried a few variations of the below code, still unable to obtain nickname.
message.guild.members.cache.get(user.id)
Edit #1
now tried the following code with no success. (boost1ID contains the ID of 1 user)
var mem1 = message.guild.members.fetch(boost1ID).nickname
Edit #2
tried a new method of obtaining displayname from ID.
var guild = client.guilds.cache.get('guildid');
var mem1 = guild.member(boost1ID);
var mem2 = guild.member(boost2ID);
var mem3 = guild.member(boost3ID);
var mem4 = guild.member(boost4ID);
var nickname1 = mem1 ? mem1.displayName : null;
var nickname2 = mem2 ? mem2.displayName : null;
var nickname3 = mem3 ? mem3.displayName : null;
var nickname4 = mem4 ? mem4.displayName : null;
var Allnicknames = `${nickname1} ${nickname2} ${nickname3} ${nickname4}`
message.channel.send(`testing nicknames: ${Allnicknames}`)
I managed to only return my own name since i dont have a nickname on this server, but the other three users who does have a nickname returned null.
This is the simplest solution:
// your users ids
const IDs = [ '84847448498748974', '48477847847844' ];
const promises = IDs.map((userID) => {
return new Promise(async (resolve) => {
const member = message.guild.member(userID) || await message.guild.members.fetch(userID);
resolve(member.displayName || member.user.username);
});
});
const nicknames = await Promise.all(promises);
// you now have access to ALL the nicknames, even if the members were not cached!
The members you are trying to get the nicknames of are not necessarily cached, and this fixes that.
I made an example that could help you.
let testUsers = [];
module.exports = class extends Command {
constructor(...args) {
super(...args, {
description: 'Testing.',
category: "Information",
});
}
async run(message) {
function getNicknames(userArr, guild) {
let playerNicks = [];
for(var i = 0; i < userArr.length; i++) {
playerNicks.push(guild.member(userArr[i]).displayName);
}
return playerNicks;
}
let testUser = message.guild.members.cache.get(message.author.id);
testUsers.push(testUser);
let guild = message.guild;
console.log(getNicknames(testUsers, guild));
}
}
I created a function getNicknames that takes in two parameters. The first one is an Array of users (as you get one from your function getPlayers()) and the second one is the guild you are playing in. You need to provide the guild, because every user should be a GuildMember, because you want to use .displayName. I created a user Array outside of my command code, because otherwise there will only be one user in the Array everytime you use the command. Inside of the getNicknames() function I have created a new Array playerNicks that I basically fill with the user nicknames we get from our provided user Array.
Now you have to implement that into your code.
The call of the function getNicknames(), for your code should look like this:
getNicknames(getPlayers(), message.guild);
So I have figured out how to set up a simple database with discord.js in a users.json file and my !start cmnd works to create the users database, but when me and my cousin tried the !daily cmnds, the cmnd seems to be fine but I get this error: TypeError: Cannot read property 'a number' of undefined. I believe the number refers to my user number or database number (a number means an actual long number, not "a number").
Also here is the code that goes along with this that is in my index.js file:
var UserJSON = JSON.parse(Fs.readFileSync('./DB/users.json'));
UserJSON[message.author.id] = {
bal: 0,
lastclaim: 0,
}
Fs.writeFileSync('./DB/users.json', JSON.stringify(UserJSON));
let SuccessEmbed = new Discord.MessageEmbed();
SuccessEmbed.setTitle("**SUCCESS**");
SuccessEmbed.setDescription("You have joined the economy! type !help to get started");
message.channel.send(SuccessEmbed);
return;
}
if (args[0] == "daily") {
let userJSON = JSON.parse(Fs.readFileSync('./DB/users.json'));
if (Math.floor(new Date().getTime() - UserJSON[message.author.id].lastclaim) / (1000 * 60 * 60 * 24) < 1) {
let WarningEmbed = new Discord.MessageEmbed()
WarningEmbed.setTitle("**ERROR**");
WarningEmbed.setDescription("You have claimed today already");
message.channel.send(WarningEmbed);
return;
}
UserJSON[message.author.id].bal += 500;
UserJSON[message.author.id].lastclaim = new Date().getTime();
Fs.writeFileSync('./DB/users.json', JSON.stringify(UserJSON));
let SuccessEmbed = new Discord.MessageEmbed();
SuccessEmbed.setTitle("**SUCCESS**");
SuccessEmbed.setDescription("You have claimed a daily reward of 500 coins!");
message.channel.send(SuccessEmbed);
}
}
})
Also to specify, the ./DB/users.json refers to the folder DB for database and users.json is the file that stores the databases.
Here is what the user.json file looks like:
{"*my database number*":{"bal":0,"lastclaim":0},"*my cousin's database number*":{"bal":0,"lastclaim":0}}
Is there any code I need to add into my index.js file to stop this from happening. If possible, answer as soon as possible so I can get this error worked out. Thank You!
Edit: I somehow figured this out by re-doing it and this is the finished product if anyone wants to start an economy bot:
const Discord = require("discord.js");
const client = new Discord.Client();
const Fs = require("fs");
const prefix = "!";
client.on("ready", () => {
console.log("Ready!");
});
client.on("message", async (message) => {
if(message.content.startsWith(prefix)) {
var args = message.content.substr(prefix.length)
.toLowerCase()
.split(" ");
if (args[0] == "start") {
let UserJSON = JSON.parse(Fs.readFileSync("./DB/users.json"));
UserJSON[message.author.id] = {
bal: 0,
lastclaim: 0,
}
Fs.writeFileSync("./DB/users.json", JSON.stringify(UserJSON));
let SuccessEmbed = new Discord.MessageEmbed();
SuccessEmbed.setTitle("**SUCCESS**");
SuccessEmbed.setDescription("You have joined the economy! type !help to get started");
message.channel.send(SuccessEmbed);
return;
}
if (args[0] == "daily") {
let UserJSON = JSON.parse(Fs.readFileSync("./DB/users.json"));
if (Math.floor(new Date().getTime() - UserJSON[message.author.id].lastclaim) / (1000 * 60 * 60 * 24) < 1) {
let WarningEmbed = new Discord.MessageEmbed()
WarningEmbed.setTitle("**ERROR**");
WarningEmbed.setDescription("You have claimed today already");
message.channel.send(WarningEmbed);
return;
}
UserJSON[message.author.id].bal += 500;
UserJSON[message.author.id].lastclaim = new Date().getTime();
Fs.writeFileSync("./DB/users.json", JSON.stringify(UserJSON));
let SuccessEmbed = new Discord.MessageEmbed();
SuccessEmbed.setTitle("**SUCCESS**");
SuccessEmbed.setDescription("You have claimed a daily reward of 500 discord coins!");
message.channel.send(SuccessEmbed);
}
}
})
client.login('your token');
also remember to make a DB folder with an users.json file
I realized that the problem with the code is that instead of vars, it needed to be let before the UserJSON, so the line of code should read:
let UserJSON = JSON.parse(Fs.readFileSync("./DB/users.json"));
UserJSON[message.author.id] = {
bal: 0,
lastclaim: 0,
}
I want to Trigger a Cloud Funtion on insert to Cloud Firestore document for the first time with given uid
The below code triggers on insert of newUserId
functions.firestore
.document(`teamProfile/{teamId}/teamMemberList/{newUserId}`)
.onCreate()
Requirement --
Document : Post
Fields : id, title, uid
id is the document id.
Here, the Cloud function should trigger if it's the first post by uid(user).
Edited response (following clarification in comments):
Data structure:
users/{userId}/posts/
◙ post18sfge89s
- title: My first post!
- t: 1572967518
◙ post2789sdjnf
- title: I like posting
- t: 1572967560
posts/
◙ post18sfge89s
- title: My first post!
- uid: someUid1
- comments/
◙ comment237492384
...
◙ comment234234235
...
◙ post2789sdjnf
- title: I like posting
- uid: someUid1
Cloud Function Code:
With the above structure, you will need two Cloud Functions to manage it – one for handling each new post (to copy information to the author's post list) and one for checking if it's the author's first post.
// Function: New post handler
exports.newPost = functions.firestore
.document('posts/{postId}')
.onCreate((postDocSnap, context) => {
// get relevant information
const postId = postDocSnap.id; // provided automatically
const postTitle = postDocSnap.get('title');
const userId = postDocSnap.get('uid');
const postedAt = postDocSnap.createTime; // provided automatically
// add to user's post list/index
return firestore.doc(`users/${userId}/posts/${postId}`)
.set({
title: postTitle,
t: postedAt
});
});
// Function: New post by user handler
exports.newPostByUser = functions.firestore
.document('users/{userId}/posts/{postId}')
.onCreate((postDocSnap, context) => {
// get references
const userPostsColRef = postDocSnap.ref.parent;
const userDocRef = userPostsCol.parent;
// get snapshot of user data
return userDocRef.get()
.then((userDocSnap) => {
// check if "First Post Event" has already taken place
if (userDocSnap.get('madeFirstPostEvent') != true) {
return getCollectionSize(userPostsColRef).then((length) => {
if (length == 1) {
return handleFirstPostByUser(userDocSnap, postDocSnap);
} else {
return; // could return "false" here
}
});
}
});
});
// Pseudo-function: First post by user handler
function handleFirstPostByUser(userDocSnap, postDocSnap) {
return new Promise(() => {
const postId = postDocSnap.id;
const postTitle = postDocSnap.get('title');
const userId = userDocSnap.id;
// do something
// mark event handled
return userDocSnap.ref.update({madeFirstPostEvent: true});
});
}
// returns a promise containing the length of the given collection
// note: doesn't filter out missing (deleted) documents
function getCollectionSize(colRef) {
return colRef.listDocuments()
.then(docRefArray => {
return docRefArray.length;
});
}
Original response (for posts that are private to each team):
Assumptions:
Checking for a user's first post in a specific team, not platform wide.
Unknown data structure – I have used what I think will work well with your existing structure.
Data Structure:
The teamContent/ collection is structured so that it can contain subcollections for different items such as posts, attachments, pictures, etc.
teamProfile/{teamId}/teamMemberList/{userId}/posts/
◙ post18sfge89s
- title: My first post!
- t: 1572967518
◙ post2789sdjnf
- title: I like posting
- t: 1572967560
teamContent/{teamId}/posts/
◙ post18sfge89s
- title: My first post!
- author: someUid1
- comments/
◙ comment237492384
...
◙ comment234234235
...
◙ post2789sdjnf
- title: I like posting
- author: someUid1
Cloud Function Code:
With the above structure, you will need two Cloud Functions to manage it – one for handling each new post (to copy information to the author's post list) and one for checking if it's the author's first post in that particular team.
// Function: New team post handler
exports.newPostInTeam = functions.firestore
.document('teamContent/{teamId}/posts/{postId}')
.onCreate((postDocSnap, context) => {
// get relevant information
const postId = postDocSnap.id; // provided automatically
const postTitle = postDocSnap.get('title');
const authorId = postDocSnap.get('author');
const postedAt = postDocSnap.createTime; // provided automatically
const teamId = context.params.teamId;
// add to user's post list/index
return firestore.doc(`teamProfile/${teamId}/teamMemberList/${authorId}/posts/${postId}`)
.set({
title: postTitle,
t: postedAt
});
});
// Function: New post by team member handler
exports.newPostByTeamMember = functions.firestore
.document('teamProfile/{teamId}/teamMemberList/{userId}/posts/{postId}')
.onCreate((postDocSnap, context) => {
// get references
const userPostsColRef = postDocSnap.ref.parent;
const userDocRef = userPostsCol.parent;
// get snapshot of user data
return userDocRef.get()
.then((userDocSnap) => {
// check if "First Post Event" has already taken place
if (userDocSnap.get('madeFirstPostEvent') != true) {
return getCollectionSize(userPostsColRef).then((length) => {
if (length == 1) {
return handleFirstPostInTeamEvent(userDocSnap, postDocSnap);
} else {
return; // could return "false" here
}
});
}
});
});
// Pseudo-function: First post by team member handler
function handleFirstPostInTeamEvent(userDocSnap, postDocSnap) {
return new Promise(() => {
const postId = postDocSnap.id;
const postTitle = postDocSnap.get('title');
const userId = userDocSnap.id;
// do something
// mark event handled
return userDocSnap.update({madeFirstPostEvent: true});
});
}
// returns a promise containing the length of the given collection
// note: doesn't filter out missing (deleted) documents
function getCollectionSize(colRef) {
return colRef.listDocuments()
.then(docRefArray => {
return docRefArray.length;
});
}
Notes:
Above code is not completely idempotent. If multiple posts from the same user are all uploaded at once, there is a possibility that the handleFirstPost* functions will be called multiple times.
Above code doesn't account for missing documents returned from listDocuments() in the getCollectionSize() function. This is not a concern in the above sample because the {userId}/posts collection doesn't have any subcollections. Be wary if you call it elsewhere though.
No error handling is included
Use of async/await syntax can make it cleaner
Above code was written without deploying it, bugs/typos may be present
I want to call/trigger a transaction inside from another transaction. how that will be possible.
async function updateOrder(uo) { // eslint-disable-line no-unused-vars
// Get the asset registry for the asset.
assetRegistry = await getAssetRegistry('org.example.basic.OrderList');
for(var i=0;i< uo.asset.orderDtls.length;i++)
{
if(uo.asset.orderDtls[i].orderID==uo.orderID){
uo.asset.orderDtls[i].orderStatus="Accepted";
}
}
await assetRegistry.update(uo.asset);
Please provide any sample code/example to trigger another transaction whenever this transaction happen.
Please view the github issue here:
https://github.com/hyperledger/composer/issues/4375
It should answer your question. A quote from the issue:
/**
* TransactionOne
* #param {org.example.TransactionOne} The transaction one object
* #transaction
*/
async function transactionOne(tx) {
const factory = getFactory();
tx.subTransactions.forEach(async (subTransactionData, idx) => {
const subTx = factory.newResource(namespace, "TransactionTwo", tx.transactionId + ":" + idx);
subTx.subTransactionData= subTransactiondata;
await transactionTwo(subTx);
});
}
Below is virtually all of the code for a node.js app that lets you get playlists for artists if you run the command simply followed by an artists name
simplay the Beatles
From the output in the terminal, I know that the code in the ._flush method (added to the prototype of UrlsForNamesTransform) is getting run but it's never explicitly called. UrlsForNamesTransform extends the Transform stream in node.js, which I mention because I've seen it in other code before where a function is running without explicitly getting called (at least that I can see). Is it something about Transform or what is happening to make the code in ._flush run?
This is the github repo for the code https://github.com/thlorenz/simplay
'use strict';
var urlschema = 'http://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&artist={{artist}}&api_key={{apikey}}&format=json';
var hyperquest = require('hyperquest')
, table = require('text-table')
, colors = require('ansicolors')
, styles = require('ansistyles')
var stream = require('stream');
var util = require('util');
var Transform = stream.Transform;
util.inherits(UrlsForNamesTransform, Transform);
function UrlsForNamesTransform (opts) {
if (!(this instanceof UrlsForNamesTransform)) return new UrlsForNamesTransform(opts);
opts = opts || {};
Transform.call(this, opts);
this._writableState.decodeStrings = false;
this.artist = opts.artist;
this.json = '';
}
UrlsForNamesTransform.prototype._transform = function (chunk, encoding, cb) {
this.json += chunk.toString();
cb();
};
UrlsForNamesTransform.prototype._flush = function (cb) {
var records = [];
try {
var o = JSON.parse(this.json);
var artists = o.similarartists.artist;
if (!Array.isArray(artists)) {
this.push('Sorry, no records for "' + this.artist + '" where found, please correct your spelling and/or try another artist.');
return cb();
}
artists.forEach(function (node) {
var youtubeurl = 'http://www.youtube.com/results?search_query={{artist}},playlist'.replace('{{artist}}', node.name);
var rdiourl = 'http://www.rdio.com/search/{{artist}}/artists/'.replace('{{artist}}', node.name);
var lastfmurl = 'http://www.last.fm/music/{{artist}}'.replace('{{artist}}', node.name);
var lastfmRadioUrl = 'http://www.last.fm/listen/artist/{{artist}}'.replace('{{artist}}', node.name);
var urls = [
''
, colors.white(' youtube: ') + styles.underline(colors.brightBlue(encodeURI(youtubeurl)))
, colors.blue (' rdio: ') + styles.underline(colors.brightBlue(encodeURI(rdiourl)))
, colors.brightRed (' last.fm: ') + styles.underline(colors.brightBlue(encodeURI(lastfmurl)))
, colors.red (' last.fm radio: ') + styles.underline(colors.brightBlue(encodeURI(lastfmRadioUrl)))
, ''
, ''].join('\n');
records.push([ '\n' + colors.brightYellow(node.name), colors.cyan(node.match), urls ]);
})
this.push(table(records.reverse()));
cb();
} catch (err) {
cb(err);
}
}
var go = module.exports =
/**
* Retrieves similar artists for the given artist from last.fm using the apikey.
* Then it converts the information to display youtube.com, last.fm, rdio playlist/artist urls for each artist.
*
* #name simplay
* #function
* #param {String} artist the artist to find similar artists for
* #param {String} apikey the api key to be used with last.fm
* #return {ReadableStream} that will push the url information
*/
function simplay(artist, apikey) {
if (!artist) throw new Error('Please provid the artist that you like to get similar artist links for');
if (!apikey) throw new Error('Please set LASTFM_API env variable to your API key: http://www.last.fm/api/account/create');
var url = urlschema
.replace('{{artist}}', artist)
.replace('{{apikey}}', apikey);
return hyperquest(url)
.on('error', console.error)
.pipe(new UrlsForNamesTransform({ artist: artist }))
.on('error', console.error)
};
The important line is this one:
util.inherits(UrlsForNamesTransform, Transform);
What this means is that UrlsForNamesTransform is a subclass of Transform. There is very good documentation on subclassing Transform, which can be found on the node.js api site.
Essentially, a subclass of Transform must implement _transform and can implement _flush, but is expected to never call either of those functions. Methods in Transform will call them based on events on the incoming stream.