]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
json: fix array parsing
authorTimePath <andrew.hardaker1995@gmail.com>
Thu, 31 Mar 2016 03:53:13 +0000 (14:53 +1100)
committerTimePath <andrew.hardaker1995@gmail.com>
Thu, 31 Mar 2016 04:07:08 +0000 (15:07 +1100)
matrix.org integration

16 files changed:
qcsrc/lib/_all.inc
qcsrc/lib/_mod.inc
qcsrc/lib/json.qc
qcsrc/lib/matrix/_mod.inc [new file with mode: 0644]
qcsrc/lib/matrix/command.qc [new file with mode: 0644]
qcsrc/lib/matrix/command.qh [new file with mode: 0644]
qcsrc/lib/matrix/matrix.qc [new file with mode: 0644]
qcsrc/lib/matrix/matrix.qh [new file with mode: 0644]
qcsrc/lib/test.qh
qcsrc/lib/urllib.qc
qcsrc/menu/_mod.inc
qcsrc/menu/matrix.qc [new file with mode: 0644]
qcsrc/server/_mod.inc
qcsrc/server/cl_player.qc
qcsrc/server/matrix.qc [new file with mode: 0644]
qcsrc/server/matrix.qh [new file with mode: 0644]

index fb7122fd8be2c5f5b6a3d7ee8dd7dc0bf059922c..c5e020660d3980f5f029bf97af8112b17603f6f6 100644 (file)
@@ -103,3 +103,5 @@ void    isnt_bool(   float this) { print(ftos(this)); }
 #include "urllib.qc"
 #include "vector.qh"
 #include "yenc.qh"
+
+#include "matrix/_mod.inc"
index fd811e7a9caaa642ab6e60f96217b50ba0532974..ecec32a053299816c41560b64faa6d92e013d4f0 100644 (file)
@@ -1,5 +1,6 @@
 // generated file; do not modify
 #include "angle.qc"
+#include "json.qc"
 #include "p2mathlib.qc"
 #include "random.qc"
 #include "sortlist.qc"
index 555c4b10cc75a6877eb3450bedcde8a29aab87cb..acdf198e835bc83206df152766913225a27bb90c 100644 (file)
@@ -78,7 +78,7 @@ bool _json_parse_array() {
         int it = bufstr_add(_json_buffer, key, 0);
         bool ret = false; WITH(string, _json_ns, key, ret = _json_parse_value());
         if (!ret) {
-            bufstr_set(_json_buffer, it, string_null);
+            bufstr_free(_json_buffer, it);
             if (required) JSON_FAIL("expected value"); else break;
         }
         bufstr_set(_json_buffer, len, ftos(n + 1));
@@ -157,6 +157,7 @@ bool _json_parse_string(bool add) {
                 case 'n': esc = "\n"; break;
                 case 't': esc = "\t"; break;
                 case 'u': esc = "\\u"; break; // TODO
+                case '/': esc = "/"; break;
             }
             s = strcat(s, esc);
         } else {
@@ -271,6 +272,13 @@ void json_del(int buf)
     buf_del(buf);
 }
 
+void json_dump(int buf)
+{
+    for (int i = 0, n = buf_getsize(buf); i < n; ++i) {
+        print(bufstr_get(buf, i), "\n");
+    }
+}
+
 #undef JSON_BEGIN
 #undef JSON_FAIL
 #undef JSON_END
@@ -287,8 +295,6 @@ TEST(json, Parse)
     print(s, "\n");
     int buf = json_parse(s, _json_parse_object);
     EXPECT_NE(-1, buf);
-    for (int i = 0, n = buf_getsize(buf); i < n; ++i) {
-        print(bufstr_get(buf, i), "\n");
-    }
+    json_dump(buf);
     SUCCEED();
 }
