if-follow-package Source

if-follow-package is a JavaScript library that provides APIs for managing followers and followings on GitHub. It acts as Github plugin for follow control.

// Importing required modules
const axios = require('axios');
require('dotenv').config();

// Retrieving GitHub credentials from environment variables
const { USER: username, TOKEN: token } = process.env;

/**
 * Module for managing followers and followings on GitHub.
 *
 * @typedef {Object} IfFollow
 * @property {Function} isFollower - Checks if a user is following the authenticated user.
 * @property {Function} isFollowing - Checks if the authenticated user is following a given user.
 * @property {Function} totalFollowers - Retrieves the total number of followers for the authenticated user.
 * @property {Function} totalFollowings - Retrieves the total number of users that the authenticated user is following.
 * @property {Function} whoNotFollowingBack - Retrieves users that the authenticated user is not following back.
 * @property {Function} whoFollowingBack - Retrieves users that the authenticated user is following back.
 * @property {Function} isFollowingBack - Checks if a user is following the authenticated user back.
 * @property {Function} unfollowNotFollowingBack - Unfollows a user who is not following back.
 * @property {Function} unfollowAllNotFollowingBack - Unfollows all users who are not following back.
 */

/**
 * Creates an instance of the IfFollow module.
 *
 * @function
 * @param {string} yourUsername - Your GitHub username.
 * @param {string} yourToken - Your GitHub personal access token.
 * @returns {IfFollow} An object containing functions to interact with followers and followings.
 */
