summaryrefslogtreecommitdiff
path: root/scripts/chat.js
blob: 2c0eaad3a60c2aa1f4368b77cb9db03741ffb166 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
function parseIRCMessage(line) {
  line = line.replace(/\r?\n$/, '');
  let tags = {};
  let prefix = null;
  let nick = null;
  let command = null;
  const params = [];

  // tags
  if (line[0] === '@') {
    const end = line.indexOf(' ');
    const rawTags = line.slice(1, end);
    for (const tag of rawTags.split(';')) {
      const [k, v = true] = tag.split('=');
      tags[k] = v === true ? true : v;
    }
    line = line.slice(end + 1);
  }

  // prefix
  if (line[0] === ':') {
    const end = line.indexOf(' ');
    prefix = line.slice(1, end);
    const bang = prefix.indexOf('!');
    if (bang !== -1) nick = prefix.slice(0, bang);
    line = line.slice(end + 1);
  }

  // command and params
  const parts = line.split(' ');
  command = parts.shift();
  for (let i = 0; i < parts.length; i++) {
    if (parts[i][0] === ':') {
      params.push(parts.slice(i).join(' ').slice(1));
      break;
    }
    if (parts[i].length) params.push(parts[i]);
  }

  return { tags, prefix, nick, command, params };
}

function addMessage(message) {
    const messages = document.getElementById("messages");
    if (!messages || message.command != "PRIVMSG") {
        return;
    }
    
    // msg base
    const elem = document.createElement("p");
    elem.classList.add("message");
    
    // badges
    if ("badges" in message.tags && message.tags["badges"].length > 0) {
        for (const b of message.tags["badges"].split(",")) {
            if (b in badges) {
                const badgeImg = document.createElement("img");
                badgeImg.src = badges[b];
                badgeImg.loading = 'lazy';
                elem.append(badgeImg);
            }
        }
    }
    
    // username
    const usernameElem = document.createElement("span");
    elem.append(usernameElem);
    if ("color" in message.tags) {
        usernameElem.style.color = message.tags["color"];
    }
    usernameElem.classList.add("author");
    usernameElem.textContent = `${message.nick}:`;
    
    // message text
    elem.innerHTML += ' ' + message.params[1];
    
    messages.append(elem);
    
    window.scrollTo(0, document.body.scrollHeight);
    
    // remove old messages
    if (messages.childElementCount > 100) {
        for (let i = 0; i < messages.childElementCount - 100; i++) {
            messages.childNodes[i].remove();
        }
    }
}

function addSystemMessage(message) {
    addMessage({
        "params": ["#", message],
        "nick": "System",
        "prefix": "system",
        "tags": {},
        "command": "PRIVMSG",
    });
}

function connectToChat(host, nick, password, room) {
    const socket = new WebSocket(host);
    
    socket.addEventListener("open", () => {
        socket.send(`NICK ${nick}`);
        socket.send(`PASS ${password}`);
        socket.send("CAP REQ :twitch.tv/tags");
        socket.send(`JOIN #${room}`);
        addSystemMessage("Connected to the chat!");
    });
    
    socket.addEventListener("message", (e) => {
        const lines = e.data.split("\r\n");
        
        for (const line of lines) {
            const l = line.trim();
            if (l.length == 0) {
                continue;
            }
            addMessage(parseIRCMessage(l));
        }
    });
    
    socket.addEventListener("close", (e) => {
        addMessage("Chat", `Disconnected from the chat: ${e.reason}`);
        addMessage("Chat", "Reconnecting to the chat in 5 seconds...");
        setTimeout(() => connectToChat(host, nick, password, room), 5000);
    });
}