コード例 #1
0
ファイル: f2t-core.js プロジェクト: Mithgol/node-fido2twi
            err => {
               if( err ) return finishedExportToTwitter(err);

               if(
                  Array.isArray(newLastRead) && newLastRead.length > 0
               ) putLastReadToFile(
                  path.resolve(__dirname, sourceArea + '.lastread.json'),
                  newLastRead
               );

               var numTweets = msgExports.length;
               if( numTweets > 0 ){
                  cl.status(
                     `Done. ${numTweets} tweets posted from ${sourceArea}.`
                  );
               } else cl.skip(
                  `Done. No new tweets posted from ${sourceArea}.`
               );

               return finishedExportToTwitter(null);
            }
コード例 #2
0
ファイル: f2t-core.js プロジェクト: Mithgol/node-fido2twi
var weightedCrop = (tweetText, textLimit) => {
   var newLength = tweetText.length;
   var newText;
   var does_not_fit = true;
   // `weightedCrop` is called only when `newLength` does not fit initially

   while( does_not_fit ){
      newLength--;
      if( newLength < 1 ){
         cl.fail('Cannot shorten: ' + tweetText);
         cl.status('Text limit: ' + textLimit);
         process.exit(1);
      }
      newText = tweetText.slice(0, newLength).replace(
         /[\uD800-\uDBFF]$/g, '' // kill a trailing high surrogate, if any
      ) + '…';
      if( twitxt.parseTweet(newText).weightedLength <= textLimit ){
         // does_not_fit = false;
         return newText;
      }
   }
};
コード例 #3
0
ファイル: fido2twi.js プロジェクト: Mithgol/node-fido2twi
         cl.fail(`A file's path is not given after "--msg=".`);
         process.exit(1);
      }
      return false;
   }

   return true;
});

if( params.length < 1 ){
   clog('Usage:');
   clog('   fido2twi sourceArea');
   clog('');
   clog('Parameter:');
   clog('');
   clog('sourceArea  -- areatag of an echomail area in Fidonet');
   clog('');
   clog('An optional "--msg=filename" parameter (before or after the above)');
   clog('means that an individual message (contained in the given file)');
   clog('becomes tweeted from the given echomail area.');
   process.exit(1);
}

const sourceArea = params[0];

if( msgFilePath !== null ){
   cl.status(`Posting an individual message from ${sourceArea}...`);
} else cl.status(`Looking in ${sourceArea} for tweets...`);

