Communities.js

import PagingQuery from './models/PagingQuery';
import PagingResult from './models/PagingResult';
import UserId from './models/UserId';
import UserIdList from './models/UserIdList';
import User from './models/communities/User';
import FriendsQuery from './models/communities/FriendsQuery';
import UsersQuery from './models/communities/UsersQuery';
import FollowQuery from './models/communities/FollowQuery';
import FollowersQuery from './models/communities/FollowersQuery';
import Activity from './models/communities/Activity';
import ActivityContent from './models/communities/ActivityContent';
import SuggestedFriend from './models/communities/SuggestedFriend';
import AnnouncementsQuery from './models/communities/AnnouncementsQuery';
import ActivitiesQuery from './models/communities/ActivitiesQuery';
import PostActivityTarget from './models/communities/PostActivityTarget';
import CommunitiesEntityType from './models/communities/CommunitiesEntityType';
import ReactionsQuery from './models/communities/ReactionsQuery';
import VotesQuery from './models/communities/VotesQuery';
import UserVotes from './models/communities/UserVotes';
import RemoveActivitiesQuery from './models/communities/RemoveActivitiesQuery';
import TagsQuery from './models/communities/TagsQuery';
import Topic from './models/communities/Topic';
import TopicsQuery from './models/communities/TopicsQuery';
import Group from './models/communities/Group';
import GroupContent from './models/communities/GroupContent';
import GroupsQuery from './models/communities/GroupsQuery';
import UserReactions from './models/communities/UserReactions';
import AddGroupMembersQuery from './models/communities/AddGroupMembersQuery';
import UpdateGroupMembersQuery from './models/communities/UpdateGroupMembersQuery';
import GroupMember from './models/communities/GroupMember';
import JoinGroupQuery from './models/communities/JoinGroupQuery';
import RemoveGroupMembersQuery from './models/communities/RemoveGroupMembersQuery';
import MembersQuery from './models/communities/MembersQuery';
import Membership from './models/communities/Membership';
import ChatMessageContent from './models/communities/ChatMessageContent';
import ChatId from './models/communities/ChatId';
import ChatMessage from './models/communities/ChatMessage';
import ChatMessagesQuery from './models/communities/ChatMessagesQuery';
import Chat from './models/communities/Chat';
import Tag from './models/communities/Tag';
import Label from './models/communities/Label';
import LabelsQuery from './models/communities/LabelsQuery';
import SearchQuery from './models/communities/SearchQuery';
import SearchResult from './models/communities/SearchResult';
import ChatMessagesPagingQuery from './models/communities/ChatMessagesPagingQuery';
import ChatMessagesPagingResult from './models/communities/ChatMessagesPagingResult';
import SDK from './sdk';
import { uploadAttachment } from './utils';

import "./thrift/usermanagement_types";
import "./thrift/common_types";
import "./thrift/socialgraph_types";
import "./thrift/groups_types";
import "./thrift/chats_types";
import "./thrift/activityfeed_types";
import "./thrift/hades_types";

/**
 * @class Communities
 * @hideconstructor
 */
export default class Communities {
    /**
     * Add users to a friend list
     * If operation succeed - user become friends with everyone in the list.
     * If some or all users are already in the friends list, they will be ignored.
     *
     * @memberOf Communities
     * @param {UserIdList} userIdList - User identifiers you want to become friend with.
     * @return {Promise<number>} Promise with updated number of friends.
     */
    static addFriends(userIdList) {
        return SDK.request(
            'addFriends',
            new AddFriendsRequest({ userIds: userIdList.ids })
        );
    }

    /**
     * Remove users from a friends list.
     * If some or all users are not in the friends list, they will be ignored.
     *
     * @memberOf Communities
     * @param {UserIdList} userIdList - Users identifiers you don't want to be friends anymore.
     * @return {Promise<number>} Promise with updated number of friends.
     */
    static removeFriends(userIdList) {
        return SDK.request(
            'removeFriends',
            new RemoveFriendsRequest({ userIds: userIdList.ids })
        );
    }

    /**
     * Check if users are your friends.
     *
     * @memberOf Communities
     * @param {UserIdList} userIdList - Users identifiers.
     * @return {Promise<string, boolean>} Promise of a map of booleans, where key is user ID and value is a boolean
     *                indicating if the user is in the friends list or not.
     */
    static areFriends(userIdList) {
        return SDK.request(
            'areFriends',
            new AreFriendsRequest({ userIds: userIdList.ids })
        );
    }

    /**
     * Check if a user is your friend.
     *
     * @memberOf Communities
     * @param {UserId} id - Unique user identifier.
     * @return {Promise<boolean>} Promis with boolean: true, if user is your friend, false otherwise
     */
    static isFriend(id) {
        return SDK.request('isFriend', id);
    }

    /**
     * Get number of friends.
     *
     * @memberOf Communities
     * @param {FriendsQuery} query - Query.
     * @return {Promise<number>} Promiswe with friends count.
     */
    static getFriendsCount(query) {
        return SDK.request('getFriendsV2', new GetFriendsRequest({
            userId: query && query.userId ? query.userId.id : null
        }))
            .then((res) => res && res.totalNumber);
    }

    /**
     * Get a list of friends for current user.
     *
     * @memberOf Communities
     * @param {PagingQuery<FriendsQuery>} pagingQuery - Pagination query.
     * @return {Promise<PagingResult<User[]>>} Promise with list of users.
     */
    static getFriends({ query, ...pagination}) {
        return SDK.request('getFriendsV2', new GetFriendsRequest({
            pagination: new Pagination(pagination),
            userId: query.userId.id || null
        }))
            .then((res) => {
                return new PagingResult(res.users, res.nextCursor);
            });
    }

