Commit 66dd7167 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Implement private messages.

parent bd5cd7c1
......@@ -138,6 +138,7 @@ Typing a line starting with a slash `/` in the chat dialogue causes
a command to be sent to the server. The following commands are available
to all users:
- `/msg user text`: sends a private message;
- `/me text`: sends a chat message starting with the sender's username;
- `/leave`: equivalent to clicking the *Disconnect* button.
- `/set var val`: sets the value of a configuration variable without any
......
......@@ -141,6 +141,7 @@ type clientMessage struct {
Type string `json:"type"`
Kind string `json:"kind,omitempty"`
Id string `json:"id,omitempty"`
Dest string `json:"dest,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Permissions group.ClientPermissions `json:"permissions,omitempty"`
......@@ -158,14 +159,14 @@ func fromJSTime(tm uint64) time.Time {
if tm == 0 {
return time.Time{}
}
return time.Unix(int64(tm)/1000, (int64(tm)%1000) * 1000000)
return time.Unix(int64(tm)/1000, (int64(tm)%1000)*1000000)
}
func toJSTime(tm time.Time) uint64 {
if tm.Before(time.Unix(0, 0)) {
return 0
}
return uint64((tm.Sub(time.Unix(0, 0)) + time.Millisecond / 2) / time.Millisecond)
return uint64((tm.Sub(time.Unix(0, 0)) + time.Millisecond/2) / time.Millisecond)
}
type closeMessage struct {
......@@ -292,7 +293,7 @@ func addDownConn(c *webClient, id string, remote conn.Up) (*rtpDownConnection, e
return conn, err
}
func addDownConnHelper(c *webClient, conn *rtpDownConnection, remote conn.Up) (error) {
func addDownConnHelper(c *webClient, conn *rtpDownConnection, remote conn.Up) error {
c.mu.Lock()
defer c.mu.Unlock()
......@@ -1048,23 +1049,38 @@ func handleClientMessage(c *webClient, m clientMessage) error {
}
case "chat":
tm := toJSTime(time.Now())
c.group.AddToChatHistory(
m.Id, m.Username, tm, m.Kind, m.Value,
)
if m.Dest == "" {
c.group.AddToChatHistory(
m.Id, m.Username, tm, m.Kind, m.Value,
)
}
mm := clientMessage{
Type: "chat",
Id: m.Id,
Dest: m.Dest,
Username: m.Username,
Time: tm,
Kind: m.Kind,
Value: m.Value,
}
clients := c.group.GetClients(nil)
for _, cc := range clients {
cc, ok := cc.(*webClient)
if ok {
cc.write(mm)
if m.Dest == "" {
clients := c.group.GetClients(nil)
for _, cc := range clients {
ccc, ok := cc.(*webClient)
if ok {
ccc.write(mm)
}
}
} else {
cc := c.group.GetClient(m.Dest)
if cc == nil {
return c.error(group.UserError("user unknown"))
}
ccc, ok := cc.(*webClient)
if !ok {
return c.error(group.UserError("this user doesn't chat"))
}
ccc.write(mm)
}
case "groupaction":
switch m.Kind {
......
......@@ -124,7 +124,7 @@ function ServerConnection() {
/**
* onchat is called whenever a new chat message is received.
*
* @type {(this: ServerConnection, id: string, username: string, time: number, kind: string, message: string) => void}
* @type {(this: ServerConnection, id: string, dest: string, username: string, time: number, kind: string, message: string) => void}
*/
this.onchat = null;
/**
......@@ -148,6 +148,7 @@ function ServerConnection() {
* @property {string} type
* @property {string} [kind]
* @property {string} [id]
* @property {string} [dest]
* @property {string} [username]
* @property {string} [password]
* @property {Object<string,boolean>} [permissions]
......@@ -285,7 +286,7 @@ ServerConnection.prototype.connect = async function(url) {
case 'chat':
if(sc.onchat)
sc.onchat.call(
sc, m.id, m.username, m.time, m.kind, m.value,
sc, m.id, m.dest, m.username, m.time, m.kind, m.value,
);
break;
case 'clearchat':
......@@ -428,10 +429,11 @@ ServerConnection.prototype.newUpStream = function(id) {
* @param {string} kind - The kind of message, either "" or "me".
* @param {string} message - The text of the message.
*/
ServerConnection.prototype.chat = function(username, kind, message) {
ServerConnection.prototype.chat = function(username, kind, dest, message) {
this.send({
type: 'chat',
id: this.id,
dest: dest,
username: username,
kind: kind,
value: message,
......
......@@ -297,6 +297,15 @@ textarea.form-reply {
background: #ececec;
}
.message-private {
background: white;
}
.message-private .message-header:after {
content: "(private)";
margin-left: 1em;
}
.message-system {
font-size: 10px;
background: #ececec;
......
......@@ -1227,6 +1227,7 @@ function formatTime(time) {
* @typedef {Object} lastMessage
* @property {string} [nick]
* @property {string} [peerId]
* @property {string} [dest]
*/
/** @type {lastMessage} */
......@@ -1239,7 +1240,7 @@ let lastMessage = {};
* @param {string} kind
* @param {string} message
*/
function addToChatbox(peerId, nick, time, kind, message){
function addToChatbox(peerId, dest, nick, time, kind, message) {
let userpass = getUserPass();
let row = document.createElement('div');
row.classList.add('message-row');
......@@ -1248,12 +1249,16 @@ function addToChatbox(peerId, nick, time, kind, message){
row.appendChild(container);
if(!peerId)
container.classList.add('message-system');
else if(userpass.username === nick) {
if(userpass.username === nick)
container.classList.add('message-sender');
}
if(dest)
container.classList.add('message-private');
if(kind !== 'me') {
let p = formatLines(message.split('\n'));
if (lastMessage.nick !== nick || lastMessage.peerId !== peerId) {
if(lastMessage.nick !== nick ||
lastMessage.peerId !== peerId ||
lastMessage.dest !== (dest || null)) {
let header = document.createElement('p');
let user = document.createElement('span');
user.textContent = nick;
......@@ -1272,6 +1277,7 @@ function addToChatbox(peerId, nick, time, kind, message){
container.appendChild(p);
lastMessage.nick = nick;
lastMessage.peerId = peerId;
lastMessage.dest = (dest || null);
} else {
let asterisk = document.createElement('span');
asterisk.textContent = '*';
......@@ -1288,8 +1294,7 @@ function addToChatbox(peerId, nick, time, kind, message){
container.appendChild(user);
container.appendChild(content);
container.classList.add('message-me');
delete(lastMessage.nick);
delete(lastMessage.peerId);
lastMessage = {};
}
let box = document.getElementById('box');
......@@ -1387,7 +1392,7 @@ function handleInput() {
let s = "";
for(let key in settings)
s = s + `${key}: ${JSON.stringify(settings[key])}\n`
addToChatbox(null, null, Date.now(), null, s);
addToChatbox(null, null, null, Date.now(), null, s);
return;
}
let parsed = parseCommand(rest);
......@@ -1424,15 +1429,12 @@ function handleInput() {
}
serverConnection.groupAction(cmd.slice(1));
return;
case '/msg':
case '/op':
case '/unop':
case '/kick':
case '/present':
case '/unpresent': {
if(!serverConnection.permissions.op) {
displayError("You're not an operator");
return;
}
let parsed = parseCommand(rest);
let id;
if(parsed[0] in users) {
......@@ -1449,7 +1451,18 @@ function handleInput() {
displayError('Unknown user ' + parsed[0]);
return;
}
serverConnection.userAction(cmd.slice(1), id, parsed[1]);
if(cmd === '/msg') {
let username = getUsername();
if(!username) {
displayError("Sorry, you're anonymous, you cannot chat");
return;
}
serverConnection.chat(username, '', id, parsed[1]);
addToChatbox(serverConnection.id,
id, username, Date.now(), '', parsed[1]);
} else {
serverConnection.userAction(cmd.slice(1), id, parsed[1]);
}
return;
}
default:
......@@ -1474,7 +1487,7 @@ function handleInput() {
}
try {
serverConnection.chat(username, me ? 'me' : '', message);
serverConnection.chat(username, me ? 'me' : '', '', message);
} catch(e) {
console.error(e);
displayError(e);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment