#include "urllib.qc"
#include "vector.qh"
#include "yenc.qh"
+
+#include "matrix/_mod.inc"
// generated file; do not modify
#include "angle.qc"
+#include "json.qc"
#include "p2mathlib.qc"
#include "random.qc"
#include "sortlist.qc"
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));
case 'n': esc = "\n"; break;
case 't': esc = "\t"; break;
case 'u': esc = "\\u"; break; // TODO
+ case '/': esc = "/"; break;
}
s = strcat(s, esc);
} else {
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
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();
}
--- /dev/null
+// generated file; do not modify
+#include "command.qc"
+#include "matrix.qc"
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#pragma once
+
+#include "matrix.qh"
--- /dev/null
+#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;
+ }
+ }
+}
--- /dev/null
+#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;
#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, { })
// URLs
.string url_url;
+.string url_content_type;
+.string url_verb;
.float url_wbuf;
.float url_wbufpos;
.float url_rbuf;
// 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)
}
// 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);
// generated file; do not modify
#include "draw.qc"
#include "item.qc"
+#include "matrix.qc"
#include "menu.qc"
--- /dev/null
+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;
+ }
+ }
+}
#include "ipban.qc"
#include "item_key.qc"
#include "mapvoting.qc"
+#include "matrix.qc"
#include "miscfunctions.qc"
#include "playerdemo.qc"
#include "portals.qc"
* 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)
if(intermission_running)
teamsay = false;
+ if (!source) {
+ colorstr = "";
+ teamsay = false;
+ }
+
if(msgin != "")
msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
}
*/
- 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");
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;
source.(flood_field) = flood = 0;
}
+ string sourcemsgstr, sourcecmsgstr;
if(flood == 2) // cannot happen for empty msgstr
{
if(autocvar_g_chat_flood_notify_flooder)
if(privatesay)
sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
+ int ret;
if(source.muted)
{
// always fake the message
{
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
{
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;
--- /dev/null
+#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;
+ }
+ }
+}
--- /dev/null
+#pragma once