diff --git a/qcsrc/lib/matrix/_mod.inc b/qcsrc/lib/matrix/_mod.inc
new file mode 100644 (file)
index 0000000..84522f1
--- /dev/null
@@ -0,0 +1,3 @@
+// generated file; do not modify
+#include "command.qc"
+#include "matrix.qc"
diff --git a/qcsrc/lib/matrix/command.qc b/qcsrc/lib/matrix/command.qc
new file mode 100644 (file)
index 0000000..27c7ec9
--- /dev/null
@@ -0,0 +1,48 @@
+#include "command.qh"
+
+#include <common/command/all.qh>
+
+GENERIC_COMMAND(mx, "Send a matrix command") {
+    switch (argv(1)) {
+        case "user":
+            if (matrix_user) strunzone(matrix_user);
+            matrix_user = strzone(substring(command, argv_start_index(2), -1));
+            break;
+        case "token":
+            if (matrix_access_token) strunzone(matrix_access_token);
+            matrix_access_token = strzone(substring(command, argv_start_index(2), -1));
+            break;
+        case "messages":
+            MX_Messages(string_null);
+            break;
+        case "nick":
+            MX_Nick(substring(command, argv_start_index(2), -1));
+            break;
+        case "join":
+            string s = substring(command, argv_start_index(2), -1);
+            if (s != matrix_room && matrix_room != "") {
+                MX_Leave(matrix_room);
+                strunzone(matrix_room);
+            }
+            matrix_room = strzone(s);
+            MX_Join(matrix_room);
+            break;
+        case "sync":
+            MX_Sync(string_null);
+            break;
+        case "typing":
+            MX_Typing(true);
+            break;
+        case "say":
+            MX_Say(substring(command, argv_start_index(2), -1));
+            break;
+        case "leave":
+            MX_Leave(matrix_room);
+            matrix_room = string_null;
+            break;
+        case "forget":
+            MX_Forget(matrix_room);
+            matrix_room = "";
+            break;
+    }
+}
diff --git a/qcsrc/lib/matrix/command.qh b/qcsrc/lib/matrix/command.qh
new file mode 100644 (file)
index 0000000..7645954
--- /dev/null
@@ -0,0 +1,3 @@
+#pragma once
+
+#include "matrix.qh"
diff --git a/qcsrc/lib/matrix/matrix.qc b/qcsrc/lib/matrix/matrix.qc
new file mode 100644 (file)
index 0000000..a5ae3dc
--- /dev/null
@@ -0,0 +1,204 @@
+#include "matrix.qh"
+
+.string message;
+
+void MX_Nick_(entity fh, entity pass, int status);
+void MX_Nick(string name)
+{
+    entity pass = new_pure(mx);
+    pass.message = name;
+    url_single_fopen(
+        sprintf("%s/_matrix/client/r0/profile/%s/displayname?access_token=%s", autocvar_matrix_server, matrix_user, matrix_access_token),
+        FILE_WRITE,
+        MX_Nick_,
+        pass
+    );
+}
+void MX_Nick_(entity fh, entity pass, int status)
+{
+    switch (status) {
+        case URL_READY_CANWRITE: {
+            fh.url_verb = "PUT";
+            fh.url_content_type = "application/json";
+            url_fputs(fh, sprintf("{\"displayname\": \"%s\"}", pass.message));
+            remove(pass);
+            url_fclose(fh);
+            break;
+        }
+    }
+}
+
+
+void MX_Messages_(entity fh, entity pass, int status);
+void MX_Messages(string from)
+{
+    string s = sprintf("%s/_matrix/client/r0/events?room_id=%s&limit=50&timeout=30000&from=%s&access_token=%s", autocvar_matrix_server, matrix_room, from, matrix_access_token);
+    url_single_fopen(
+        s,
+        FILE_READ,
+        MX_Messages_,
+        NULL
+    );
+}
+void MX_Messages_(entity fh, entity pass, int status)
+{
+    switch (status) {
+        default: {
+            LOG_WARNINGF("status: %d", status);
+            break;
+        }
+        case URL_READY_CLOSED: break;
+        case URL_READY_CANREAD: {
+            string json = "";
+            for (string s; (s = url_fgets(fh)); ) { json = strcat(json, s, "\n"); }
+            url_fclose(fh);
+            int buf = json_parse(json, _json_parse_object);
+            EXPECT_NE(-1, buf);
+            for (int i = 0, n = stof(json_get(buf, "chunk.length")); i < n; ++i) {
+                MX_Handle(buf, sprintf("chunk.%d", i));
+            }
+            MX_Messages(json_get(buf, "end"));
+            break;
+        }
+    }
+}
+
+
+void MX_Sync_(entity fh, entity pass, int status);
+void MX_Sync(string since)
+{
+    string s = strcat(autocvar_matrix_server, "/_matrix/client/r0/sync?");
+    if (since) {
+        s = strcat(s,
+            "since=", since, "&",
+            "timeout=30000&",
+            sprintf("filter={\"account_data\":{\"types\":[]},\"presence\":{\"types\":[]},\"room\":{\"rooms\":[\"%s\"]}}&", matrix_room)
+        );
+    } else {
+        s = strcat(s,
+            "timeout=0&",
+            "filter={\"account_data\":{\"types\":[]},\"presence\":{\"types\":[]},\"room\":{\"rooms\":[]}}&"
+        );
+    }
+    s = strcat(s, "access_token=", matrix_access_token);
+    url_single_fopen(s, FILE_READ, MX_Sync_, NULL);
+}
+void MX_Sync_(entity fh, entity pass, int status)
+{
+    switch (status) {
+        default: {
+            LOG_WARNINGF("status: %d", status);
+            break;
+        }
+        case URL_READY_CLOSED: break;
+        case URL_READY_CANREAD: {
+            string json = "";
+            for (string s; (s = url_fgets(fh)); ) { json = strcat(json, s, "\n"); }
+            url_fclose(fh);
+            int buf = json_parse(json, _json_parse_object);
+            EXPECT_NE(-1, buf);
+            string arr = sprintf("rooms.join.%s.timeline.events", matrix_room);
+            for (int i = 0, n = stof(json_get(buf, sprintf("%s.length", arr))); i < n; ++i) {
+                MX_Handle(buf, sprintf("%s.%d", arr, i));
+            }
+            MX_Sync(json_get(buf, "next_batch"));
+            break;
+        }
+    }
+}
+
+
+void MX_JLF_(entity fh, entity pass, int status);
+void MX_Join(string room)
+{
+    url_single_fopen(
+        sprintf("%s/_matrix/client/r0/rooms/%s/join?access_token=%s", autocvar_matrix_server, matrix_room, matrix_access_token),
+        FILE_WRITE,
+        MX_JLF_,
+        NULL
+    );
+}
+void MX_Leave(string room)
+{
+    url_single_fopen(
+        sprintf("%s/_matrix/client/r0/rooms/%s/leave?access_token=%s", autocvar_matrix_server, matrix_room, matrix_access_token),
+        FILE_WRITE,
+        MX_JLF_,
+        NULL
+    );
+}
+void MX_Forget(string room)
+{
+    url_single_fopen(
+        sprintf("%s/_matrix/client/r0/rooms/%s/forget?access_token=%s", autocvar_matrix_server, matrix_room, matrix_access_token),
+        FILE_WRITE,
+        MX_JLF_,
+        NULL
+    );
+}
+void MX_JLF_(entity fh, entity pass, int status)
+{
+    switch (status) {
+        case URL_READY_CANWRITE: {
+            fh.url_content_type = "application/json";
+            url_fputs(fh, sprintf("{}", pass.message));
+            url_fclose(fh);
+            break;
+        }
+    }
+}
+
+
+void MX_Typing_(entity fh, entity pass, int status);
+void MX_Typing(bool state)
+{
+    entity pass = new_pure(mx);
+    pass.message = state ? "true" : "false";
+    url_single_fopen(
+        sprintf("%s/_matrix/client/r0/rooms/%s/typing/%s?access_token=%s", autocvar_matrix_server, matrix_room, matrix_user, matrix_access_token),
+        FILE_WRITE,
+        MX_Typing_,
+        pass
+    );
+}
+void MX_Typing_(entity fh, entity pass, int status)
+{
+    switch (status) {
+        case URL_READY_CANWRITE: {
+            fh.url_verb = "PUT";
+            fh.url_content_type = "application/json";
+            url_fputs(fh, sprintf("{\"typing\": %s, \"timeout\": 30000}", pass.message));
+            remove(pass);
+            url_fclose(fh);
+            break;
+        }
+    }
+}
+
+
+void MX_Say_(entity fh, entity pass, int status);
+void MX_Say(string body)
+{
+    static int txnid;
+    entity pass = new_pure(mx);
+    pass.message = strzone(body);
+    url_single_fopen(
+        sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message/%d?access_token=%s", autocvar_matrix_server, matrix_room, ++txnid, matrix_access_token),
+        FILE_WRITE,
+        MX_Say_,
+        pass
+    );
+}
+void MX_Say_(entity fh, entity pass, int status)
+{
+    switch (status) {
+        case URL_READY_CANWRITE: {
+            fh.url_verb = "PUT";
+            fh.url_content_type = "application/json";
+            url_fputs(fh, sprintf("{\"msgtype\": \"m.text\", \"body\": \"%s\"}", pass.message));
+            strunzone(pass.message); remove(pass);
+            url_fclose(fh);
+            break;
+        }
+    }
+}
diff --git a/qcsrc/lib/matrix/matrix.qh b/qcsrc/lib/matrix/matrix.qh
new file mode 100644 (file)
index 0000000..076d8a2
--- /dev/null
@@ -0,0 +1,17 @@
+#pragma once
+
+string autocvar_matrix_server = "http://matrix.org";
+string matrix_user;
+string matrix_access_token;
+string matrix_room;
+
+void MX_Messages(string from);
+void MX_Nick(string name);
+void MX_Join(string room);
+void MX_Sync(string since);
+void MX_Typing(bool state);
+void MX_Say(string body);
+void MX_Leave(string room);
+void MX_Forget(string room);
+
+var void(int buf, string ancestor) MX_Handle;
index af00f979ca0175dd8a5316e64525763b819adbf4..d05ae28cd903b76edc4770204b142d5f8689c589 100644 (file)
@@ -46,19 +46,19 @@ bool RUN_ALL_TESTS();
 #define EXPECT_FALSE(condition) EXPECT_EQ(false, condition)
 #define ASSERT_FALSE(condition) ASSERT_EQ(false, condition)
 
