huridocs/uwazi

View on GitHub
app/api/services/twitterintegration/TwitterIntegration.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
import { ResultsMessage, TaskManager } from 'api/services/tasksmanager/TaskManager';
import { tenants } from 'api/tenants';
import settings from 'api/settings/settings';
import entities from 'api/entities/entities';
import templates from 'api/templates';
import relationtypes from 'api/relationtypes';
import {
  getTextWithAttachedImages,
  getTwitterImages,
  getTwitterImagesData,
  TwitterImageData,
} from 'api/services/twitterintegration/getTwitterImages';
import { permissionsContext } from 'api/permissions/permissionsContext';

interface TweetParamsType {
  title: string;
  text: string;
  source: string;
  // eslint-disable-next-line camelcase
  user: { display_name: string; url: string };
  // eslint-disable-next-line camelcase
  created_at: number;
  hashtags: string[];
  // eslint-disable-next-line camelcase
  images_urls: string[];
}

interface TwitterIntegrationSettingsType {
  searchQueries: string[];
  hashtagsTemplateName: string;
  tweetsTemplateName: string;
  language: string;
  tweetsLanguages: string[];
}

class TwitterIntegration {
  static SERVICE_NAME = 'twitter_crawler';

  public twitterTaskManager: TaskManager;

  constructor() {
    this.twitterTaskManager = new TaskManager({
      serviceName: TwitterIntegration.SERVICE_NAME,
      processResults: this.processResults,
    });
  }

  start() {
    this.twitterTaskManager.subscribeToResults();
  }

  getTwitterIntegrationSettings = async (): Promise<TwitterIntegrationSettingsType> => {
    const settingsValues = await settings.get({}, 'features');
    if (!settingsValues.features || !settingsValues.features.twitterIntegration) {
      return {
        searchQueries: [],
        hashtagsTemplateName: '',
        tweetsTemplateName: '',
        language: '',
        tweetsLanguages: [],
      };
    }

    return settingsValues.features.twitterIntegration as TwitterIntegrationSettingsType;
  };

  addTweetsRequestsToQueue = async () => {
    const pendingTasks = await this.twitterTaskManager.countPendingTasks();
    if (pendingTasks > 0) {
      return;
    }

    await Promise.all(
      Object.keys(tenants.tenants).map(async tenant => {
        await tenants.run(async () => {
          const twitterIntegration = await this.getTwitterIntegrationSettings();

          if (!twitterIntegration.hashtagsTemplateName) {
            return;
          }

          await this.getTemplateTweets(twitterIntegration);

          // eslint-disable-next-line @typescript-eslint/no-misused-promises,no-restricted-syntax
          for (const hashtag of twitterIntegration.searchQueries) {
            // eslint-disable-next-line no-await-in-loop
            await this.twitterTaskManager.startTask({
              task: 'get-hashtag',
              tenant,
              params: {
                query: hashtag,
                tweets_languages:
                  twitterIntegration && twitterIntegration.tweetsLanguages
                    ? twitterIntegration.tweetsLanguages
                    : [],
              },
            });
          }
        }, tenant);
      })
    );
  };

  processResults = async (message: ResultsMessage): Promise<void> => {
    await tenants.run(async () => {
      permissionsContext.setCommandContext();
      const twitterIntegration = await this.getTwitterIntegrationSettings();

      if (!twitterIntegration.hashtagsTemplateName) {
        return;
      }

      const templateTweet = await this.getTemplateTweets(twitterIntegration);
      const tweetHashtags = await this.saveHashtags(twitterIntegration, message.params?.hashtags);

      const twitterImagesData: TwitterImageData[] = getTwitterImagesData(message);
      const textWithAttachedImages = getTextWithAttachedImages(message, twitterImagesData);
      const entity = await entities.save(
        {
          title: message.params?.title,
          metadata: {
            tweet_text: [{ value: textWithAttachedImages }],
            tweet_source: [{ value: { label: 'link', url: message.params?.source } }],
            tweet_author: [
              {
                value: { label: message.params?.user.display_name, url: message.params?.user.url },
              },
            ],
            tweet_date: [{ value: message.params?.created_at }],
            tweet_hashtags: tweetHashtags,
          },
          template: templateTweet._id,
        },
        { user: {}, language: twitterIntegration?.language }
      );

      await getTwitterImages(entity, twitterImagesData);
    }, message.tenant);
  };

  private saveHashtags = async (
    twitterIntegration: TwitterIntegrationSettingsType,
    hashtags: string[]
  ) => {
    const templateHashtag = await this.getHashtagsTemplate(twitterIntegration);

    const tweetHashtags = [];
    // eslint-disable-next-line no-restricted-syntax
    for (const hashtag of hashtags) {
      // eslint-disable-next-line no-await-in-loop
      const hashtagEntities = await entities.getUnrestricted({
        title: hashtag.toString(),
        template: templateHashtag._id,
      });

      if (hashtagEntities.length) {
        tweetHashtags.push({
          value: hashtagEntities[0].sharedId,
          type: 'entity',
          icon: null,
        });
      } else {
        // eslint-disable-next-line no-await-in-loop
        const savedEntity = await entities.save(
          {
            title: hashtag.toString(),
            template: templateHashtag._id,
          },
          { user: {}, language: twitterIntegration.language }
        );
        tweetHashtags.push({
          value: savedEntity.sharedId,
          type: 'entity',
          icon: null,
        });
      }
    }
    return tweetHashtags;
  };

  private getTemplateTweets = async (twitterIntegration: TwitterIntegrationSettingsType) => {
    const templatesTweet = await templates.get({ name: twitterIntegration.tweetsTemplateName });

    if (templatesTweet.length) {
      return templatesTweet[0];
    }

    const hashtagsTemplate = await this.getHashtagsTemplate(twitterIntegration);

    const relationsType = await relationtypes.get({ name: 'Twitter query' });

    const relationType = relationsType.length
      ? relationsType[0]
      : await relationtypes.save({ name: 'Twitter query' });

    return templates.save(
      {
        name: twitterIntegration.tweetsTemplateName,
        commonProperties: [
          { name: 'title', label: 'Title', type: 'text' },
          { name: 'creationDate', label: 'Date added', type: 'date' },
          { name: 'editDate', label: 'Date modified', type: 'date' },
        ],
        properties: [
          { name: 'tweet_text', label: 'Tweet text', type: 'markdown', showInCard: true },
          { name: 'tweet_source', label: 'Tweet source', type: 'link', showInCard: true },
          { name: 'tweet_author', label: 'Tweet author', type: 'link', showInCard: true },
          { name: 'tweet_date', label: 'Tweet date', type: 'date' },
          {
            name: 'tweet_hashtags',
            label: 'Tweet hashtags',
            type: 'relationship',
            relationType: relationType._id.toString(),
            content: hashtagsTemplate._id.toString(),
            filter: true,
          },
        ],
      },
      twitterIntegration.language
    );
  };

  getHashtagsTemplate = async (twitterIntegration: TwitterIntegrationSettingsType) => {
    const templatesHashtag = await templates.get({
      name: twitterIntegration.hashtagsTemplateName,
    });

    return templatesHashtag.length
      ? templatesHashtag[0]
      : templates.save(
          {
            name: twitterIntegration.hashtagsTemplateName,
            commonProperties: [{ name: 'title', label: 'Title', type: 'text' }],
          },
          twitterIntegration.language
        );
  };
}

export { TwitterIntegration };
export type { TweetParamsType, TwitterIntegrationSettingsType };