    /**
     * Get a list of suggested friends for current user.
     *
     * @memberOf Communities
     * @param {PagingQuery} pagingQuery - Pagination query.
     * @return {Promise<PagingResult<SuggestedFriend[]>>} Promise with list of users.
     */
    static getSuggestedFriends({ _query, ...pagination }) {
        const request = new GetSuggestedFriendsRequest({
            pagination: new Pagination(pagination)
        });
        return SDK.request('getSuggestedFriendsV2', request)
            .then((res) => {
                res.users = res.users.map((suggestedFriend) => {
                    return new SuggestedFriend({
                        ...suggestedFriend.user,
                        mutualFriendsCount: suggestedFriend.mutualFriends
                    });
                });

                return new PagingResult(res.users, res.nextCursor);
            });
    }

    /**
     * Replace existing friends with the provided list of users.
     *
     * @memberOf Communities
     * @param {UserIdList} userIdList - List of GetSocial user identifiers.
     * @return {Promise<number>} Promise with number of users set.
     */
    static setFriends(userIdList) {
        return SDK.request('setFriends', userIdList.ids);
    }

    /**
     * Get users matching query
     *
     * @memberOf Communities
     * @param {PagingQuery<UsersQuery>} pagingQuery - Pagination query.
     * @return {PagingResult<User>} Promise with list of users.
     */
    static getUsers({ query, ...pagination }) {
        const request = new GetUsersRequest({
            followedByUserId: query.userId
                ? query.userId.id
                : null,
            searchTerm: query.searchTerm,
            pagination: new Pagination(pagination),
            suggested: query.suggested
        });

        return SDK.request('getUsers', request)
            .then((res) => {
                return new PagingResult(
                    res.users && res.users.length
                        ? res.users.map((user) => User.create(user))
                        : null,
                    res.nextCursor);
            });
    }

    /**
     * Get users by their identifiers.
     *
     * @memberOf Communities
     * @param {UserIdList} userIdList - List of GetSocial user identifiers.
     * @return {Promise<Object<string, User>>} Promise with a map of users, where key is user ID.
     */
    static getUsersByIds(userIdList) {
        return SDK.request('getUsersById', new GetUsersRequestById({
            userIds: userIdList.ids
        }))
            .then((res) => {
                Object.keys(res.users || {}).forEach((user, id) => {
                    res.users[id] = User.create(user);
                });

                return res.users;
            });
    }

    /**
     * Get a single user by his identifier. Returns an error if user with this ID doesn't exist.
     *
     * @memberOf Communities
     * @param {UserId} userId - Single user identifier.
     * @return {Promise<User>} Promise with a user object.
     */
    static getUser(userId) {
        return Communities.getUsersByIds(new UserIdList({ ids: [userId.id] }))
            .then((users) => {
                return users[id];
            });
    }

    /**
     * Get total count of users matching provided query.
     *
     * @memberOf Communities
     * @param {UsersQuery} query - Users query.
     * @return {Promise<number>} Promise with the number of users matching the query.
     */
    static getUsersCount(query) {
        return SDK.request('getUsers', new GetUsersRequest({
            followedByUserId: query.userId.id,
            searchTerm: query.searchTerm,
            suggested: query.suggested
        }))
            .then((res) => {
                return res.totalNumber;
            });
    }

    /**
     * Follow users or topics.
     * If follow users, returns a number of users you are following now.
     * If follow topics, returns a number of topics you are following now.
     *
     * @memberOf Communities
     * @param {FollowQuery} query - Topics or users to be followed.
     * @return {Promise<number>} Promise with total number of users/topics you follow after the call.
     */
    static follow(query) {
        return SDK.request('followEntities', new FollowEntitiesRequest({
            entityType: query.ids.type,
            entityIds: query.ids.ids
        }))
            .then((res) => {
                return res.totalFollowedEntitiesCount;
            });
    }

    /**
     * Unfollow users or topics.
     * If unfollow users, returns a number of users you are following now.
     * If unfollow topics, returns a number of topics you are following now.
     *
     * @memberOf Communities
     * @param {FollowQuery} query - Topics or users to be followed.
     * @return {Promise<number>} Promise with the total number of users/topics you follow after the call.
     */
    static unfollow(query) {
        const request = new UnfollowEntitiesRequest({
            entityType: query.ids.type,
            entityIds: query.ids.ids
        });

        return SDK.request('unfollowEntities', request)
            .then((res) => {
                return res.totalFollowedEntitiesCount;
            });
    }

    /**
     * Check if user with given ID is following a list of entities.
     * Returns a map, where key is an id of entity. Value indicated if user follows the entity for that key.
     *
     * @memberOf Communities
     * @param {UserId} userId - ID of user to check if he follows certain entities.
     * @param {FollowQuery} query - List of entities to check.
     * @return {Promise<Object<string, boolean>>} Promise with a map of results.
     */
    static isFollowing(userId, query) {
        return SDK.request('isFollowing', new IsFollowingRequest({
            userId: userId.id,
            entityType: query.ids.type,
            entityIds: query.ids.ids
        }))
            .then((res) => {
                return res.result;
            });
    }