-#define EXPECT_NE(val1, val2) EXPECT_TRUE(val1 != val2)
+#define EXPECT_NE(val1, val2) EXPECT_TRUE((val1) != (val2))
 #define ASSERT_NE(val1, val2) _TEST_ASSERT(EXPECT_NE(val1, val2))
 
-#define EXPECT_LT(val1, val2) EXPECT_TRUE(val1 < val2)
+#define EXPECT_LT(val1, val2) EXPECT_TRUE((val1) < (val2))
 #define ASSERT_LT(val1, val2) _TEST_ASSERT(EXPECT_LT(val1, val2))
 
-#define EXPECT_LE(val1, val2) EXPECT_TRUE(val1 <= val2)
+#define EXPECT_LE(val1, val2) EXPECT_TRUE((val1) <= (val2))
 #define ASSERT_LE(val1, val2) _TEST_ASSERT(EXPECT_LE(val1, val2))
 
-#define EXPECT_GT(val1, val2) EXPECT_TRUE(val1 > val2)
+#define EXPECT_GT(val1, val2) EXPECT_TRUE((val1) > (val2))
 #define ASSERT_GT(val1, val2) _TEST_ASSERT(EXPECT_GT(val1, val2))
 
-#define EXPECT_GE(val1, val2) EXPECT_TRUE(val1 >= val2)
+#define EXPECT_GE(val1, val2) EXPECT_TRUE((val1) >= (val2))
 #define ASSERT_GE(val1, val2) _TEST_ASSERT(EXPECT_GE(val1, val2))
 
 #define EXPECT_NO_FATAL_FAILURE(statement) EXPECT_NO_FATAL_FAILURE_(statement, { })
