hnschat-web/assets/js/script.js
2024-04-16 22:26:30 +00:00

1402 lines
29 KiB
JavaScript
Executable File

let { e2ee } = await import(`./e2ee.js?r=${revision}`);
let { ws } = await import(`./ws.js?r=${revision}`);
let { ui } = await import(`./ui.js?r=${revision}`);
let { stream } = await import(`./stream.js?r=${revision}`);
let { listeners } = await import(`./listeners.js?r=${revision}`);
function log(string) {
console.log(string);
}
export class HNSChat {
constructor() {
window.hnschat = this;
try {
this.varo = new Varo();
}
catch {}
this.mobile = false;
this.host = window.location.host;
this.hash = window.location.hash.substring(1);
this.page;
this.data;
this.ui = new ui(this);
this.ws = new ws(this);
this.listeners = new listeners(this);
this.e2ee = new e2ee();
this.streamURL = "https://media.hns.chat/stream";
this.stream;
this.isActive = true;
this.keys;
this.session;
this.domain;
this.domains;
this.staked;
this.conversation;
this.settings;
this.messages = [];
this.seen = {};
this.users;
this.channels;
this.pms;
this.tab = "channels";
this.timeFormat = "g:i A";
this.dateFormat = "F jS, Y";
this.gotChannels;
this.gotPms;
this.gotMentions;
this.loadingMessages;
this.replying;
this.queued = [];
this.typing;
this.typingSent;
this.typingDelay = 2;
this.typingSendDelay = 1;
this.lastTyped;
this.typers = {};
this.active = [];
this.avatars = {};
this.action = "\x01ACTION";
this.commands = ["me", "shrug", "slap"];
this.hasBob;
this.init();
}
varoLoaded() {
if (typeof this.varo !== "undefined") {
return true;
}
return false
}
getPage() {
let pathname = document.location.pathname;
let match = pathname.match(/\/(?<page>.+?)(?:\/(?<data>.+)|$)/);
if (match) {
let groups = match.groups;
if (groups.page) {
this.page = groups.page;
}
if (groups.data) {
this.data = groups.data;
}
}
else {
this.page = "chat";
}
}
async api(data) {
if (this.session) {
data.session = this.session;
}
let output = new Promise(function(resolve) {
$.post("/api", JSON.stringify(data), function(response){
if (response) {
let json = JSON.parse(response);
resolve(json);
}
});
});
return await output;
}
time() {
return Math.floor(Date.now() / 1000);
}
handlePush(init=false) {
if (localStorage.handlePush) {
try {
let info = JSON.parse(localStorage.handlePush);
localStorage.setItem("domain", info.domain);
localStorage.setItem("conversation", info.conversation);
if (!init) {
if (this.domain !== info.domain) {
this.changeDomain(info.domain);
}
this.changeConversation(info.conversation);
}
}
catch {}
}
localStorage.removeItem("handlePush");
}
async init() {
this.handlePush(true);
this.getPage();
switch (this.page) {
case "sync":
this.loadSync();
break;
case "buy":
break;
default:
try {
this.keys = JSON.parse(localStorage.keys);
}
catch {
this.e2ee.generateKeys().then(r => {
this.keys = r;
localStorage.setItem("keys", JSON.stringify(this.keys));
});
}
if (this.hash) {
localStorage.setItem("hash", this.hash);
history.pushState("", document.title, window.location.pathname + window.location.search);
}
else {
this.hash = localStorage.hash;
}
this.mobile = localStorage.mobile || false;
this.session = localStorage.session;
this.domain = localStorage.domain;
this.conversation = localStorage.conversation;
try {
this.settings = JSON.parse(localStorage.settings);
this.loadSettings();
}
catch {
this.settings = {};
}
this.firstLaunch = localStorage.firstLaunch;
if (!this.firstLaunch) {
localStorage.setItem("firstLaunch", this.time());
}
await this.startSession();
await this.setPublicKey();
await this.ws.connect();
break;
}
switch (this.page) {
case "chat":
this.stream = new stream(this);
this.ui.setConversationTab();
this.ui.setupSync();
this.ui.setupNotifications();
break;
}
if (typeof bob3 != "undefined") {
this.hasBob = true;
this.ui.hasBob();
}
}
loadSettings() {
Object.keys(this.settings).forEach((setting, k) => {
let value = this.settings[setting];
switch (setting) {
case "chatDisplayMode":
this.ui.chatDisplayMode(value);
break;
default:
this.ui.root.style.setProperty(`--${setting}`, value);
break;
}
});
}
loadSync() {
let hash = this.hash;
let db64 = this.db64(hash);
let json = JSON.parse(db64);
this.session = json.session;
localStorage.setItem("session", this.session);
if (json.settings) {
this.settings = json.settings;
localStorage.setItem("settings", JSON.stringify(this.settings));
}
let data = {
action: "getPublicKey"
}
this.api(data).then(r => {
if (r.success) {
let pubkey = JSON.parse(r.pubkey);
this.e2ee.importKey(pubkey.x, pubkey.y, json.privkey).then(privkey => {
this.keys = {
privateKeyJwk: privkey,
publicKeyJwk: pubkey
};
localStorage.setItem("keys", JSON.stringify(this.keys));
this.ui.openURL("/");
});
}
});
}
db64(str) {
str = (str + '==='.slice((str.length + 3) % 4)).replace(/-/g, '+').replace(/_/g, '/');
return atob(str);
}
b64(str) {
str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
return btoa(str);
}
syncLink() {
let data = {
session: this.session,
privkey: this.keys.privateKeyJwk.d,
settings: this.settings
}
let json = JSON.stringify(data);
let encoded = this.b64(json);
let link = "https://"+this.host+"/sync#"+encoded;
return link;
}
regex(pattern, string) {
return [...string.matchAll(pattern)];
}
rtrim(str, chr) {
let rgxtrim = (!chr) ? new RegExp('\\s+$') : new RegExp(chr+'+$');
return str.replace(rgxtrim, '');
}
replaceRange(s, start, end, substitute) {
let before = s.substr(0, start);
let after = s.substr(end, (s.length -end));
return before+substitute+after;
}
sorted(array, by) {
return array.sort((a, b) => a[by].localeCompare(b[by]));
}
isKey(e, key) {
if (this.key(e) == key) {
return true
}
return false;
}
key(e) {
return e.which || e.keyCode;
}
keyName(e) {
return e.key;
}
setPublicKey() {
let data = {
action: "setPublicKey",
pubkey: JSON.stringify(this.keys.publicKeyJwk)
};
return this.api(data);
}
async startSession() {
if (this.session) {
return;
}
let data = {
"action": "startSession"
};
return this.api(data).then(r => {
this.session = r.session;
localStorage.setItem("session", r.session);
});
}
sendDomain() {
if (!this.domain) {
if (this.domains.length) {
this.domain = this.domains[0].id;
localStorage.setItem("domain", this.domain);
}
else {
this.ui.openURL("/id");
return;
}
}
this.ws.send(`DOMAIN ${this.domain}`);
}
message(data) {
let message = data.toString();
let parsed = message.match(/(?<command>[A-Z]+)(\s(?<body>.+))?/);
this.handle(parsed.groups);
}
async handle(parsed) {
let command = parsed.command;
let body = parsed.body;
let push = false;
if (body) {
try {
body = JSON.parse(body);
for (var k in body) {
try {
body[k] = JSON.parse(body[k]);
}
catch {}
for (var i in body[k]) {
try {
body[k][i] = JSON.parse(body[k][i]);
}
catch {}
}
}
}
catch {}
}
switch (command) {
case "SUCCESS":
switch (body.type) {
case "ADDDOMAIN":
case "ADDSLD":
this.ui.enableButton(body.type);
this.ui.handleSuccess(body);
break;
case "DELETEDOMAIN":
this.ui.removeDomain(body.id);
break;
case "VERIFYDOMAIN":
this.ui.handleSuccess(body);
break;
case "GETADDRESS":
this.ui.paymentResponse(body);
break;
}
break;
case "ERROR":
switch (body.type) {
case "ADDDOMAIN":
this.ui.errorResponse(body);
break;
case "ADDSLD":
this.ui.enableButton(body.type);
this.ui.errorResponse(body);
break;
case "MESSAGES":
this.ui.setUserList();
this.ui.messagesLoading(false);
this.ui.setGatedView(body);
this.ui.markEmptyIfNeeded();
this.loadingMessages = false;
break;
case "DOMAIN":
this.domain = null;
this.sendDomain();
break;
case "PM":
if (body.id) {
let otherUser = this.otherUserFromPM(body.id);
let queuedMessage = this.queuedMessage(otherUser.domain);
if (queuedMessage) {
this.ui.changeConversation(body.id);
this.ui.close();
}
}
else {
this.ui.errorResponse(body);
}
break;
case "GETADDRESS":
this.ui.paymentResponse(body);
break;
}
break;
case "IDENTIFIED":
if (this.page !== "sync") {
if (body.seen) {
this.seen = body.seen;
}
this.ws.send("DOMAINS");
}
break;
case "DOMAINS":
this.domains = body;
this.ui.domains(this.domains);
switch (this.page) {
case "chat":
this.sendDomain();
break;
case "id":
case "invite":
this.ws.send(`STAKED`);
break;
}
break;
case "STAKED":
this.staked = body;
this.ui.stakedDomains(body);
break;
case "DOMAIN":
this.ui.updateDomainSelect();
this.ws.send("USERS");
break;
case "USERS":
$.each(body, (k, user) => {
body[k].domain = body[k].domain.toString();
});
this.users = body;
this.ws.send(`PING`);
this.ws.send(`CHANNELS`);
this.ws.send(`PMS`);
break;
case "USER":
this.users = this.users.filter(u => {
return u.id != body.id;
});
body.domain = body.domain.toString();
this.users.push(body);
if (body.id !== this.domain) {
let pm = this.pmWithUser(body.id);
if (pm) {
this.makeSecret(pm);
}
}
this.ui.setUserList();
this.ui.updateConversations();
break;
case "CHANNELS":
this.ui.clear("channels");
this.channels = body;
if (this.channels.length) {
let sorted = this.channels.sort((a, b) => {
if (("activity" in a) && !("activity" in b)) {
return 1;
}
if (("activity" in b) && !("activity" in a)) {
return -1;
}
if (!("activity" in a) && !("activity" in b)) {
return 0;
}
return a.activity - b.activity;
});
$.each(sorted.reverse(), (k, channel) => {
this.ui.conversation("channels", channel);
});
}
this.ui.updateConversations();
this.gotChannels = true;
this.ws.send(`MENTIONS`);
this.ready(true);
break;
case "CHANNEL":
this.channels.push(body);
this.ui.conversation("channels", body);
this.ui.updateConversations();
break;
case "PMS":
this.ui.clear("pms");
this.pms = body;
if (this.pms.length) {
let sorted = this.pms.sort((a, b) => {
return b.activity - a.activity;
});
$.each(sorted, (k, conversation) => {
this.makeSecret(conversation).then((key) => {
this.ui.conversation("pms", conversation);
if (k == this.pms.length - 1) {
this.ui.updateConversations();
this.gotPms = true;
this.ready(true);
}
});
});
}
else {
this.ui.updateConversations();
this.gotPms = true;
this.ready(true);
}
break;
case "PM":
this.pms.push(body);
this.makeSecret(body).then((key) => {
this.ui.conversation("pms", body);
let otherUser = this.otherUserFromPM(body.id);
let queuedMessage = this.queuedMessage(otherUser.domain);
if (queuedMessage) {
this.ui.changeConversation(body.id);
this.ui.close();
}
});
break;
case "MESSAGES":
this.ui.setUserList();
this.ui.insertMessages(body, true).then(() => {
this.ui.messagesLoading(false);
if (body.at) {
this.ui.scrollToMessage(body.at);
}
else if (body.after) {
this.ui.backInPresent(body);
}
if (!this.isChannel(this.conversation)) {
let otherUser = this.otherUserFromPM(this.conversation);
let queuedMessage = this.queuedMessage(otherUser.domain);
if (queuedMessage) {
this.queued = this.queued.filter(q => {
return q.domain !== queuedMessage.domain;
});
this.sendMessage(this.conversation, queuedMessage.message);
}
}
});
break;
case "MESSAGE":
case "NOTICE":
delete this.typers[body.user];
let c;
if (this.isChannel(body.conversation)) {
c = this.channelForID(body.conversation);
}
else {
c = this.pmForID(body.conversation);
}
c.activity = body.time;
this.ui.updateConversations();
this.ui.moveConversationToTop(body.conversation);
await this.decryptMessageIfNeeded(body.conversation, body).then(decrypted => {
body.message = decrypted[0];
if (body.p_message) {
body.p_message = decrypted[1];
}
if (typeof body.message == "object") {
if (body.message.message) {
body.message.message = body.message.message.toString();
}
body.message = JSON.stringify(body.message);
}
if (typeof body.p_message == "object") {
if (body.p_message.message) {
body.p_message.message = body.p_message.message.toString();
}
body.p_message = JSON.stringify(body.p_message);
}
body.message = body.message.toString();
if (body.p_message) {
body.p_message = body.p_message.toString();
}
if (!this.ui.inThePast && body.conversation == this.conversation) {
let data = {
messages: [body]
}
this.ui.insertMessages(data);
this.seen[this.conversation] = this.time();
if (!this.isActive) {
if (this.isChannel(body.conversation)) {
if (this.mentionsMe(body.message)) {
push = "mention";
}
else if (this.replyingToMe(body)) {
push = "reply";
}
}
else {
push = "pm";
}
}
}
else {
if (this.isChannel(body.conversation)) {
if (this.mentionsMe(body.message)) {
this.ui.markMention(body.conversation, true);
push = "mention";
}
else if (this.replyingToMe(body)) {
push = "reply";
}
}
else {
push = "pm";
}
}
if (push) {
if (body.user !== this.domain) {
let name;
switch (push) {
case "pm":
name = this.ui.toUnicode(this.userForID(body.user).domain);
break;
case "mention":
case "reply":
name = `${this.ui.toUnicode(this.userForID(body.user).domain)} - #${this.ui.toUnicode(c.name)}`;
break;
}
let subtitle = this.ui.messageSummary(decrypted[0]);
subtitle = this.ui.stripHTML(this.ui.replaceIds(subtitle));
this.ui.sendNotification(name, subtitle, body.conversation);
}
}
});
break;
case "DELETEMESSAGE":
this.ui.deleteMessage(body.id);
break;
case "REACT":
if (body.conversation == this.conversation) {
let message = this.messages.filter(m => {
return m.id == body.message;
})[0];
try {
let json = JSON.parse(message.reactions);
if (!Object.keys(json).includes(body.reaction)) {
json[body.reaction] = [];
}
if (json[body.reaction].includes(body.user)) {
let x = json[body.reaction].indexOf(body.user);
delete json[body.reaction].splice(x, 1);
if (!Object.keys(json[body.reaction]).length) {
delete json[body.reaction];
}
}
else {
json[body.reaction].push(body.user);
}
message.reactions = JSON.stringify(json);
this.ui.updateReactions(body.message);
this.seen[this.conversation] = this.time();
}
catch {}
}
break;
case "TYPING":
this.typers[body.from] = {
to: body.to,
time: this.time()
}
break;
case "MENTIONS":
this.ui.updateMentions(body);
this.gotMentions = true;
this.ready(true);
break;
case "PONG":
this.updateActiveUsers(body.active);
//this.ui.checkVersion(body.version);
break;
case "PINMESSAGE":
this.channelForID(body.conversation).pinned = body.id;
if (this.conversation == body.conversation) {
this.ui.setPinnedMessage();
}
break;
case "STARTVIDEO":
this.ui.muteAll();
this.channelForID(body.conversation).video = true;
this.channelForID(body.conversation).videoUsers = body.users;
this.ui.showVideoIfNeeded();
break;
case "INVITEVIDEO":
this.channelForID(body.conversation).videoSpeakers = body.speakers;
this.ui.showVideoIfNeeded();
break;
case "JOINVIDEO":
if (body.users) {
this.channelForID(body.conversation).videoUsers = body.users;
}
if (body.watchers) {
this.channelForID(body.conversation).videoWatchers = body.watchers;
}
if (body.speakers) {
this.channelForID(body.conversation).videoSpeakers = body.speakers;
}
this.ui.showVideoIfNeeded();
break;
case "VIEWVIDEO":
if (body.users) {
this.channelForID(body.conversation).videoUsers = body.users;
}
if (body.watchers) {
this.channelForID(body.conversation).videoWatchers = body.watchers;
if (body.watchers.includes(this.domain)) {
this.channelForID(body.conversation).watching = true;
}
}
this.ui.showVideoIfNeeded();
break;
case "LEAVEVIDEO":
if (body.users) {
this.channelForID(body.conversation).videoUsers = body.users;
}
if (body.watchers) {
this.channelForID(body.conversation).videoWatchers = body.watchers;
}
if (body.speakers) {
this.channelForID(body.conversation).videoSpeakers = body.speakers;
}
this.ui.showVideoIfNeeded();
break;
case "ENDVIDEO":
this.endVideo(body.conversation);
break;
case "MUTEVIDEO":
case "MUTEAUDIO":
let toggle;
let user = this.channelForID(body.conversation).videoUsers[body.user];
switch (command) {
case "MUTEVIDEO":
toggle = "toggleVideo";
user.video = !user.video;
break;
case "MUTEAUDIO":
toggle = "toggleAudio";
user.audio = !user.audio;
break;
}
this.ui.updateVideoUsers();
if (body.user == this.domain) {
this.ui.setMute(toggle, -1);
}
break;
case "CONNECTED":
if (this.userForID(body)) {
this.userForID(body).active = true;
this.ui.updateUserList();
this.ui.updateConversations();
}
break;
case "DISCONNECTED":
if (this.userForID(body)) {
this.userForID(body).active = false;
this.ui.updateUserList();
this.ui.updateConversations();
}
break;
}
}
endAllVideo() {
try {
this.stream.close();
$.each(this.channels, (k, c) => {
this.endVideo(c.id);
});
}
catch {};
}
endVideo(conversation) {
this.channelForID(conversation).videoUsers = {};
this.channelForID(conversation).videoWatchers = [];
this.channelForID(conversation).videoSpeakers = [];
this.channelForID(conversation).video = false;
this.channelForID(conversation).watching = false;
this.ui.showVideoIfNeeded();
}
currentVideoUsers() {
return this.channelForID(this.conversation).videoUsers;
}
updateActiveUsers(active) {
if (this.users) {
let current = this.active;
this.active = active;
current.forEach(u => {
if (!active.includes(u)) {
let user = this.userForID(u);
user.active = false;
}
});
active.forEach(u => {
let user = this.userForID(u);
user.active = true;
});
this.ui.updateUserList();
this.ui.updateConversations();
}
}
otherUserFromPM(id) {
let pm = this.pmForID(id);
let otherUserID = this.otherUser(pm.users);
let otherUser = this.userForID(otherUserID);
return otherUser;
}
queuedMessage(domain) {
return this.queued.filter(q => {
return q.domain == domain;
})[0];
}
ready(bool) {
if (bool) {
if (this.gotChannels && this.gotPms && this.gotMentions) {
this.ui.changeMe(this.domain);
this.ui.setLoading(false);
this.ui.handleHash();
this.changeConversation(this.conversation);
this.changedConversation();
}
}
else {
this.gotChannels = false;
this.gotPms = false;
this.gotMentions = false;
this.ui.setLoading(true);
}
}
isChannel(name) {
if (name) {
if (name.toString().length == 8) {
return true;
}
}
return false;
}
channelForID(id) {
return this.channels.filter(c => {
return c.id == id;
})[0];
}
channelForName(name) {
return this.channels.filter(c => {
return c.name == name;
})[0];
}
stakedForName(name) {
return this.staked.filter(c => {
return c.name == name;
})[0];
}
pmForID(id) {
return this.pms.filter(c => {
return c.id == id;
})[0];
}
pmWithUser(id) {
return this.pms.filter(c => {
return c.users.includes(id);
})[0];
}
otherUser(users) {
return users.filter(u => {
return u !== this.domain;
})[0];
}
userForID(id) {
if (this.users) {
return this.users.filter(u => {
return u.id == id;
})[0];
}
return false;
}
userForName(name, active=false) {
return this.users.filter(u => {
return (u.domain == name || this.ui.toUnicode(u.domain) == name) && !u.locked && !u.deleted;
})[0];
}
usersForConversation(id) {
if (this.channels) {
let conversation = this.channels.filter(c => {
return c.id == id;
})[0];
let users = this.users.filter(u => {
return !u.locked;
});
if (!conversation.public) {
users = this.users.filter(u => {
return !u.locked && u.tld == conversation.name || u.id == "n2sTWh5EZGI8xMQr";
});
}
return users;
}
return [];
}
changeConversation(id) {
let current = this.conversation;
let change = id;
if (!this.pmForID(id) && !this.channelForID(id)) {
id = false;
}
if (!id) {
change = this.channels[0].id;
}
if (current !== change) {
this.conversation = change;
localStorage.setItem("conversation", this.conversation);
this.changedConversation();
}
}
changedConversation() {
this.ui.closeMenusIfNeeded();
this.messages = [];
this.ui.clear("input");
this.ui.clear("messages");
this.ui.setInThePast(false);
this.ui.messagesLoading(true);
this.ui.emptyUserList();
this.ui.searchUsers(false);
this.ui.clearSelection();
this.ui.updateTypingView();
this.getMessages();
if (this.isChannel(this.conversation)) {
this.tab = "channels";
}
else {
this.tab = "pms";
}
this.ui.setConversationTab();
this.ui.setActiveConversation();
this.ui.markUnread(this.conversation, false);
this.ui.markMention(this.conversation, false);
this.ui.updateInputBar();
this.ui.showVideoIfNeeded();
this.seen[this.conversation] = this.time();
this.ws.send(`CHANGEDCONVERSATION ${this.conversation}`);
}
getMessage(id) {
let data = {
action: "getMessage",
domain: this.domain,
id: id
};
return this.api(data);
}
getMessages(options={}) {
if (this.loadingMessages) {
return;
}
this.loadingMessages = true;
let data = {
conversation: this.conversation,
};
let merged = {...data,...options};
this.ws.send(`MESSAGES ${JSON.stringify(merged)}`);
}
async makeSecret(pm) {
let output = new Promise(resolve => {
let otherUser = this.otherUserFromPM(pm.id);
let otherKey = otherUser.pubkey;
this.e2ee.deriveKey(otherKey, this.keys.privateKeyJwk).then(key => {
otherUser.sharedkey = key;
resolve(key);
});
});
return await output;
}
async decryptedBody(conversation, message) {
let output = new Promise(resolve => {
let pm = this.pmForID(conversation);
let user = this.userForID(this.otherUser(pm.users));
this.e2ee.decryptMessage(message, user.sharedkey, conversation).then(decrypted => {
resolve(decrypted.trim());
});
});
return await output;
}
async decryptMessageIfNeeded(conversation, message) {
let body = new Promise(resolve => {
if (this.isChannel(conversation)) {
resolve(message.message);
}
else {
this.decryptedBody(conversation, message.message).then(decrypted => {
resolve(decrypted);
});
}
});
let replyBody = new Promise(resolve => {
if (message.p_message) {
if (this.isChannel(conversation)) {
resolve(message.p_message);
}
else {
this.decryptedBody(conversation, message.p_message).then(decrypted => {
resolve(decrypted);
});
}
}
else {
resolve();
}
});
let prepared = await Promise.all([body, replyBody]);
return prepared;
}
async encryptedBody(conversation, message) {
let output = new Promise(resolve => {
let pm = this.pmForID(conversation);
let user = this.userForID(this.otherUser(pm.users));
this.e2ee.encryptMessage(message, user.sharedkey, conversation).then(encrypted => {
resolve(encrypted.trim());
});
});
return await output;
}
async encryptMessageIfNeeded(conversation, message) {
let output = new Promise(resolve => {
if (this.isChannel(conversation)) {
resolve(message);
}
else {
this.encryptedBody(conversation, message).then(encrypted => {
resolve(encrypted);
});
}
});
return await output;
}
changeDomain(domain) {
this.domain = domain;
localStorage.setItem("domain", domain);
if (this.page == "chat") {
this.ready(false);
this.ws.send(`DOMAIN ${domain}`);
}
}
usersInMessage(message) {
let matches = this.regex(/\@(?<name>[^ \x00]+?)\//gm, message);
return matches;
}
channelsInMessage(message) {
let matches = this.regex(/\#(?<name>[^ \x00]+?)(?:\s|$)/gm, message);
return matches;
}
replaceCompletions(text) {
let output = text;
while (this.usersInMessage(output).length) {
let users = this.usersInMessage(output);
let result = users[0];
let name = result.groups.name;
let start = result.index;
let end = (start + name.length + 1 + 1);
let match = this.users.filter(u => {
return this.ui.toUnicode(u.domain) == name && !u.locked;
});
let replace;
if (match.length) {
let id = match[0].id;
replace = `@${id}`;
}
else {
replace = `@\x00${name}/`;
}
output = this.replaceRange(output, start, end, replace);
}
while (this.channelsInMessage(output).length) {
let channels = this.channelsInMessage(output);
let result = channels[0];
let name = result.groups.name;
let start = result.index;
let end = (start + name.length + 1);
let match = this.channels.filter(c => {
return this.ui.toUnicode(c.name) == name;
});
let replace;
if (match.length) {
let id = match[0].id;
replace = `@${id}`;
}
else {
replace = `#\x00${name}`;
}
output = this.replaceRange(output, start, end, replace);
}
return output;
}
sendTyping() {
let input = $("textarea#message").val();
if (!this.typing || !input || (input && input[0] == "/")) {
return;
}
let send = true;
if (this.typingSent) {
let diff = this.time() - this.typingSent;
if (diff < this.typingSendDelay) {
send = false;
}
}
if (send) {
this.typingSent = this.time();
let data = {
from: this.domain,
to: this.conversation
}
this.ws.send(`TYPING ${JSON.stringify(data)}`);
}
}
updateTypingStatus() {
if (this.lastTyped) {
if ((this.time() - this.lastTyped) > this.typingDelay) {
this.typing = false;
this.lastTyped = false;
}
else if ($("textarea#message").is(":focus") && $("textarea#message").val() !== "") {
this.typing = true;
}
}
}
sendMessage(conversation, message) {
if (!message.trim().length) {
return;
}
this.encryptMessageIfNeeded(conversation, message).then(encrypted => {
let data = {
conversation: conversation,
message: encrypted
}
if (this.replying) {
data.replying = this.replying.message;
}
this.typing = false;
this.ws.send(`MESSAGE ${JSON.stringify(data)}`);
this.replying = null;
this.ui.updateReplying();
});
}
async sendPayment(address, amount) {
const wallet = await bob3.connect();
if (!amount) {
return { message: "Please enter an amount." };
}
try {
const send = await wallet.send(address, amount);
return send;
}
catch (error) {
return error;
}
}
async upload(data, attachment) {
let output = new Promise(resolve => {
$.ajax({
url: "https://hns.chat/upload",
type: "POST",
data: data,
cache: false,
contentType: false,
processData: false,
beforeSend: (e) => {},
xhr: () => {
let p = $.ajaxSettings.xhr();
p.upload.onprogress = () => {}
return p;
},
success: (response) => {
let json = JSON.parse(response);
resolve(json);
}
});
});
return await output;
}
deleteAttachment(id) {
let data = {
action: "deleteAttachment",
id: id
};
return this.api(data);
}
mentionsMe(message) {
if (message.includes(`@${this.domain}`)) {
return true;
}
return false;
}
replyingToMe(message) {
if (message.p_user && message.p_user == this.domain) {
return true;
}
return false;
}
}
new HNSChat();