    /**
     * Get users who follow certain entity. Returns a list of followers and a cursor for a next query.
     * If cursor is empty string, or {@link PagingResult#isLastPage()} is true, this is the last page.
     *
     * @memberOf Communities
     * @param {PagingQuery<FollowersQuery>} pagingQuery - Followers query and pagination details.
     * @return {Promise<PagingResult<User[]>>} Promise with list of followers and a cursor for the next request.
     */
    static getFollowers({ query, ...pagination}) {
        const request = new GetEntityFollowersRequest({
            id: new SGEntity({
                id: query.ids.ids[0],
                entityType: query.ids.type
            }),
            searchTerm: query.searchTerm,
            pagination: new Pagination(pagination)
        });

        return SDK.request('getEntityFollowers', request)
            .then((res) => {
                return new PagingResult(res.followers, res.nextCursor);
            });
    }

    /**
     * Get total count of users who follows certain entity.
     *
     * @memberOf Communities
     * @param {FollowersQuery} query - Followers query.
     * @return {Promise<number>} Promise with number of users who follows certain entity.
     */
    static getFollowersCount(query) {
        const request = new GetEntityFollowersRequest({
            id: new SGEntity({
                id: query.ids.ids[0],
                entityType: query.ids.type
            }),
            searchTerm: query.searchTerm
        });

        return SDK.request('getEntityFollowers', request)
            .then((res) => {
                return res.totalNumber;
            });
    }

    /**
     * Get announcements matching query. Returns a list of announcements.
     *
     * @memberOf Communities
     * @param {AnnouncementsQuery} query - Announcements query and pagination details.
     * @return {Promise<Activity[]>} Promise with  list of announcements.
     */
    static getAnnouncements(query) {
        const request = new GetAnnouncementsRequest({
            target: new SGEntity({
                id: query.ids.ids[0],
                entityType: query.ids.type
            }),
            withPolls: query.pollStatus
        });

        return SDK.request('getAnnouncements', request)
            .then((res) => {
                return res.data.map((announcement) => {
                    return Activity.create({
                        ...announcement.activity,
                        author: announcement.activity.author.isApp
                            ? User.appUserData()
                            : announcement.activity.author.publicUser
                    });
                });
            });
    }

    /**
     * Get activities matching query. Returns a list of activities and a cursor for a next query.
     * If cursor is empty string, or {@link PagingResult#isLastPage()} is true, this is a last page.
     *
     * @memberOf Communities
     * @param {PagingQuery<ActivitiesQuery>} pagingQuery - Activities query and pagination details.
     * @return {Promise<PagingResult<Activity>>} Promise with list of activities and a cursor for a next query.
     */
    static getActivities({ query, ...pagination }) {
        const request = new GetActivitiesV2Request({
            pagination: new Pagination(pagination),
            target: query.ids
                ? new SGEntity({
                    id: query.ids.ids[0],
                    entityType: query.ids.type
                })
                : null,
            author: query.author
                ? query.author.toString()
                : null,
            tags: query.tag
                ? [query.tag]
                : null,
            isTrending: query.trending || false,
            withPolls: query.pollStatus,
            labels: query.labels,
            mentions: query.mentions
                ? query.mentions.map((id) => id.toString())
                : null,
            properties: query.properties,
            searchTerm: query.searchTerm,
            reactionGroup: query.reactionGroup,
            reactions: query.reactions
        });

        return SDK.request('getActivitiesV2', request)
            .then((res) => {
                return new PagingResult(
                    res.data.map((item) => {
                        // Try to get source from entityDetails list
                        const sourceId = item.source.id;
                        const source = (res.entityDetails &&
                            res.entityDetails
                                .filter((e) => {
                                    return e.id.id === sourceId.id &&
                                        e.id.entityType === sourceId.entityType;
                                }).pop()) ||
                            item.source;

                        return Activity.create({
                            ...item,
                            author: item.author.isApp
                                ? User.appUserData()
                                : (item.author.publicUser &&
                                res.authors[item.author.publicUser.id]) ||
                                item.author.publicUser,
                            source
                        })
                    }),
                    res.nextCursor
                );
            });
    }

    /**
     * Get activity by ID
     *
     * @memberOf Communities
     * @param {string} id - Activity ID.
     * @return {Promise<Activity>} Promise with activity.
     */
    static getActivity(id) {
        const request = new GetActivityByIDRequest({
            activityId: id
        });

        return SDK.request('getActivityByID', request)
            .then((res) => {
                return Activity.create({
                    ...res.activity,
                    author: res.activity.author.isApp
                        ? User.appUserData()
                        : res.activity.author.publicUser
                });
            });
    }

    /**
     * Post activity to a specific target.
     *
     * @memberOf Communities
     * @param {ActivityContent} content - Activity content.
     * @param {PostActivityTarget} target - Target where to post the activity.
     * @return {Promise<Activity>} Promise with activity, if posted successfully.
     */
    static postActivity(content, target) {
        const lang = SDK.getLanguage();
        const poll = content.poll;
        let promises = [];

        if (content.attachments &&
            content.attachments.length) {
            content.attachments.forEach((attachment) => {
                promises.push(uploadAttachment(attachment, 'ACTIVITY_FEED'));
            });
        }

        if (poll &&
            poll.options &&
            poll.options.length) {
            poll.options.forEach((option) => {
                if (option.attachment) {
                    promises.push(
                        uploadAttachment(option.attachment, 'ACTIVITY_FEED')
                    );
                }
            });
        }

        return Promise.all(promises).then(() => {
            const request = new CreateActivityRequest({
                content: {
                    [lang]: {
                        text: content.text,
                        attachments: content.attachments,
                        button: content.button
                            ? {
                                buttonTitle: content.button.title,
                                action: content.button.action
                            }
                            : null
                    }
                },
                properties: content.properties,
                labels: content.labels,
                target: new SGEntity({
                    id: target.getTargetId(),
                    entityType: target.getType()
                }),
                poll: poll
                    ? new AFPollContent({
                        allowMultiVotes: poll.allowMultipleVotes,
                        endsAt: poll.endDate,
                        pollOptions: poll.options.map((option) => {
                            return new AFPollOption({
                                id: option.optionId || null,
                                content: {
                                    [lang]: new AFPollOptionContent({
                                        text: option.text,
                                        attachment: option.attachment || null
                                    })
                                }
                            });
                        })
                    })
                    : null
            });

            return SDK.request('createActivity', request)
                .then((res) => {
                    return Activity.create({
                        ...res.activity,
                        author: res.activity.author.publicUser
                            ? res.activity.author.publicUser
                            : res.activity.author
                    });
                });
        })
    }

