Source: messages.js

'use strict';

const assert = require('assert');

var conversationsModule = require('./conversations.js');
var conversationsEndpoint = require('../endpoints/conversations-v1.js');
var usersModule = require('./users.js');
var Events = require('../client/events.js').Events;

var winston = require('winston');

/**
 * @description This is a notification of a existing message describing it.
 * @property    {Client}        client              [Client]{@link Client} object used to create this [MessageNotification]{@link MessageNotification} instance.
 * @property    {Conversation}  conversation        [Conversation]{@link Conversation} where the message have been sent.
 * @property    {string}        ID                  Curse UUID of the message, this ID is shared between all references of this message.
 * @property    {string}        content             Text content of the message.
 * @property    {number}        notificationType    Specify the type of the notification following the conversations-v1 ConversationNotificationType endpoint definition.
 * * Normal: 0  (message received)
 * * Edited: 1  (message edited)
 * * Liked: 2   (message liked)
 * * Deleted: 3 (message deleted)
 * @property    {string}        senderName          Nickname (if set) or Curse username of the message author.
 * @property    {number}        senderID            **DEPRECATED** Curse ID of the author of the message (Please use MessageNotification.sender.ID).
 * @property    {User}          sender              [User]{@link User} object corresponding to the author of the message.
 * @property    {number}        serverTimestamp     Timestamp of the message on the server side.
 * @property    {number}        likes               Amount of likes.
 * @property    {array}         likeUserIDs         Array of number corresponding to the IDs of all the user liking this message.
 * @property    {boolean}       isDeleted           True if the message have been deleted (content will be empty then).
 * @property    {Role}          senderVanityRole    Most important [Role]{@link Role} of the author on this server (**undefined** if not a conversation from a server).
 * @property    {array}         senderRoles         Array of [Role]{@link Role} of the author on this server (**undefined** if not a conversation from a server).
 * @property    {number}        editedTimestamp     Timestamp of the last edition of the message, if none, it is set at 0.
 * @property    {User}          editedUser          [User]{@link User} object corresponding to the author of the last edition of this message.
 * @property    {string}        editedUsername      Nickname or Curse username of the author of the last edition of this message.
 * @property    {number}        deletedTimestamp    Timestamp of the suppression of the message, if none, it is set at 0.
 * @property    {User}          deletedUser         [User]{@link User} object corresponding to the author of the suppression this message.
 * @property    {string}        deletedUsername     Nickname or Curse username of the author of the suppression of this message.
 * @property    {array}         mentions            Array of {Users}[@link User] mentioned in this message.
 * @property    {boolean}       atEveryoneMention   True if there is a global mention in the message.
 */
class MessageNotification {
    constructor(conversationMessageNotification, conversation, client){
        var self = this;

        //For debugging purposes
        this._conversationMessageNotifiation = conversationMessageNotification;

        this.client = client;
        this.conversation = conversation;
        this.ID = conversationMessageNotification.ServerID;
        this.content = conversationMessageNotification.Body;
        this.serverTimestamp = conversationMessageNotification.Timestamp;
        this.notificationType = conversationMessageNotification.NotificationType;

        this.sender;
        this.senderName = conversationMessageNotification.SenderName;
        this.senderID = conversationMessageNotification.SenderID; // /!\ Deprecated
        this.senderVanityRole;
        this.senderRoles = [];

        this.likes = conversationMessageNotification.LikeCount;
        this.likeUserIDs = conversationMessageNotification.LikeUserIDs;

        this.editedTimestamp = conversationMessageNotification.EditedTimestamp;
        this.editedUser = this.client.getUser(conversationMessageNotification.EditedUserID);
        this.editedUsername = conversationMessageNotification.EditedUsername;

        this.isDeleted = (conversationMessageNotification.NotificationType
            == conversationsEndpoint.ConversationNotificationType.Deleted);
        this.deletedTimestamp = conversationMessageNotification.DeletedTimestamp;
        this.deletedUser = this.client.getUser(conversationMessageNotification.DeletedUserID);
        this.deletedUsername = conversationMessageNotification.DeletedUsername;

        this.mentions = [];
        this.atEveryoneMention = false;


        //Link to our users
        if(client.users.has(conversationMessageNotification.SenderID)){
            this.sender = client.users.get(conversationMessageNotification.SenderID);
        } else {
            this.sender = new usersModule.User(conversationMessageNotification.SenderID, client);
        }

        //Add roles field to message
        if(this.client.servers.has(conversationMessageNotification.RootConversationID)){
            //Update VanityRole
            var server = this.client.servers.get(conversationMessageNotification.RootConversationID);
            this.senderVanityRole = server.roles.get(conversationMessageNotification.SenderVanityRole);
            //Update Roles
            for (let roleID of conversationMessageNotification.SenderRoles){
                this.senderRoles.push(server.roles.get(roleID));
            }
        }

        //Link mentions to users objects
        for (let user of conversationMessageNotification.Mentions){
            if(user === 0){
                this.atEveryoneMention = true;
            } else {
                this.mentions.push(this.client.getUser(user));
            }
        }
    }

    /**
     * @description Edit the content of the message
     * @param  {string}   newContent Text of the new message
     * @param  {Function} callback  Facultative arg, callback: (errors) => {}.
       This function can take an argument errors that is null or undefined when function ends correctly.
     */
    editContent(newContent, callback){
        //Define an empty void if no callback specified
        if(callback === undefined){
            callback = _ => {};
        }
        var retrying = false;
        var self = this;
        (function inner(){
            var req = new conversationsEndpoint.ConversationEditMessageRequest(newContent, []);
            conversationsEndpoint.conversationsEdit(self.conversation.ID, self.ID,
                self.serverTimestamp, req, self.conversation.client.token, function(errors){

                //Handle backend cache issue
                if(errors != undefined && errors.code === 404 && !retrying){
                    retrying = true;
                    winston.log('debug', 'Message.editContent', 'First attempt denied, retrying');
                    setTimeout(function(){
                        inner();
                    }, 1000);
                }
                else if(errors != undefined){
                    winston.log('error', 'Message.editContent', 'Cannot edit message', self.ID);
                    callback(errors);
                }
                else {
                    callback(null);
                }
            });
        })();

    }

    /**
     * @description Delete the message from the conversation
     * @param  {Function} callback  Facultative arg, callback: (errors) => {}.
       This function can take an argument errors that is null or undefined when function ends correctly.
     */
    deleteMessage(callback){
        //Define an empty void if no callback specified
        if(callback === undefined){
            callback = _ => {};
        }
        var retrying = false;
        var self = this;
        (function inner(){
            conversationsEndpoint.conversationsDelete(self.conversation.ID, self.ID,
                self.serverTimestamp, self.conversation.client.token, function(errors){

                //We check for an existing error with the code 404 encountered for the first time
                if(errors != undefined && errors.code === 404 && !retrying){
                    winston.log('debug', 'Message.deleteContent', 'First attempt denied, retrying');
                    setTimeout(function(){
                        inner();
                    }, 1000);
                }
                //Handle second time error, or other kind of errors than previously check
                else if(errors != undefined){
                    winston.log('error', 'Message.deleteContent', 'Cannot delete message', self.ID, self.serverTimestamp);
                    callback(errors);
                }
                //It worked
                else {
                    callback(null);
                }
            });
        })();
    }

    reply(content, callback){
        this.conversation.sendMessage(content, callback);
    }

}

exports.MessageNotification = MessageNotification;
exports.NotificationType = conversationsEndpoint.ConversationNotificationType;