const debug = require("debug")("edupage:log");
const error = require("debug")("edupage:error");
const RawData = require("../lib/RawData");
const {iterate} = require("../lib/utils");
const Attachment = require("./Attachment");
const Class = require("./Class");
const Edupage = require("./Edupage");
const {ENDPOINT, ENTITY_TYPE, API_STATUS, TIMELINE_ITEM_TYPE} = require("./enums");
const {APIError, EdupageError, MessageError} = require("./exceptions");
const Parent = require("./Parent");
const Plan = require("./Plan");
const Student = require("./Student");
const Teacher = require("./Teacher");
const User = require("./User");
const Assignment = require("./Assignment");
debug.log = console.log.bind(console);
class Message extends RawData {
constructor(data = {}, edupage = null) {
super(data);
if(!data.data) data.data = {};
if(typeof data.data === "string") data.data = JSON.parse(data.data) || {};
this.edupage = edupage;
this.id = data.timelineid;
this.type = data.typ;
this.creationDate = data.cas_pridania ? new Date(data.cas_pridania) : null;
this.timelineDate = data.timestamp ? new Date(data.timestamp) : null;
this.otherId = data.otherId || null;
this.repliesCount = +data.pocet_reakcii || 0;
this.lastReplyDate = data.posledna_reakcia ? new Date(data.posledna_reakcia) : null;
this.isRemoved = !!+data.removed;
this.isReply = !!this._data.data.textReply;
this.isImportant = !!+data.data.receipt || !!+data.data.importantReply;
this.seenDate = this.isImportant ? (data.data.myConfirmations?.receipt ? new Date(data.data.myConfirmations?.receipt) : null) : this.creationDate;
this.isSeen = !!this.seenDate;
this.likedDate = data.data.myConfirmations?.like ? new Date(data.data.myConfirmations?.like) : null;
this.isLiked = !!this.likedDate;
this.doneDate = null;
this.isDone = false;
this.isStarred = false;
this.likes = data.data.confirmations?.like || 0;
this.text = data.data.messageContent || data.text;
this.title = data.user_meno;
this.participantsCount = null;
this.participants = [];
this.likedBy = [];
this.seenBy = [];
this.owner = null;
this.recipient = null;
this.recipientUserString = null;
this.isWildcardRecipient = false;
this.replyOf = null;
this.attachments = [];
this.replies = [];
this.assignment = null;
if(this.edupage) Message.prototype.init.call(this);
}
init(edupage = null) {
if(edupage) this.edupage = edupage;
if("item" in this._data) {
const data = this._data.item;
this.timelineDate = data.timestamp ? new Date(data.timestamp) : null;
this.repliesCount = +data.pocet_reakcii || 0;
this.lastReplyDate = data.posledna_reakcia ? new Date(data.posledna_reakcia) : null;
this.isRemoved = !!+data.removed;
this.seenDate = this.isImportant ? (this._data.reakcie[0]?.data?.myConfirmations?.receipt ? new Date(this._data.reakcie[0]?.data?.myConfirmations?.receipt) : null) : this.creationDate;
this.isSeen = !!this.seenDate;
this.likedDate = this._data.reakcie[0]?.data?.myConfirmations?.like ? new Date(this._data.reakcie[0]?.data?.myConfirmations?.like) : null;
this.isLiked = !!this.likedDate;
this.likes = data.data.confirmations?.like || 0;
}
if("numParticipants" in this._data) this.participantsCount = this._data.numParticipants;
if("participants" in this._data) this.participants = iterate(this._data.participants).map(([i, user, data]) => this.edupage.getUserByUserString(user) || User.from(user, data, this.edupage));
if("reakcie" in this._data) {
this._data.reakcie
.forEach(e => {
if(e.pomocny_zaznam) return;
if(e.timelineid == this._data.item?.timelineid) return;
if(e.cas_pridania == this._data.cas_pridania) return;
if(e.typ == TIMELINE_ITEM_TYPE.CONFIRMATION) {
const data = Message.parseUsername(e.vlastnik_meno);
const user = this.edupage.getUserByUserString(e.vlastnik) || User.from(e.vlastnik, data, this.edupage);
if(e.data.like) this.likedBy.push({user, date: new Date(e.data.like)});
if(this.isImportant) this.seenBy.push({user, date: new Date(e.cas_pridania)});
} else {
if(this.replies.some(t => t.id == e.timelineid)) return;
const msg = new Message(e, this.edupage);
if(!this.replies.includes(msg)) this.replies.push(msg);
}
});
this.repliesCount = this.replies.length;
}
const props = this._data.userProps || this.edupage._data.timelineUserProps[this.id] || {};
this.doneDate = props.doneMaxCas ? new Date(props.doneMaxCas) : null;
this.isDone = !!this.doneDate;
this.isStarred = !!+props.starred;
if(this._data.vlastnik) {
const data = Message.parseUsername(this._data.vlastnik_meno);
this.owner = this.edupage.getUserByUserString(this._data.vlastnik) ||
User.from(this._data.vlastnik, data, this.edupage);
}
if(this._data.user) {
const {recipient, wildcard} = this.getRecipient(this._data.user);
this.recipient = recipient;
this.isWildcardRecipient = wildcard;
this.recipientUserString = this._data.user;
}
if(this._data.reakcia_na && this._data.typ != TIMELINE_ITEM_TYPE.CONFIRMATION) {
const message = this.edupage.timelineItems.find(e => e.id == this._data.reakcia_na);
if(message) {
message.replies.push(this);
this.replyOf = message;
} else {
}
}
if(this._data.data.attachements) {
const attchs = this._data.data.attachements;
this.attachments = Object.keys(attchs).map(e => new Attachment({src: e, name: attchs[e]}, this.edupage));
}
if(this._data.data.superid) {
this.assignment = this.edupage.assignments.find(e => e.superId == this._data.data.superid);
}
}
async reply(options) {
const {
text,
recipient = null,
parents = false,
attachments = []
} = options;
const res = await this.edupage.api({
url: ENDPOINT.TIMELINE_CREATE_REPLY,
data: {
"groupid": this.id,
"recipient": this.isReply
? this.owner.getUserString(parents)
: (recipient ? recipient.getUserString(parents) : ""),
"text": text,
"moredata": JSON.stringify({attachements: attachments})
}
});
if(res.status !== API_STATUS.OK) {
error(`[Reply] Received invalid status from the server '${res.status}'`);
throw new APIError(`Failed to send message: Invalid status received '${res.status}'`, res);
}
const _len = this.replies.length;
this._data.reakcie = res.data.reakcie;
Message.prototype.init.call(this);
if(this.replies.length > _len) {
if(this.replies.length > _len + 1) debug(`[Reply] Message contains multiple new replies (${_len} -> ${this.replies.length})`);
return this.replies[this.replies.length - 1];
} else {
error(`[Reply] Message had ${_len} replies, and now has ${this.replies.length}`, res, this.replies);
throw new MessageError(`Failed to get sent message: No new replies were created`);
}
}
async markAsLiked(state = true) {
if(!this.edupage) throw new EdupageError(`Message does not have assigned Edupage instance yet`);
const res = await this.edupage.api({
url: ENDPOINT.TIMELINE_CREATE_CONFIRMATION,
data: {
"groupid": this.id,
"confirmType": "like",
"val": (+state).toString()
}
});
await this.refresh(res);
return state;
}
async markAsSeen() {
if(!this.edupage) throw new EdupageError(`Message does not have assigned Edupage instance yet`);
const res = await this.edupage.api({
url: ENDPOINT.TIMELINE_CREATE_CONFIRMATION,
data: {
"groupid": this.id,
"confirmType": "receipt",
"val": ""
}
});
await this.refresh(res);
}
async markAsDone(state = true) {
if(!this.edupage) throw new EdupageError(`Message does not have assigned Edupage instance yet`);
const res = await this.edupage.api({
url: ENDPOINT.TIMELINE_FLAG_HOMEWORK,
data: {
"homeworkid": "timeline:" + this.id,
"flag": "done",
"value": (+state).toString()
}
});
if(!("timelineUserProps" in res)) throw new APIError(`Failed to refresh message: Invalid status received '${res.status}'`, this, res);
this._data.userProps = null;
this.edupage._data.timelineUserProps = res.timelineUserProps;
Message.prototype.init.call(this);
return state;
}
async markAsStarred(state = true) {
if(!this.edupage) throw new EdupageError(`Message does not have assigned Edupage instance yet`);
const res = await this.edupage.api({
url: ENDPOINT.TIMELINE_FLAG_HOMEWORK,
data: {
"homeworkid": "timeline:" + this.id,
"flag": "important",
"value": (+state).toString()
}
});
if(!("timelineUserProps" in res)) throw new APIError(`Failed to refresh message: Invalid status received '${res.status}'`, this, res);
this._data.userProps = null;
this.edupage._data.timelineUserProps = res.timelineUserProps;
Message.prototype.init.call(this);
return state;
}
async refresh(data = null) {
if(!this.edupage) throw new EdupageError(`Message does not have assigned Edupage instance yet`);
const res = data || await this.edupage.api({
url: ENDPOINT.TIMELINE_GET_REPLIES,
data: {
groupid: this.id,
lastsync: ""
}
});
if(res.status !== API_STATUS.OK) throw new APIError(`Failed to refresh message: Invalid status received '${res.status}'`, this, res);
res.data.reakcie.forEach(e => {
if(typeof e.data === "string") e.data = JSON.parse(e.data);
});
this._data = {...this._data, ...res.data};
Message.prototype.init.call(this);
}
static parseUsername(name) {
const {firstname = "", lastname = ""} = name.match(/(?<firstname>.*?)\s?(?<lastname>\S+)(?:\s\(|$)/)?.groups || {};
return {firstname, lastname};
}
getRecipient(userString) {
userString = userString.replace("Only", "");
const {type, id, wildcard} = User.parseUserString(userString);
let recipient = null;
if(type == ENTITY_TYPE.TEACHER) recipient = this.edupage.teachers.find(e => e.id == id);
if(type == ENTITY_TYPE.STUDENT) recipient = this.edupage.students.find(e => e.id == id);
if(type == ENTITY_TYPE.PARENT) recipient = this.edupage.parents.find(e => e.id == id);
if(type == ENTITY_TYPE.STUD_PLAN || type == ENTITY_TYPE.CUST_PLAN) recipient = this.edupage.plans.find(e => e.id == id);
if(type == ENTITY_TYPE.STUD_CLASS) recipient = this.edupage.classes.find(e => e.id == id);
if(type == ENTITY_TYPE.CLASS) recipient = this.edupage.plans.find(e => e.customClassId == id);
return {recipient, wildcard};
}
}
module.exports = Message;