    /**
     * Update activity with a new content. Activity is entire replaced.
     *
     * @memberOf Communities
     * @param {string} id - Activity ID.
     * @param {ActivityContent} content - Activity content.
     * @return {Promise<Activity>} Promise with activity, if updated successfully.
     */
    static updateActivity(id, content) {
        const lang = SDK.getLanguage();
        const poll = content.poll;
        let promises = [];

        if (content.attachments &&
            content.attachments.length) {
            content.attachments.forEach((attachment) => {
                promises.push(uploadAttachment(attachment, 'ACTIVITY_FEED'));
            });
        }

        if (poll &&
            poll.options &&
            poll.options.length) {
            poll.options.forEach((option) => {
                if (option.attachment) {
                    promises.push(
                        uploadAttachment(option.attachment, 'ACTIVITY_FEED')
                    );
                }
            });
        }

        return Promise.all(promises).then(() => {
            const request = new UpdateActivityRequest({
                activityId: id,
                content: {
                    [lang]: {
                        text: content.text,
                        attachments: content.attachments,
                        button: content.button
                            ? {
                                buttonTitle: content.button.title,
                                action: content.button.action
                            }
                            : null
                    }
                },
                properties: content.properties,
                labels: content.labels,
                poll: poll
                    ? new AFPollContent({
                        allowMultiVotes: poll.allowMultipleVotes,
                        endsAt: poll.endDate,
                        pollOptions: poll.options.map((option) => {
                            return new AFPollOption({
                                id: option.optionId || null,
                                content: {
                                    [lang]: new AFPollOptionContent({
                                        text: option.text,
                                        attachment: option.attachment || null
                                    })
                                }
                            });
                        })
                    })
                    : null
            });

            return SDK.request('updateActivity', request)
                .then((res) => {
                    return Activity.create({
                        ...res.activity,
                        author: res.activity.author.publicUser
                            ? res.activity.author.publicUser
                            : res.activity.author
                    });
                });
        });
    }

    /**
     * Set reaction to the activity.
     * Existing reactions will be removed.
     *
     * @memberOf Communities
     * @param {string} reaction - Reaction type.
     * @param {string} activityId - Activity ID.
     * @return {Promise<void>} Promise.
     */
    static setReaction(reaction, activityId) {
        const request = new CreateReactionRequest({
            reaction,
            id: new SGEntity({
                id: activityId,
                entityType: CommunitiesEntityType.Activity
            }),
            keepExisting: false
        });

        return SDK.request('createReaction', request);
    }

    /**
     * Add reaction to the activity. If this reaction was already added, success is called.
     * Existing reactions will be kept.
     *
     * @memberOf Communities
     * @param {string} reaction - Reaction type.
     * @param {string} activityId - Activity ID.
     * @return {Promise<void>} Promise.
     */
    static addReaction(reaction, activityId) {
        const request = new CreateReactionRequest({
            reaction,
            id: new SGEntity({
                id: activityId,
                entityType: CommunitiesEntityType.Activity
            }),
            keepExisting: true
        });

        return SDK.request('createReaction', request);
    }

    /**
     * Remove reaction from the activity
     *
     * @memberOf Communities
     * @param {string} reaction - Reaction type.
     * @param {string} activityId - Activity ID.
     * @return {Promise<void>} Promise.
     */
    static removeReaction(reaction, activityId) {
        const request = new DeleteReactionRequest({
            reaction,
            id: new SGEntity({
                id: activityId,
                entityType: CommunitiesEntityType.Activity
            })
        });

        return SDK.request('deleteReaction', request);
    }

    /**
     * Get reactions matching query.
     * If cursor is empty string, or {@link PagingResult#isLastPage()} is true, this is a last page.
     *
     * @memberOf Communities
     * @param {PagingQuery<ReactionsQuery>} pagingQuery - Reactions query and pagination details.
     * @return {Promise<PagingResult<UserReactions>>} Promise with list of reactions and a cursor for a next query.
     */
    static getReactions({ query, ...pagination }) {
        const request = new GetReactionsRequest({
            pagination: new Pagination(pagination),
            target: new SGEntity({
                id: query.ids.ids[0],
                entityType: query.ids.type
            }),
            reaction: query.reaction
        });

        return SDK.request('getReactions', request)
            .then((res) => {
                return new PagingResult(
                    res.reactions
                        ? res.reactions.map((reaction) => {
                            return new UserReactions({
                                user: reaction.creator.isApp
                                    ? User.appUserData()
                                    : {
                                        ...reaction.creator.publicUser,
                                        isVerified: reaction.creator.isVerified
                                    },
                                reactions: reaction.reactions
                            });
                        })
                        : [],
                    res.nextCursor
                );
            });
    }