require('./f2t-core.js')(sourceArea, { msgFilePath: msgFilePath });
コード例 #4
0
ファイル: f2t-core.js プロジェクト: Mithgol/node-fido2twi
module.exports = (sourceArea, options) => {
   var confF2T = simteconf(
      path.resolve(__dirname, 'fido2twi.config'),
      { skipNames: ['//', '#'] }
   );
   // Read skipped lines:
   var SkipBySubj = confF2T.all('SkipBySubj');
   if( SkipBySubj === null ) SkipBySubj = [];
   SkipBySubj = SkipBySubj.map( nextSubj => nextSubj.trim() );
   // Read HPT areas:
   var areas = fidoconfig.areas(confF2T.last('AreasHPT'), {
      encoding: confF2T.last('EncodingHPT') || 'utf8'
   });
   // Read IPFS configuration:
   var hostportIPFS = confF2T.last('IPFS');
   if( hostportIPFS === null ){
      cl.fail('IPFS settings are not found in fido2twi.config.');
      process.exit(1);
   }
   var [hostIPFS, portIPFS] = hostportIPFS.split(':');
   if( typeof portIPFS === 'undefined' ){
      portIPFS = 5001;
      cl.status(
         'IPFS port is not given in fido2twi.config; assuming port 5001.'
      );
   }

   // read an array of FGHI URLs of last read messages:
   var arrLastRead = getLastReadFromFile(
      path.resolve(__dirname, sourceArea + '.lastread.json')
   );

   var twi = new twitter({
      consumer_key:        confF2T.last('ConsumerKey'),
      consumer_secret:     confF2T.last('ConsumerSecret'),
      access_token_key:    confF2T.last('AccessTokenKey'),
      access_token_secret: confF2T.last('AccessTokenSecret')
   });

   async.waterfall([
      // read the path of the given echomail area
      callback => areas.area(sourceArea, (err, areaData) => {
         if( err ) return quitOnAreaError(err, sourceArea);
         return callback(null, areaData.path);
      }),
      (areaPath, callback) => { // initialize the echobase, read its index
         var echobase = JAM(areaPath);
         echobase.readJDX( err => callback(err, echobase) );
      },
      (echobase, callback) => { // get the username in Twitter
         twi.get(
            'account/verify_credentials',
            {
               include_entities: false,
               skip_status: true,
               include_email: false
            },
            (err, credentials) => {
               if( err ) return callback(err);
               if(
                  typeof credentials.screen_name !== 'string' ||
                  credentials.screen_name.length < 1
               ) return callback(
                  new Error('Invalid `screen_name` credentials.')
               );

               cl.ok(`Successfully verified credentials of @${
               credentials.screen_name}.`);
               return callback(null, {
                  twiUsername: credentials.screen_name,
                  echobase: echobase
               });
            }
         );
      },
      (wrappedData, callback) => { // get the configuration of Twitter
         twi.get(
            'help/configuration',
            (err, twiConfig) => {
               if( err ) return callback(err);
               if(
                  typeof twiConfig.short_url_length_https !== 'number' ||
                  twiConfig.short_url_length_https < 2
               ) return callback(new Error(
                  "Abnormal Twitter's `short_url_length_https` configuration."
               ));

               cl.ok(`Read Twitter's configuration. HTTPS short URLs are ${
               twiConfig.short_url_length_https} characters long.`);
               wrappedData.textLimit = 279 - twiConfig.short_url_length_https;
               return callback(null, wrappedData);
            }
         );
      },
      (wrappedData, callback) => {//generate an array of tweet texts
         var echobase = wrappedData.echobase;

         if( options.msgFilePath !== null ) return postTweetFromMessage(
            options.msgFilePath, echobase, sourceArea, wrappedData.textLimit,
            wrappedData.twiUsername, hostIPFS, portIPFS, callback
         );

         var echosize = echobase.size();
         if( echosize < 1 ) return callback(null, []);

         var msgExports = [];
         var nextMessageNum = echosize;
         var lastReadEncountered = false;
         var newLastRead = [];

         // `msgExports` is filled in reverse chronological order
         // `msgExports` would contain (string) message texts for Twitter
         async.doUntil(
            exportDone => {
               echobase.readHeader(nextMessageNum, (err, header) => {
                  if( err ) return exportDone(err);
                  nextMessageNum--;

                  var decoded = echobase.decodeHeader(header);

                  var itemFGHIURL = generateMessageFGHIURL(
                     sourceArea, decoded.msgid, decoded.origTime
                  );

                  // header and URL are enough to decide if an export happens

                  if( arrLastRead.includes(itemFGHIURL) ){
                     lastReadEncountered = true;
                     return exportDone(null); // do not export previously read
                  } else newLastRead.push(itemFGHIURL);

                  if(
                     typeof decoded.from === 'string' &&
                     decoded.from.startsWith('@') // probably a Twitter handle
                  ) return exportDone(null); // do not re-export to Twitter

                  if(
                     typeof decoded.subj === 'string' &&
                     SkipBySubj.includes( decoded.subj.trim() )
                  ) return exportDone(null); // skip → do not post to Twitter

                  if(
                     Array.isArray(decoded.kludges) &&
                     decoded.kludges.some(aKludge =>
                        aKludge.toLowerCase() === 'sourcesite: twitter'
                     )
                  ) return exportDone(null); // do not re-export to Twitter

                  // now it's decided that an export should happen
                  generateTweetExport(
                     msgExports, wrappedData.twiUsername,
                     sourceArea, echobase, header, decoded,
                     wrappedData.textLimit,
                     itemFGHIURL, hostIPFS, portIPFS, exportDone
                  );
               });
            },
            // `true` if should stop exporting:
            () => lastReadEncountered ||
               nextMessageNum < 1 ||
               msgExports.length >= maxExports,
            err => callback(err, msgExports, newLastRead)
         );
      },
      (msgExports, newLastRead, finishedExportToTwitter) => {
         // `newLastRead` may be not an Array, e.g. after single message post
         var lastIDX = msgExports.length - 1;
         async.eachOfSeries(
            msgExports.reverse(), // restore chronological order
            (nextMessage, messageIDX, sentToTwitter) => {
               twi.post(
                  'statuses/update',
                  { status: nextMessage },
                  err => {
                     if( err ) return sentToTwitter(err);

                     cl.ok(nextMessage);
                     if( messageIDX < lastIDX ){
                        setTimeout(() => {
                           return sentToTwitter(null);
                        }, twiDelay);
                     } else return sentToTwitter(null);
                  }
               );
            },
            err => {
               if( err ) return finishedExportToTwitter(err);

               if(
                  Array.isArray(newLastRead) && newLastRead.length > 0
               ) putLastReadToFile(
                  path.resolve(__dirname, sourceArea + '.lastread.json'),
                  newLastRead
               );

               var numTweets = msgExports.length;
               if( numTweets > 0 ){
                  cl.status(
                     `Done. ${numTweets} tweets posted from ${sourceArea}.`
                  );
               } else cl.skip(
                  `Done. No new tweets posted from ${sourceArea}.`
               );

               return finishedExportToTwitter(null);
            }
         );
      }
   ], err => { // waterfall finished
      if( err ){
         cl.fail('fido2twi error:');
         console.dir(err);
      }
   });
};