index 80145d8b84c15142b97be992968e38601e1f7c5f..c4c700a72d6b8341e9e561af642d638853c78e9d 100644 (file)
@@ -7,6 +7,8 @@ const float URL_FH_STDOUT = -2;
 
 // URLs
 .string url_url;
+.string url_content_type;
+.string url_verb;
 .float url_wbuf;
 .float url_wbufpos;
 .float url_rbuf;
@@ -96,6 +98,8 @@ void url_single_fopen(string url, int mode, url_ready_func rdy, entity pass)
                                // create a writing end that does nothing yet
                                e = new_pure(url_single_fopen_file);
                                e.url_url = strzone(url);
+                               e.url_content_type = "text/plain";
+                               e.url_verb = "";
                                e.url_fh = URL_FH_CURL;
                                e.url_wbuf = buf_create();
                                if (e.url_wbuf < 0)
@@ -231,7 +235,7 @@ void url_fclose(entity e)
                        }
 
                        // POST the data
-                       if (!crypto_uri_postbuf(e.url_url, i + MIN_URL_ID, "text/plain", "", e.url_wbuf, 0))
+                       if (!crypto_uri_postbuf(e.url_url, i + MIN_URL_ID, e.url_content_type, e.url_verb, e.url_wbuf, 0))
                        {
                                LOG_INFO("url_fclose: failure in crypto_uri_postbuf\n");
                                e.url_ready(e, e.url_ready_pass, URL_READY_ERROR);
index 589808ec117bdeb2cf8924137193e0c4bc34766d..8fdd71d84cfc8388c4d0d472aa77c9f047ca3f55 100644 (file)
@@ -1,4 +1,5 @@
 // generated file; do not modify
 #include "draw.qc"
 #include "item.qc"
+#include "matrix.qc"
 #include "menu.qc"
diff --git a/qcsrc/menu/matrix.qc b/qcsrc/menu/matrix.qc
new file mode 100644 (file)
index 0000000..b0c4ec8
--- /dev/null
@@ -0,0 +1,26 @@
+var void MX_Handle(int buf, string ancestor)
+{
+    string type = json_get(buf, strcat(ancestor, ".type"));
+    switch (type) {
+        case "m.typing": {
+            string arr = strcat(ancestor, ".content.user_ids");
+            for (int i = 0, n = stof(json_get(buf, sprintf("%s.length", arr))); i < n; ++i) {
+                string s = json_get(buf, sprintf("%s.%d", arr, i));
+                print("\{1}", s, " is typing...\n");
+            }
+            break;
+        }
+        case "m.room.message": {
+            string msgtype = json_get(buf, strcat(ancestor, ".content.msgtype"));
+            switch (msgtype) {
+                case "m.text": {
+                    string sender = json_get(buf, strcat(ancestor, ".sender"));
+                    string body = json_get(buf, strcat(ancestor, ".content.body"));
+                    if (body) print("\{1}", sender, ": ", body, "\n");
+                    break;
+                }
+            }
+            break;
+        }
+    }
+}
index d502c48ed35b35c8f28c2806364bb0fd13db00bb..9d0531ac62c5a9bca1211a1bfb71a29bf7797a0b 100644 (file)
@@ -15,6 +15,7 @@
 #include "ipban.qc"
 #include "item_key.qc"
 #include "mapvoting.qc"
+#include "matrix.qc"
 #include "miscfunctions.qc"
 #include "playerdemo.qc"
 #include "portals.qc"
index 42904695c693b35e39835bed217e3c78ef2635c8..a749ec2def81b8b60c1faaf8b9bf55d4270fda0a 100644 (file)
@@ -664,20 +664,14 @@ void MoveToTeam(entity client, int team_colour, int type)
  *   0 = reject
  *  -1 = fake accept
  */
-int Say(entity source, float teamsay, entity privatesay, string msgin, bool floodcontrol)
+int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
 {
-       string msgstr, colorstr, cmsgstr, namestr, fullmsgstr, sourcemsgstr, fullcmsgstr, sourcecmsgstr, colorprefix;
-       float flood;
-       var .float flood_field;
-       float ret;
-       string privatemsgprefix = string_null; float privatemsgprefixlen = 0;
-
-       if(!teamsay && !privatesay)
-               if(substring(msgin, 0, 1) == " ")
-                       msgin = substring(msgin, 1, strlen(msgin) - 1); // work around DP say bug (say_team does not have this!)
+       if (!teamsay && !privatesay) if (substring(msgin, 0, 1) == " ")
+        msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
 
        msgin = formatmessage(msgin);
 
+    string colorstr;
        if (!IS_PLAYER(source))
                colorstr = "^0"; // black for spectators
        else if(teamplay)
@@ -691,6 +685,11 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
        if(intermission_running)
                teamsay = false;
 
+    if (!source) {
+               colorstr = "";
+               teamsay = false;
+    }
+
        if(msgin != "")
                msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
 
@@ -705,18 +704,18 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
        }
        */
 
-       if(autocvar_g_chat_teamcolors)
-               namestr = playername(source);
-       else
-               namestr = source.netname;
+    string namestr = "";
+    if (source)
+        namestr = autocvar_g_chat_teamcolors ? playername(source) : source.netname;
 
-       if(strdecolorize(namestr) == namestr)
-               colorprefix = "^3";
-       else
-               colorprefix = "^7";
+    string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
 
-       if(msgin != "")
-       {
+    string msgstr, cmsgstr;
+    string privatemsgprefix = string_null;
+    int privatemsgprefixlen = 0;
+       if (msgin == "") {
+        msgstr = cmsgstr = "";
+       } else {
                if(privatesay)
                {
                        msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
@@ -750,23 +749,22 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
                                msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
                                msgstr = strcat("\{1}^4* ", "^7", msgin);
                        }
-                       else
-                               msgstr = strcat("\{1}", colorprefix, namestr, "^7: ", msgin);
+                       else {
+                msgstr = "\{1}";
+                msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
+                msgstr = strcat(msgstr, msgin);
+            }
                        cmsgstr = "";
                }
                msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
        }
-       else
-       {
-               msgstr = cmsgstr = "";
-       }
 
-       fullmsgstr = msgstr;
-       fullcmsgstr = cmsgstr;
+       string fullmsgstr = msgstr;
+       string fullcmsgstr = cmsgstr;
 
        // FLOOD CONTROL
-       flood = 0;
-       flood_field = floodcontrol_chat;
+       int flood = 0;
+       var .float flood_field = floodcontrol_chat;
        if(floodcontrol)
        {
                float flood_spl;
@@ -838,6 +836,7 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
                        source.(flood_field) = flood = 0;
        }
 
+    string sourcemsgstr, sourcecmsgstr;
        if(flood == 2) // cannot happen for empty msgstr
        {
                if(autocvar_g_chat_flood_notify_flooder)
@@ -873,6 +872,7 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
        if(privatesay)
                sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
 
+    int ret;
        if(source.muted)
        {
                // always fake the message
@@ -913,7 +913,7 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
                {
                        sprint(source, sourcemsgstr);
                        dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame, LAMBDA(sprint(it, msgstr)));
+                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame, sprint(it, msgstr));
                }
                else if(teamsay > 0) // team message, only sent to team mates
                {
@@ -921,26 +921,28 @@ int Say(entity source, float teamsay, entity privatesay, string msgin, bool floo
                        dedicated_print(msgstr); // send to server console too
                        if(sourcecmsgstr != "")
                                centerprint(source, sourcecmsgstr);
-                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team, LAMBDA(
+                       FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team, {
                                sprint(it, msgstr);
                                if(cmsgstr != "")
                                        centerprint(it, cmsgstr);
-                       ));
+                       });
                }
                else if(teamsay < 0) // spectator message, only sent to spectators
                {
                        sprint(source, sourcemsgstr);
                        dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source, LAMBDA(sprint(it, msgstr)));