    /**
     * Set votes to the activity.
     * Existing votes will be removed.
     *
     * @memberOf Communities
     * @param {string[]} pollOptionIds - Poll option IDs.
     * @param {string} activityId - Activity ID.
     * @return {Promise<void>} Promise.
     */
    static setVotes(pollOptionIds, activityId) {
        const request = new CreateVoteRequest({
            target: new SGEntity({
                id: activityId,
                entityType: CommunitiesEntityType.Activity
            }),
            optionIds: pollOptionIds,
            keepExisting: false
        });

        return SDK.request('createVote', request);
    }

    /**
     * Add votes to the activity.
     * Existing votes will be kept.
     *
     * @memberOf Communities
     * @param {string[]} pollOptionIds - Poll option IDs.
     * @param {string} activityId - Activity ID.
     * @return {Promise<void>} Promise.
     */
    static addVotes(pollOptionIds, activityId) {
        const request = new CreateVoteRequest({
            target: new SGEntity({
                id: activityId,
                entityType: CommunitiesEntityType.Activity
            }),
            optionIds: pollOptionIds,
            keepExisting: true
        });

        return SDK.request('createVote', request);
    }

    /**
     * Remove votes from the activity.
     *
     * @memberOf Communities
     * @param {string[]} pollOptionIds - Poll option IDs.
     * @param {string} activityId - Activity ID.
     * @return {Promise<void>} Promise.
     */
    static removeVotes(pollOptionIds, activityId) {
        const request = new DeleteVoteRequest({
            target: new SGEntity({
                id: activityId,
                entityType: CommunitiesEntityType.Activity
            }),
            optionIds: pollOptionIds
        });

        return SDK.request('deleteVote', request);
    }

    /**
     * Bookmark an activity
     *
     * @memberOf Communities
     * @param {string} activityId - Acitivity ID
     * @returns {Promise<void>} Promise
     */
    static bookmark(activityId) {
        const request = new CreateBookmarkRequest({
            target: new SGEntity({
                id: activityId,
                entityType: CommunitiesEntityType.Activity
            })
        });

        return SDK.request('createBookmark', request);
    }

    /**
     * Remove a bookmark from an activity
     *
     * @memberOf Communities
     * @param {string} activityId - Acitivity ID
     * @returns {Promise<void>} Promise
     */
     static removeBookmark(activityId) {
        const request = new DeleteBookmarkRequest({
            target: new SGEntity({
                id: activityId,
                entityType: CommunitiesEntityType.Activity
            })
        });

        return SDK.request('deleteBookmark', request);
    }

    /**
     * Get votes matching query. Returns votes and a cursor for the next query.
     * If cursor is empty string, or {@link PagingResult#isLastPage()} is true, this is a last page.
     *
     * @memberOf Communities
     * @param {PagingQuery<VotesQuery>} pagingQuery - Votes query and pagination details.
     * @return {Promise<PagingResult<UserVotes>>} Promise with votes and a cursor for a next query.
     */
    static getVotes({ query, ...pagination }) {
        const request = new GetVotesRequest({
            pagination: new Pagination(pagination),
            target: new SGEntity({
                id: query.ids.ids[0],
                entityType: query.ids.type
            }),
            optionId: query.pollOptionId
        });

        return SDK.request('getVotes', request)
            .then((res) => {
                return new PagingResult(
                    res.votes
                        ? res.votes.map((vote) => {
                            return new UserVotes({
                                user: vote.creator.isApp
                                    ? User.appUserData()
                                    : {
                                        ...vote.creator.publicUser,
                                        isVerified: vote.creator.isVerified
                                    },
                                votes: vote.optionIds
                            });
                        })
                        : null,
                    res.nextCursor
                );
            });
    }

    /**
     * Report activity with a specified reason.
     *
     * @memberOf Communities
     * @param {string} id - Activity ID.
     * @param {number} reason - Reason of reporting. One of {@link ReportingReason}
     * @param {string} [explanation] - Could be arbitrary string.
     * @return {Promise<void>} Promise.
     */
     static reportActivity(id, reason, explanation) {
        const request = new ReportEntityV2Request({
            reason,
            explanation,
            id: new SGEntity({
                id,
                entityType: CommunitiesEntityType.Activity
            })
        });

        return SDK.request('reportEntityV2', request);
    }

    /**
     * Remove all activities matching query.
     *
     * @memberOf Communities
     * @param {RemoveActivitiesQuery} query - Activities to be removed.
     * @return {Promise<void>} Promise.
     */
    static removeActivities(query) {
        return SDK.request('removeActivities', query.ids);
    }

    /**
     * Get tags matching query. Returns a list of tags.
     *
     * @memberOf Communities
     * @memberOf Communities
     * @param {PagingQuery<TagsQuery>} pagingQuery - Tags query and pagination details.
     * @return {Promise<PagingResult<Tag[]>>} Promise with list of tags and next cursor.
     */
    static getTags({ query, ...pagination }) {
        const request = new GetHashtagsRequest({
            searchTerm: query.search,
            followedByUserId: query.follower
                ? query.follower.id
                : null,
            isTrending: query.trending,
            pagination: new Pagination(pagination),
            target: query.target && query.target.ids
                ? new SGEntity({
                    id: query.target.ids.ids[0],
                    entityType: query.target.ids.type
                })
                : null
        });

        return SDK.request('getHashtags', request)
            .then((res) => {
                return new PagingResult(
                    res.hashtags.map((tag) => Tag.create(tag)),
                    res.nextCursor
                );
            });
    }