function ifFollow(yourUsername = username, yourToken = token) {
  /**
   * Retrieves all followers of the specified user.
   * @private
   * @returns {Promise<Array<string>>} An array of usernames of followers.
   */
  async function getAllFollowers() {
    const followers = [];
    let page = 1;

    // Fetch followers in paginated manner
    while (true) {
      try {
        const response = await axios.get(
          `https://api.github.com/users/${yourUsername}/followers`,
          {
            params: { page, per_page: 100 },
            auth: {
              username: yourUsername,
              password: yourToken,
            },
          },
        );

        const data = response.data;

        // Break if no more followers or data
        if (!data || data.length === 0) {
          break;
        }

        // Extract and store usernames of followers
        followers.push(...data.map(({ login }) => login));
        page++;
      } catch (error) {
        // Handle Axios errors
        handleAxiosError(error);
        return [];
      }
    }

    return followers;
  }

  /**
   * Retrieves all users that the specified user is following.
   * @private
   * @returns {Promise<Array<string>>} An array of usernames of users being followed.
   */
  async function getAllFollowing() {
    const following = [];
    let page = 1;

    // Fetch following in paginated manner
    while (true) {
      try {
        const response = await axios.get(
          `https://api.github.com/users/${yourUsername}/following`,
          {
            params: { page, per_page: 100 },
            auth: {
              username: yourUsername,
              password: yourToken,
            },
          },
        );

        const data = response.data;

        // Break if no more following or data
        if (!data || data.length === 0) {
          break;
        }

        // Extract and store usernames of following
        following.push(...data.map(({ login }) => login));
        page++;
      } catch (error) {
        // Handle Axios errors
        handleAxiosError(error);
        return [];
      }
    }

    return following;
  }

  /**
   * Handles errors from Axios requests.
   * @param {Error} error - The error object.
   */
  function handleAxiosError(error) {
    if (error.response) {
      // The request was made, but the server responded with an error
      console.error(
        `API Error: ${error.response.status} - ${error.response.data.message}`,
      );
    } else if (error.request) {
      // The request was made, but no response was received
      console.error('No response received from the server');
    } else {
      // Something happened in setting up the request that triggered an Error
      console.error(`Error: ${error.message}`);
    }
  }

  return {
    /**
     * Checks if a user is following the authenticated user.
     * @param {string} username - The username to check.
     * @returns {Promise<string>} A message indicating if the user follows or not.
     */
    async isFollower(username) {
      const followers = await getAllFollowers();
      const message = followers.includes(username)
        ? `Yes, ${username} follows you!`
        : `No, ${username} does not follow you!`;
      console.log(message);
      return message;
    },

    /**
     * Checks if the authenticated user is following a given user.
     * @param {string} username - The username to check.
     * @returns {Promise<string>} A message indicating if the user is followed or not.
     */
    async isFollowing(username) {
      const following = await getAllFollowing();
      const message = following.includes(username)
        ? `Yes, you follow ${username}!`
        : `No, you do not follow ${username}!`;
      console.log(message);
      return message;
    },

    /**
     * Retrieves the total number of followers for the authenticated user.
     * @returns {Promise<number>} The total number of followers.
     */
    async totalFollowers() {
      const followers = await getAllFollowers();
      console.log(`Your total Followers: ${followers.length}`);
      return followers.length;
    },

    /**
     * Retrieves the total number of users that the authenticated user is following.
     * @returns {Promise<number>} The total number of followings.
     */
    async totalFollowings() {
      const following = await getAllFollowing();
      console.log(`Your total Followings: ${following.length}`);
      return following.length;
    },

    /**
     * Retrieves users that the authenticated user is not following back.
     * @returns {Promise<Array<string>>} An array of usernames not followed back.
     */
    async whoNotFollowingBack() {
      const followers = await getAllFollowers();
      const following = await getAllFollowing();
      const notFollowingBack = following.filter(
        (user) => !followers.includes(user),
      );
      return notFollowingBack;
    },

    /**
     * Retrieves users that the authenticated user is following back.
     * @returns {Promise<Array<string>>} An array of usernames being followed back.
     */
    async whoFollowingBack() {
      const followers = await getAllFollowers();
      const following = await getAllFollowing();
      const followingBacks = following.filter((user) =>
        followers.includes(user),
      );
      return followingBacks;
    },

    /**
     * Checks if a user is following the authenticated user back.
     * @param {string} username - The username to check.
     * @returns {Promise<string>} A message indicating if the user is following back or not.
     */
    async isFollowingBack(username) {
      const followingBacks = await this.whoFollowingBack();
      const message = followingBacks.includes(username)
        ? `Yes, ${username} following back!`
        : `No, ${username} does not following back!`;
      console.log(message);
      return message;
    },

    /**
     * Unfollows a user who is not following back.
     *
     * @async
     * @param {string} username - The username of the user to unfollow if not following back.
     * @returns {Promise<void>} A promise that resolves once the user is unfollowed.
     */
    async unfollowNotFollowingBack(username) {
      // Get the list of users who are not following back
      const notFollowingBack = await this.whoNotFollowingBack();

      // Check if the specified user is in the list of users not following back
      if (notFollowingBack.includes(username)) {
        try {
          // Attempt to unfollow the user using GitHub API
          await axios.delete(
            `https://api.github.com/user/following/${username}`,
            {
              headers: {
                Authorization: `token ${yourToken}`, // Include the personal access token for authentication
              },
            },
          );
          console.log(`Unfollowed: ${username}`);
        } catch (error) {
          // Handle errors during unfollowing
          console.error(`Failed to unfollow: ${username}`);
        }
      } else {
        console.log(
          `Sorry, ${username} is not a user who is not following you back`,
        );
      }
    },

    /**
     * Unfollows all users who are not following back.
     *
     * @async
     * @returns {Promise<void>} A promise that resolves once all users are unfollowed.
     */
    async unfollowAllNotFollowingBack() {
      // Get the list of users who are not following back
      const notFollowingBack = await this.whoNotFollowingBack();

      // Iterate through each user and attempt to unfollow using GitHub API
      for (const user of notFollowingBack) {
        try {
          await axios.delete(`https://api.github.com/user/following/${user}`, {
            headers: {
              Authorization: `token ${yourToken}`, // Include the personal access token for authentication
            },
          });
          console.log(`Unfollowed: ${user}`);
        } catch (error) {
          // Handle errors during unfollowing
          console.error(`Failed to unfollow: ${user}`);
        }
      }
    },
  };
}

/**
 * Exports the ifFollow function.
 * @module
 * @default
 * @function ifFollow
 * @returns {IfFollow} An object containing functions to interact with followers and followings.
 */
module.exports = ifFollow;