+                       FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr));
                }
-               else if(sourcemsgstr != msgstr) // trimmed/server fixed message, sent to all players
-               {
-                       sprint(source, sourcemsgstr);
-                       dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source, LAMBDA(sprint(it, msgstr)));
-               }
-               else
-                       bprint(msgstr); // entirely normal message, sent to all players -- bprint sends to server console too.
+               else {
+            if (sourcemsgstr != msgstr) { // trimmed/server fixed message, sent to all players
+                sprint(source, sourcemsgstr);
+                dedicated_print(msgstr); // send to server console too
+                FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr));
+            } else { // entirely normal message, sent to all players -- bprint sends to server console too.
+                bprint(msgstr);
+            }
+            if (source) MX_Say(strcat(playername(source), "^7: ", msgin));
+        }
        }
 
        return ret;
diff --git a/qcsrc/server/matrix.qc b/qcsrc/server/matrix.qc
new file mode 100644 (file)
index 0000000..b7d26de
--- /dev/null
@@ -0,0 +1,22 @@
+#include "matrix.qh"
+
+#include "cl_player.qh"
+
+var void MX_Handle(int buf, string ancestor)
+{
+    string type = json_get(buf, strcat(ancestor, ".type"));
+    switch (type) {
+        case "m.room.message": {
+            string msgtype = json_get(buf, strcat(ancestor, ".content.msgtype"));
+            switch (msgtype) {
+                case "m.text": {
+                    string sender = json_get(buf, strcat(ancestor, ".sender"));
+                    string body = json_get(buf, strcat(ancestor, ".content.body"));
+                    if (sender != matrix_user && body) Say(NULL, false, NULL, body, false);
+                    break;
+                }
+            }
+            break;
+        }
+    }
+}
diff --git a/qcsrc/server/matrix.qh b/qcsrc/server/matrix.qh
new file mode 100644 (file)
index 0000000..6f70f09
--- /dev/null
@@ -0,0 +1 @@
+#pragma once