    /**
     * Get tags count for matching query. Returns amount of tags.
     *
     * @memberOf Communities
     * @param {TagsQuery} query - Tags query.
     * @return {Promise<number>} Promise with tags count.
     */
    static getTagsCount(query) {
        const request = new GetHashtagsRequest({
            searchTerm: query.search,
            followedByUserId: query.follower
                ? query.follower.id
                : null,
            isTrending: query.trending,
            target: query.target && query.target.ids
                ? new SGEntity({
                    id: query.target.ids.ids[0],
                    entityType: query.target.ids.type
                })
                : null
        });

        return SDK.request('getHashtags', request)
            .then((res) => {
                return res.totalNumber
            });
    }

    /**
     * Get labels matching query. Returns a list of labels.
     *
     * @memberOf Communities
     * @param {PagingQuery<LabelsQuery>} query - Labels query and pagination details.
     * @return {Promise<PagingResult<Label[]>>} Promise with list of labels and a cursor for a next query.
     */
    static getLabels({ query, ...pagination }) {
        const request = new GetLabelsRequest({
            searchTerm: query.search,
            followedByUserId: query.follower
                ? query.follower.id
                : null,
            isTrending: query.trending,
            pagination: new Pagination(pagination)
        });

        return SDK.request('getLabels', request)
            .then((res) => {
                return new PagingResult(
                    res.labels.map((label) => Label.create(label)),
                    res.nextCursor
                );
            });
    }

    /**
     * Get labels count for matching query. Returns amount of labels.
     *
     * @memberOf Communities
     * @param {LabelsQuery} query Labels query.
     * @return {Promise<number>} Called with labels count.
     */
    static getLabelsCount(query) {
        const request = new GetLabelsRequest({
            searchTerm: query.search,
            followedByUserId: query.follower
                ? query.follower.id
                : null,
            isTrending: query.trending
        });

        return SDK.request('getLabels', request)
            .then((res) => {
                return res.totalNumber;
            });
    }

    /**
     * Get topic by ID. If topic with given ID doesn't exist, return an error.
     *
     * @memberOf Communities
     * @param {string} id - Topic ID.
     * @return {Promise<Topic>} Promise with topic.
     */
    static getTopic(id) {
        const request = new GetTopicRequest({
            id
        });

        return SDK.request('getTopic', request)
            .then((res) => Topic.create(res.topic));
    }

    /**
     * Get topics matching query. Returns a list of topics and a cursor for a next query.
     * If cursor is empty string, or {@link PagingResult#isLastPage()} is true, this is a last page.
     *
     * @memberOf Communities
     * @param {PagingQuery<TopicsQuery>} pagingQuery - Topics query and pagination details.
     * @return {Promise<PagingResult<Topic[]>>} Promise with list of topics and a cursor for a next query.
     */
    static getTopics({ query, ...pagination }) {
        const request = new GetTopicsRequest({
            searchTerm: query.search,
            isTrending: query.trending,
            followedByUserId: query.userId
                ? query.userId.id
                : null,
            labels: query.labels,
            properties: query.properties,
            pagination: new Pagination(pagination)
        });

        return SDK.request('getTopics', request)
            .then((res) => {
                return new PagingResult(
                    res.topics.map((topic) => Topic.create(topic)),
                    res.nextCursor
                );
            });
    }

    /**
     * Get total count of topics matching provided query.
     *
     * @memberOf Communities
     * @param {TopicsQuery} query - Topics query.
     * @return {Promise<number>} Promise with a number of topics matching the query.
     */
    static getTopicsCount(query) {
        const request = new GetTopicsRequest({
            searchTerm: query.search,
            isTrending: query.trending,
            followedByUserId: query.userId
                ? query.userId.id
                : null,
            labels: query.labels,
            properties: query.properties
        });

        return SDK.request('getTopics', request)
            .then((res) => {
                return res.totalNumber;
            });
    }

    // Groups

    /**
     * Create a new group.
     *
     * @memberOf Communities
     * @param {GroupContent} groupContent - Group content.
     * @return {Promise<Group>} Promise with created group.
     */
    static createGroup(groupContent) {
        return uploadAttachment(groupContent.avatar || {}, 'ACTIVITY_FEED')
            .then(() => {
                const lang = SDK.getLanguage();
                const request = new CreateGroupRequest({
                    ...groupContent,
                    title: groupContent.title
                        ? {
                            [lang]: groupContent.title
                        }
                        : null,
                    groupDescription: groupContent.description
                        ? {
                            [lang]: groupContent.description
                        }
                        : null,
                    avatarUrl: groupContent.avatar.imageUrl || null
                });

                return SDK.request('createGroup', request)
                    .then((res) => Group.create(res.group));
            });
    }

    /**
     * Update an existing group.
     *
     * @memberOf Communities
     * @param {string} groupId - Group ID.
     * @param {GroupContent} groupContent - New Group content.
     * @return {Promise<Group>} Promise with updated group.
     */
    static updateGroup(groupId, groupContent) {
        return uploadAttachment(groupContent.avatar || {}, 'ACTIVITY_FEED')
            .then(() => {
                const lang = SDK.getLanguage();
                const request = new UpdateGroupRequest({
                    id: groupId,
                    ...groupContent,
                    title: {
                        [lang]: groupContent.title
                    },
                    groupDescription: groupContent.description
                    ? {
                        [lang]: groupContent.description
                    }
                    : null,
                    avatarUrl: groupContent.avatar.imageUrl
                });

                return SDK.request('updateGroup', request)
                    .then((res) => Group.create(res.group));
            });
    }

    /**
     * Remove groups.
     *
     * @memberOf Communities
     * @param {string[]} groupIds - Group IDs.
     * @return {Promise<void>} Promise.
     */
    static removeGroups(groupIds) {
        const request = new DeleteGroupsRequest({
            ids: groupIds
        });

        return SDK.request('deleteGroups', request);
    }

    /**
     * Get a single group.
     *
     * @memberOf Communities
     * @param {string} groupId - Group ID.
     * @return {Promise<Group>} Promise if groups are removed.
     */
    static getGroup(groupId) {
        const request = new GetGroupRequest({
            id: groupId
        });

        return SDK.request('getGroup', request)
            .then((res) => Group.create(res.group));
    }

    /**
     * Get groups based on query parameters.
     *
     * @memberOf Communities
     * @param {PagingQuery<GroupsQuery>} pagingQuery - Query parameter.
     * @return {PagingResult<Group[]>} Promise with groups and cursor for next query.
     */
    static getGroups({ query, ...pagination }) {
        const request = new GetGroupsRequest({
            searchTerm: query.search,
            isTrending: query.trending,
            followedByUserId: query.followerId
                ? query.followerId.id
                : null,
            memberUserId: query.memberId
                ? query.memberId.toString()
                : null,
            labels: query.labels,
            properties: query.properties,
            pagination: new Pagination(pagination)
        });

        return SDK.request('getGroups', request)
            .then((res) => {
                return new PagingResult(
                    res.groups.map((group) => Group.create(group)),
                    res.nextCursor
                );
            });
    }

    /**
     * Get number of groups based on query parameters.
     *
     * @memberOf Communities
     * @param {GroupsQuery} query - Groups Query.
     * @return {Promise<number>} Promsie with amount of groups.
     */
    static getGroupsCount(query) {
        const request = new GetGroupsRequest({
            searchTerm: query.search,
            isTrending: query.trending,
            followedByUserId: query.followerId
                ? query.followerId.id
                : null,
            memberUserId: query.memberId
                ? query.memberId.id
                : null,
            labels: query.labels,
            properties: query.properties
        });

        return SDK.request('getGroups', request)
            .then((res) => {
                return res.totalNumber;
            });
    }

    /**
     * Add group members to a group.
     *
     * @memberOf Communities
     * @param {AddGroupMembersQuery} query - Query parameter.
     * @return {Promise<GroupMember[]>} Promise with new group members.
     */
    static addGroupMembers(query) {
        const request = new UpdateGroupMembersRequest({
            groupId: query.internalQuery.groupId,
            userIds: query.internalQuery.userIdList.toStrings(),
            role: query.internalQuery.role,
            status: query.internalQuery.status
        });

        return SDK.request('updateGroupMembers', request)
            .then((res) => {
                return res.members.map((member) => new GroupMember(member));
            });
    }

    /**
     * Current user join to group.
     *
     * @memberOf Communities
     * @param {JoinGroupQuery} query - Query parameter.
     * @return {Promise<GroupMember>} Promise with new group member.
     */
    static joinGroup(query) {
        const request = new UpdateGroupMembersRequest({
            groupId: query.internalQuery.groupId,
            userIds: query.internalQuery.userIdList.toStrings(),
            role: query.internalQuery.role,
            status: query.internalQuery.status,
            invitationToken: query.invitationToken
        });

        return SDK.request('updateGroupMembers', request)
            .then((res) => {
                return res.members.map((member) => new GroupMember(member));
            });
    }

    /**
     * Update existing group members.
     *
     * @memberOf Communities
     * @param {UpdateGroupMembersQuery} query - Query.
     * @return {Promise<GroupMember[]>} Promise with updated members.
     */
    static updateGroupMembers(query) {
        const request = new UpdateGroupMembersRequest({
            groupId: query.groupId,
            userIds: query.userIdList.toStrings(),
            role: query.role,
            status: query.status
        });

        return SDK.request('updateGroupMembers', request)
            .then((res) => {
                return res.members.map((member) => new GroupMember(member));
            });
    }

    /**
     * Remove users from a group.
     *
     * @memberOf Communities
     * @param {RemoveGroupMembersQuery} query - Query.
     * @return {Promise<void>} Promise.
     */
    static removeGroupMembers(query) {
        const request = new RemoveGroupMembersRequest({
            groupId: query.groupId,
            userIds: query.userIdList.toStrings()
        });

        return SDK.request('removeGroupMembers', request);
    }

    /**
     * Get members of a group.
     *
     * @memberOf Communities
     * @param {PagingQuery<MembersQuery>} pagingQuery - Query with pagination details.
     * @return {Promise<PagingResult<GroupMember[]>>} Promise with group members and cursor for next query.
     */
    static getGroupMembers({ query, ...pagination}) {
        const request = new GetGroupMembersRequest({
            groupId: query.groupId,
            role: query.role,
            status: query.status,
            searchTerm: query.searchTerm,
            pagination: new Pagination(pagination)
        });

        return SDK.request('getGroupMembers', request)
            .then((res) => {
                return new PagingResult(
                    res.members.map((member) => new GroupMember(member)),
                    res.nextCursor
                );
            });
    }

    /**
     * Check if users are member of group.
     *
     * @memberOf Communities
     * @param {string}  groupId - Group ID.
     * @param {UserIdList} userIds - User IDs.
     * @return {Promise<Object<string, Membership>>} Promise with map of user ID and membership.
     */
    static areGroupMembers(groupId, userIds) {
        const request = new AreGroupMembersRequest({
            groupId,
            userIds: userIds.toStrings()
        });

        return SDK.request('areGroupMembers', request)
            .then((res) => {
                Object.keys(res.result || {}).forEach((key) => {
                    res.result[key] = new Membership(res.result[key]);
                });

                return res.result;
            });
    }

    // Chats

    /**
     * Send chat message to the specified recipient.
     *
     * @memberOf Communities
     * @param {ChatMessageContent} content - Chat message content.
     * @param {ChatId} target - Chat ID.
     * @return {Promise<ChatMessage>} Promise with the sent message.
     */
    static sendChatMessage(content, target) {
        let promises = [];

        if (content.attachments &&
            content.attachments.length) {
            content.attachments.forEach((attachment) => {
                promises.push(uploadAttachment(attachment, 'CHAT'));
            });
        }

        return Promise.all(promises).then(() => {
            const request = new SendChatMessageRequest({
                id: target.id,
                userId: target.userId,
                content: {
                    ...content,
                    attachments: content.attachments
                        ? content.attachments.map((attachment) => {
                            return new SGChatMessageAttachment({
                                imageURL: attachment.imageUrl,
                                videoURL: attachment.videoUrl
                            });
                        })
                        : null
                },
                properties: content.properties
            });

            return SDK.request('sendChatMessage', request)
                .then((res) => {
                    if (!res.message) {
                        return null;
                    }

                    return ChatMessage.create({
                        ...res.message,
                        text: res.message.content.text,
                        attachments: res.message.content.attachments,
                        author: res.message.author.publicUser
                    });
                });
        });
    }

    /**
     * Get chat messages.
     *
     * @memberOf Communities
     * @param {ChatMessagesPagingQuery<ChatMessagesQuery>} query - Query.
     * @return {Promise<ChatMessagesPagingResult<ChatMessage[]>>} Promise with chat messages and cursors.
     */
    static getChatMessages({query, limit, next, previous, refresh}) {
        const request = new GetChatMessagesRequest({
            id: query.chatId.id,
            userId: query.chatId.userId
                ? query.chatId.userId.id
                : null,
            pagination: new SGChatPagination({
                limit,
                cursor: previous || next || refresh
            })
        });

        return SDK.request('getChatMessages', request)
            .then((res) => {
                return new ChatMessagesPagingResult({
                    entries: res.messages.map((message) => ChatMessage.create({
                        ...message.content,
                        ...message,
                        author: res.authors[message.author.publicUser.id]
                    })),
                    next: res.nextCursor,
                    previous: res.previousCursor,
                    refresh: res.pollingCursor
                });
            });
    }

    /**
     * Get existing chats.
     *
     * @memberOf Communities
     * @param {PagingQuery} pagingQuery - Query and pagination.
     * @return {Promise<PagingResult<Chat[]>>} Promise with chats and cursor for next query.
     */
    static getChats(query) {
        const request = new GetChatsRequest({
            pagination: new Pagination({
                limit: query.limit,
                nextCursor: query.nextCursor
            }),
            memberUserId: UserId.currentUser().id
        });

        return SDK.request('getChats', request)
            .then((res) => {
                return new PagingResult(
                    res.chats.map(Chat.create),
                    res.nextCursor
                );
            });
    }

    /**
     * Get a single chat.
     *
     * @memberOf Communities
     * @param {ChatId} chatId - Chat ID.
     * @return {Promise<Chat>} Promise with Chat.
     */
    static getChat(chatId) {
        const request = new GetChatRequest({
            id: chatId.id,
            userId: chatId.userId
                ? chatId.userId.id
                : null
        });

        return SDK.request('getChat', request)
            .then((res) => {
                return Chat.create(res.chat);
            });
    }

    /**
     * Search topics, groups, users, activities, tags and labels by matching query.
     * Returns a list of each of those entities with a cursor for a next query.
     * If cursor is empty string, or {@link PagingResult#isLastPage()} is true, this is a last page.
     *
     * @memberOf Communities
     * @param {PagingQuery<SearchQuery>} query - Search query and pagination.
     * @return {Promise<SearchResult>} Promise with Result and cursor(s) for next query.
     */
     static search({ query, ...pagination }) {
        const request = new GlobalSearchRequest({
            targets: query.entities,
            searchTerm: query.searchTerm,
            properties: query.properties,
            labels: query.labels,
            pagination: new Pagination(pagination),
        });

        return SDK.request('search', request)
            .then((res) => SearchResult.create(res));
    }

    /**
     * Get list of users blocked by the current user.
     *
     * @memberOf Communities
     * @param {PagingQuery} query - Pagination query.
     * @return {Promise<PagingResult<User[]>>} Promise with users and cursor for next query.
     */
     static getBlockedUsers(query) {
        const request = new GetBlockedUsersRequest({
            pagination: new Pagination(query.pagination)
        });

        return SDK.request('getBlockedUsers', request)
            .then((res) => {
                return new PagingResult(
                    res.blockedUsers.map(User.create),
                    res.nextCursor
                );
            })
    }

    /**
     * Block one or more users.
     *
     * @memberOf Communities
     * @param {UserIdList} ids - List of User IDs to block.
     * @return {Promise<void>} Promise.
     */
    static blockUsers(ids) {
        const request = new BlockUsersRequest({
            blockedUserIds: ids.toStrings()
        });

        return SDK.request('blockUsers', request);
    }

    /**
     * Unblock one or more users.
     *
     * @memberOf Communities
     * @param {UserIdList} ids List of User IDs to unblock.
     * @return {Promise<void>} Promise
     */
    static unblockUsers(ids) {
        const request = new BlockUsersRequest({
            blockedUserIds: ids.toStrings()
        });

        return SDK.request('unblockUsers', request);
    }
}