diff --git a/api/assets/schemas.json b/api/assets/schemas.json
index e27087a9..bfe6092b 100644
--- a/api/assets/schemas.json
+++ b/api/assets/schemas.json
@@ -80,11 +80,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -141,27 +160,7 @@
}
},
"additionalProperties": false,
- "required": [
- "name",
- "type"
- ],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -307,11 +306,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -367,11 +385,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -469,22 +483,6 @@
},
"additionalProperties": false,
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -630,11 +628,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -690,11 +707,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -741,22 +754,6 @@
},
"additionalProperties": false,
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -902,11 +899,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -962,11 +978,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -992,22 +1004,6 @@
},
"additionalProperties": false,
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -1153,11 +1149,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -1213,11 +1228,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -1246,22 +1257,6 @@
"messages"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -1407,11 +1402,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -1467,11 +1481,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -1509,22 +1519,6 @@
"type"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -1670,11 +1664,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -1730,11 +1743,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -1765,22 +1774,6 @@
"name"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -1926,11 +1919,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -1986,11 +1998,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -2016,22 +2024,6 @@
},
"additionalProperties": false,
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -2177,11 +2169,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -2237,11 +2248,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -2279,22 +2286,6 @@
]
},
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -2440,11 +2431,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -2500,11 +2510,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -2555,22 +2561,6 @@
"name"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -2716,11 +2706,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -2776,11 +2785,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -2870,22 +2875,6 @@
"name"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -3031,11 +3020,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -3091,11 +3099,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -3121,22 +3125,6 @@
},
"additionalProperties": false,
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -3282,11 +3270,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -3342,11 +3349,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -3372,22 +3375,6 @@
"nick"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -3533,11 +3520,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -3593,11 +3599,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -3635,22 +3637,6 @@
},
"additionalProperties": false,
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -3796,11 +3782,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -3856,11 +3861,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -3893,22 +3894,6 @@
]
},
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -4054,11 +4039,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -4114,11 +4118,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -4147,22 +4147,6 @@
"name"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -4308,11 +4292,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -4368,11 +4371,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -4401,22 +4400,6 @@
"name"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -4562,11 +4545,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -4622,11 +4624,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -4651,22 +4649,6 @@
},
"additionalProperties": false,
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -4812,11 +4794,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -4872,11 +4873,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -4921,22 +4918,6 @@
"channel_id"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -5082,11 +5063,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -5142,11 +5142,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -5198,22 +5194,6 @@
},
"additionalProperties": false,
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -5359,11 +5339,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -5419,11 +5418,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -5453,22 +5448,6 @@
"enabled"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -5614,11 +5593,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -5674,11 +5672,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -5710,22 +5704,6 @@
"name"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -5871,11 +5849,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -5931,11 +5928,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -5967,22 +5960,6 @@
"recipients"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -6128,11 +6105,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -6188,11 +6184,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -6245,22 +6237,6 @@
},
"additionalProperties": false,
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -6406,11 +6382,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -6466,11 +6461,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -6496,22 +6487,6 @@
"type"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -6657,11 +6632,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -6717,11 +6711,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -6751,22 +6741,6 @@
"username"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -6912,11 +6886,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -6972,11 +6965,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
@@ -7189,22 +7178,6 @@
"timezone_offset"
],
"definitions": {
- "ChannelType": {
- "enum": [
- 0,
- 1,
- 10,
- 11,
- 12,
- 13,
- 2,
- 3,
- 4,
- 5,
- 6
- ],
- "type": "number"
- },
"ChannelPermissionOverwriteType": {
"enum": [
0,
@@ -7350,11 +7323,30 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/ChannelType"
+ "enum": [
+ 0,
+ 1,
+ 10,
+ 11,
+ 12,
+ 13,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6
+ ],
+ "type": "number"
},
"topic": {
"type": "string"
},
+ "icon": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"bitrate": {
"type": "integer"
},
@@ -7410,11 +7402,7 @@
"type": "integer"
}
},
- "additionalProperties": false,
- "required": [
- "name",
- "type"
- ]
+ "additionalProperties": false
},
"RelationshipType": {
"enum": [
diff --git a/api/package-lock.json b/api/package-lock.json
index fd9c68b4..499c612e 100644
--- a/api/package-lock.json
+++ b/api/package-lock.json
@@ -11,6 +11,7 @@
"license": "ISC",
"dependencies": {
"@fosscord/util": "file:../util",
+ "@types/morgan": "^1.9.3",
"ajv": "8.6.2",
"ajv-formats": "^2.1.1",
"amqplib": "^0.8.0",
@@ -34,6 +35,7 @@
"mongoose": "^5.12.3",
"mongoose-autopopulate": "^0.12.3",
"mongoose-long": "^0.3.2",
+ "morgan": "^1.10.0",
"multer": "^1.4.2",
"node-fetch": "^2.6.1",
"patch-package": "^6.4.7",
@@ -1452,6 +1454,14 @@
"@types/node": "*"
}
},
+ "node_modules/@types/morgan": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz",
+ "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/multer": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz",
@@ -2271,6 +2281,22 @@
}
]
},
+ "node_modules/basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "dependencies": {
+ "safe-buffer": "5.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/basic-auth/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
"node_modules/bcrypt": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
@@ -8021,6 +8047,42 @@
"mongoose": "*"
}
},
+ "node_modules/morgan": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
+ "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
+ "dependencies": {
+ "basic-auth": "~2.0.1",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-finished": "~2.3.0",
+ "on-headers": "~1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/morgan/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/morgan/node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/morgan/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
"node_modules/morphdom": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.6.1.tgz",
@@ -8582,6 +8644,14 @@
"node": ">= 0.8"
}
},
+ "node_modules/on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/on-net-listen": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/on-net-listen/-/on-net-listen-1.1.2.tgz",
@@ -14113,6 +14183,14 @@
}
}
},
+ "@types/morgan": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz",
+ "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==",
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/multer": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz",
@@ -14793,6 +14871,21 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
+ "basic-auth": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
+ "requires": {
+ "safe-buffer": "5.1.2"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ }
+ }
+ },
"bcrypt": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
@@ -19500,6 +19593,38 @@
"integrity": "sha512-5gTjPH6HUmtNhamv8MPwExWo01Z4d9CT5njZlupqqbmxzMXTbDOgCuP/jnK+9SV0Fs7nuyYlXv7pJ/nA2pAAuA==",
"requires": {}
},
+ "morgan": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
+ "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
+ "requires": {
+ "basic-auth": "~2.0.1",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-finished": "~2.3.0",
+ "on-headers": "~1.0.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
"morphdom": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.6.1.tgz",
@@ -19964,6 +20089,11 @@
"ee-first": "1.1.1"
}
},
+ "on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
+ },
"on-net-listen": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/on-net-listen/-/on-net-listen-1.1.2.tgz",
diff --git a/api/package.json b/api/package.json
index ad959e57..98b15787 100644
--- a/api/package.json
+++ b/api/package.json
@@ -62,6 +62,7 @@
},
"dependencies": {
"@fosscord/util": "file:../util",
+ "@types/morgan": "^1.9.3",
"ajv": "8.6.2",
"ajv-formats": "^2.1.1",
"amqplib": "^0.8.0",
@@ -85,6 +86,7 @@
"mongoose": "^5.12.3",
"mongoose-autopopulate": "^0.12.3",
"mongoose-long": "^0.3.2",
+ "morgan": "^1.10.0",
"multer": "^1.4.2",
"node-fetch": "^2.6.1",
"patch-package": "^6.4.7",
diff --git a/api/src/Server.ts b/api/src/Server.ts
index b9ca3fba..4a226d12 100644
--- a/api/src/Server.ts
+++ b/api/src/Server.ts
@@ -1,3 +1,4 @@
+import { OptionsJson } from 'body-parser';
import "missing-native-js-functions";
import { Connection } from "mongoose";
import { Server, ServerOptions } from "lambert-server";
@@ -11,6 +12,7 @@ import path from "path";
import { initRateLimits } from "./middlewares/RateLimit";
import TestClient from "./middlewares/TestClient";
import { initTranslation } from "./middlewares/Translation";
+import morgan from "morgan";
export interface FosscordServerOptions extends ServerOptions {}
@@ -36,6 +38,29 @@ export class FosscordServer extends Server {
await Config.init();
await initEvent();
+
+ /*
+ DOCUMENTATION: uses LOG_REQUESTS environment variable
+
+ # only log 200 and 204
+ LOG_REQUESTS=200 204
+ # log everything except 200 and 204
+ LOG_REQUESTS=-200 204
+ # log all requests
+ LOG_REQUESTS=-
+ */
+
+ let logRequests = process.env["LOG_REQUESTS"] != undefined;
+ if(logRequests) {
+ this.app.use(morgan("combined", {
+ skip: (req, res) => {
+ var skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false);
+ if(process.env["LOG_REQUESTS"]?.charAt(0) == '-') skip = !skip;
+ return skip;
+ }
+ }));
+ }
+
this.app.use(CORS);
this.app.use(BodyParser({ inflate: true, limit: "10mb" }));
@@ -65,6 +90,9 @@ export class FosscordServer extends Server {
this.app.use(ErrorHandler);
TestClient(this.app);
+ if(logRequests){
+ console.log("Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!");
+ }
return super.start();
}
}
diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts
index 02ac9884..3f434f5e 100644
--- a/api/src/routes/channels/#channel_id/index.ts
+++ b/api/src/routes/channels/#channel_id/index.ts
@@ -1,6 +1,7 @@
-import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, ChannelType, ChannelPermissionOverwriteType } from "@fosscord/util";
-import { Router, Response, Request } from "express";
-import { route } from "@fosscord/api";
+import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util";
+import { Request, Response, Router } from "express";
+import { handleFile, route } from "@fosscord/api";
+
const router: Router = Router();
// TODO: delete channel
// TODO: Get channel
@@ -16,23 +17,37 @@ router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res:
router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
const { channel_id } = req.params;
- const channel = await Channel.findOneOrFail({ id: channel_id });
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
- // TODO: Dm channel "close" not delete
- const data = channel;
+ if (channel.type === ChannelType.DM) {
+ const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } })
+ recipient.closed = true
+ await Promise.all([
+ recipient.save(),
+ emitEvent({ event: "CHANNEL_DELETE", data: channel, user_id: req.user_id } as ChannelDeleteEvent)
+ ]);
- await Promise.all([emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent), Channel.delete({ id: channel_id })]);
+ } else if (channel.type === ChannelType.GROUP_DM) {
+ await Channel.removeRecipientFromChannel(channel, req.user_id)
+ } else {
+ //TODO messages in this channel should be deleted before deleting the channel
+ await Promise.all([
+ Channel.delete({ id: channel_id }),
+ emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent)
+ ]);
+ }
- res.send(data);
+ res.send(channel);
});
export interface ChannelModifySchema {
/**
* @maxLength 100
*/
- name: string;
- type: ChannelType;
+ name?: string;
+ type?: ChannelType;
topic?: string;
+ icon?: string | null;
bitrate?: number;
user_limit?: number;
rate_limit_per_user?: number;
@@ -53,6 +68,7 @@ export interface ChannelModifySchema {
router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
var payload = req.body as ChannelModifySchema;
const { channel_id } = req.params;
+ if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);
const channel = await Channel.findOneOrFail({ id: channel_id });
channel.assign(payload);
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
index 97d1d19e..786e4581 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -26,7 +26,7 @@ router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Reques
data: {
channel_id,
message_id,
- version: 496
+ version: 3763
}
} as MessageAckEvent);
diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts
index 07bfc22d..fab20977 100644
--- a/api/src/routes/channels/#channel_id/messages/index.ts
+++ b/api/src/routes/channels/#channel_id/messages/index.ts
@@ -1,9 +1,19 @@
import { Router, Response, Request } from "express";
-import { Attachment, Channel, ChannelType, Embed, getPermission, Message, uploadFile } from "@fosscord/util";
+import {
+ Attachment,
+ Channel,
+ ChannelType,
+ DmChannelDTO,
+ Embed,
+ emitEvent,
+ getPermission,
+ Message,
+ MessageCreateEvent,
+ uploadFile
+} from "@fosscord/util";
import { HTTPError } from "lambert-server";
-import { route } from "@fosscord/api";
+import { handleMessage, postHandleMessage, route } from "@fosscord/api";
import multer from "multer";
-import { sendMessage } from "@fosscord/api";
import { FindManyOptions, LessThan, MoreThan } from "typeorm";
const router: Router = Router();
@@ -61,9 +71,9 @@ router.get("/", async (req: Request, res: Response) => {
if (!channel) throw new HTTPError("Channel not found", 404);
isTextChannel(channel.type);
- const around = `${req.query.around}`;
- const before = `${req.query.before}`;
- const after = `${req.query.after}`;
+ const around = req.query.around ? `${req.query.around}` : undefined;
+ const before = req.query.before ? `${req.query.before}` : undefined;
+ const after = req.query.after ? `${req.query.after}` : undefined;
const limit = Number(req.query.limit) || 50;
if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100");
@@ -150,10 +160,11 @@ router.post(
return res.status(400).json(error);
}
}
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] });
const embeds = [];
if (body.embed) embeds.push(body.embed);
- const data = await sendMessage({
+ let message = await handleMessage({
...body,
type: 0,
pinned: false,
@@ -161,9 +172,41 @@ router.post(
embeds,
channel_id,
attachments,
- edited_timestamp: undefined
+ edited_timestamp: undefined,
+ timestamp: new Date()
});
- return res.json(data);
+ message = await message.save();
+
+ await channel.assign({ last_message_id: message.id }).save();
+
+ if (channel.isDm()) {
+ const channel_dto = await DmChannelDTO.from(channel);
+
+ for (let recipient of channel.recipients!) {
+ if (recipient.closed) {
+ await emitEvent({
+ event: "CHANNEL_CREATE",
+ data: channel_dto.excludedRecipients([recipient.user_id]),
+ user_id: recipient.user_id
+ });
+ }
+ }
+
+ //Only one recipients should be closed here, since in group DMs the recipient is deleted not closed
+ await Promise.all(
+ channel
+ .recipients!.filter((r) => r.closed)
+ .map(async (r) => {
+ r.closed = false;
+ return await r.save();
+ })
+ );
+ }
+
+ await emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent);
+ postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error
+
+ return res.json(message);
}
);
diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/api/src/routes/channels/#channel_id/recipients.ts
index ea6bc563..c7beeee8 100644
--- a/api/src/routes/channels/#channel_id/recipients.ts
+++ b/api/src/routes/channels/#channel_id/recipients.ts
@@ -1,5 +1,57 @@
-import { Router, Response, Request } from "express";
+import { Request, Response, Router } from "express";
+import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util";
+
const router: Router = Router();
-// TODO:
+
+router.put("/:user_id", async (req: Request, res: Response) => {
+ const { channel_id, user_id } = req.params;
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
+
+ if (channel.type !== ChannelType.GROUP_DM) {
+ const recipients = [
+ ...channel.recipients!.map(r => r.user_id),
+ user_id
+ ].unique()
+
+ const new_channel = await Channel.createDMChannel(recipients, req.user_id)
+ return res.status(201).json(new_channel);
+ } else {
+ if (channel.recipients!.map(r => r.user_id).includes(user_id)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error?
+ }
+
+ channel.recipients!.push(new Recipient({ channel_id: channel_id, user_id: user_id }));
+ await channel.save()
+
+ await emitEvent({
+ event: "CHANNEL_CREATE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ user_id: user_id
+ });
+
+ await emitEvent({
+ event: "CHANNEL_RECIPIENT_ADD", data: {
+ channel_id: channel_id,
+ user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection })
+ }, channel_id: channel_id
+ } as ChannelRecipientAddEvent);
+ return res.sendStatus(204);
+ }
+});
+
+router.delete("/:user_id", async (req: Request, res: Response) => {
+ const { channel_id, user_id } = req.params;
+ const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
+ if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id)))
+ throw DiscordApiErrors.MISSING_PERMISSIONS
+
+ if (!channel.recipients!.map(r => r.user_id).includes(user_id)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error?
+ }
+
+ await Channel.removeRecipientFromChannel(channel, user_id)
+
+ return res.sendStatus(204);
+});
export default router;
diff --git a/api/src/routes/sticker-packs/#id/index.ts b/api/src/routes/sticker-packs/#id/index.ts
new file mode 100644
index 00000000..2344a48f
--- /dev/null
+++ b/api/src/routes/sticker-packs/#id/index.ts
@@ -0,0 +1,18 @@
+import { Request, Response, Router } from "express";
+
+const router: Router = Router();
+
+router.get("/", async (req: Request, res: Response) => {
+ //TODO
+ res.json({
+ id: "",
+ stickers: [],
+ name: "",
+ sku_id: "",
+ cover_sticker_id: "",
+ description: "",
+ banner_asset_id: ""
+ }).status(200);
+});
+
+export default router;
\ No newline at end of file
diff --git a/api/src/routes/sticker-packs/index.ts b/api/src/routes/sticker-packs/index.ts
new file mode 100644
index 00000000..6c4e46d8
--- /dev/null
+++ b/api/src/routes/sticker-packs/index.ts
@@ -0,0 +1,10 @@
+import { Request, Response, Router } from "express";
+
+const router: Router = Router();
+
+router.get("/", async (req: Request, res: Response) => {
+ //TODO
+ res.json({ sticker_packs: [] }).status(200);
+});
+
+export default router;
\ No newline at end of file
diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts
index d60c4f86..06d5c38c 100644
--- a/api/src/routes/users/#id/profile.ts
+++ b/api/src/routes/users/#id/profile.ts
@@ -19,6 +19,7 @@ router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req
connected_accounts: user.connected_accounts,
premium_guild_since: null, // TODO
premium_since: null, // TODO
+ mutual_guilds: [], // TODO {id: "", nick: null} when ?with_mutual_guilds=true
user: {
username: user.username,
discriminator: user.discriminator,
diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts
index da33f204..b5782eca 100644
--- a/api/src/routes/users/@me/channels.ts
+++ b/api/src/routes/users/@me/channels.ts
@@ -1,15 +1,12 @@
-import { Router, Request, Response } from "express";
-import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent, Recipient } from "@fosscord/util";
-import { HTTPError } from "lambert-server";
+import { Request, Response, Router } from "express";
+import { Recipient, DmChannelDTO, Channel } from "@fosscord/util";
import { route } from "@fosscord/api";
-import { In } from "typeorm";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
- const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel"] });
-
- res.json(recipients.map((x) => x.channel));
+ const recipients = await Recipient.find({ where: { user_id: req.user_id, closed: false }, relations: ["channel", "channel.recipients"] });
+ res.json(await Promise.all(recipients.map(r => DmChannelDTO.from(r.channel, [req.user_id]))));
});
export interface DmChannelCreateSchema {
@@ -19,30 +16,7 @@ export interface DmChannelCreateSchema {
router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => {
const body = req.body as DmChannelCreateSchema;
-
- body.recipients = body.recipients.filter((x) => x !== req.user_id).unique();
-
- const recipients = await User.find({ where: body.recipients.map((x) => ({ id: x })) });
-
- if (recipients.length !== body.recipients.length) {
- throw new HTTPError("Recipient/s not found");
- }
-
- const type = body.recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM;
- const name = trimSpecial(body.name);
-
- const channel = await new Channel({
- name,
- type,
- // owner_id only for group dm channels
- created_at: new Date(),
- last_message_id: null,
- recipients: [...body.recipients.map((x) => new Recipient({ user_id: x })), new Recipient({ user_id: req.user_id })]
- }).save();
-
- await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent);
-
- res.json(channel);
+ res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
});
export default router;
diff --git a/api/src/routes/users/@me/connections.ts b/api/src/routes/users/@me/connections.ts
new file mode 100644
index 00000000..411e95bf
--- /dev/null
+++ b/api/src/routes/users/@me/connections.ts
@@ -0,0 +1,11 @@
+import { Request, Response, Router } from "express";
+import { route } from "@fosscord/api";
+
+const router: Router = Router();
+
+router.get("/", route({}), async (req: Request, res: Response) => {
+ //TODO
+ res.json([]).status(200);
+});
+
+export default router;
diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts
index 58d2e481..1d72f11a 100644
--- a/api/src/routes/users/@me/relationships.ts
+++ b/api/src/routes/users/@me/relationships.ts
@@ -18,9 +18,19 @@ const router = Router();
const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection];
router.get("/", route({}), async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships"] });
+ const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships", "relationships.to"] });
+
+ //TODO DTO
+ const related_users = user.relationships.map(r => {
+ return {
+ id: r.to.id,
+ type: r.type,
+ nickname: null,
+ user: r.to.toPublicUser(),
+ }
+ })
- return res.json(user.relationships);
+ return res.json(related_users);
});
export interface RelationshipPutSchema {
@@ -48,7 +58,10 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request,
await User.findOneOrFail({
relations: ["relationships", "relationships.to"],
select: userProjection,
- where: req.body as { discriminator: string; username: string }
+ where: {
+ discriminator: String(req.body.discriminator,).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes
+ username: req.body.username
+ }
}),
req.body.type
);
diff --git a/cdn/src/Server.ts b/cdn/src/Server.ts
index 5c4a8ae5..590eda6f 100644
--- a/cdn/src/Server.ts
+++ b/cdn/src/Server.ts
@@ -58,6 +58,9 @@ export class CDNServer extends Server {
this.app.use("/team-icons/", avatarsRoute);
this.log("verbose", "[Server] Route /team-icons registered");
+ this.app.use("/channel-icons/", avatarsRoute);
+ this.log("verbose", "[Server] Route /channel-icons registered");
+
return super.start();
}
diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts
index ef3dd890..ae13cca7 100644
--- a/gateway/src/listener/listener.ts
+++ b/gateway/src/listener/listener.ts
@@ -32,7 +32,7 @@ export async function setupListener(this: WebSocket) {
});
const guilds = members.map((x) => x.guild);
const recipients = await Recipient.find({
- where: { user_id: this.user_id },
+ where: { user_id: this.user_id, closed: false },
relations: ["channel"],
});
const dm_channels = recipients.map((x) => x.channel);
@@ -116,7 +116,7 @@ async function consume(this: WebSocket, opts: EventOpts) {
.has("VIEW_CHANNEL")
)
return;
- // TODO: check if user has permission to channel
+ //No break needed here, we need to call the listenEvent function below
case "GUILD_CREATE":
this.events[id] = await listenEvent(id, consumer, listenOpts);
break;
diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts
index f6a4478f..d91cd7f2 100644
--- a/gateway/src/opcodes/Identify.ts
+++ b/gateway/src/opcodes/Identify.ts
@@ -88,20 +88,17 @@ export async function onIdentify(this: WebSocket, data: Payload) {
const user_guild_settings_entries = members.map((x) => x.settings);
const recipients = await Recipient.find({
- where: { user_id: this.user_id },
+ where: { user_id: this.user_id, closed: false },
relations: ["channel", "channel.recipients", "channel.recipients.user"],
// TODO: public user selection
});
const channels = recipients.map((x) => {
// @ts-ignore
x.channel.recipients = x.channel.recipients?.map((x) => x.user);
- // @ts-ignore
- users = users.concat(x.channel.recipients);
- if (x.channel.type === ChannelType.DM) {
- x.channel.recipients = [
- // @ts-ignore
- x.channel.recipients.find((x) => x.id !== this.user_id),
- ];
+ //TODO is this needed? check if users in group dm that are not friends are sent in the READY event
+ //users = users.concat(x.channel.recipients);
+ if (x.channel.isDm()) {
+ x.channel.recipients = x.channel.recipients!.filter((x) => x.id !== this.user_id);
}
return x.channel;
});
@@ -111,16 +108,19 @@ export async function onIdentify(this: WebSocket, data: Payload) {
});
if (!user) return this.close(CLOSECODES.Authentication_failed);
- const public_user = {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- public_flags: user.public_flags,
- avatar: user.avatar,
- bot: user.bot,
- bio: user.bio,
- };
- users.push(public_user);
+ for (let relation of user.relationships) {
+ const related_user = relation.to
+ const public_related_user = {
+ username: related_user.username,
+ discriminator: related_user.discriminator,
+ id: related_user.id,
+ public_flags: related_user.public_flags,
+ avatar: related_user.avatar,
+ bot: related_user.bot,
+ bio: related_user.bio,
+ };
+ users.push(public_related_user);
+ }
const session_id = genSessionId();
this.session_id = session_id; //Set the session of the WebSocket object
@@ -201,7 +201,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
// @ts-ignore
experiments: experiments, // TODO
guild_join_requests: [], // TODO what is this?
- users: users.unique(), // TODO
+ users: users.unique(),
merged_members: merged_members,
// shard // TODO: only for bots sharding
// application // TODO for applications
diff --git a/gateway/src/opcodes/index.ts b/gateway/src/opcodes/index.ts
index a6d13bfb..c4069589 100644
--- a/gateway/src/opcodes/index.ts
+++ b/gateway/src/opcodes/index.ts
@@ -21,5 +21,6 @@ export default {
8: onRequestGuildMembers,
// 9: Invalid Session
// 10: Hello
+ // 13: Dm_update
14: onLazyRequest,
};
diff --git a/util/src/dtos/DmChannelDTO.ts b/util/src/dtos/DmChannelDTO.ts
new file mode 100644
index 00000000..8b7a18fd
--- /dev/null
+++ b/util/src/dtos/DmChannelDTO.ts
@@ -0,0 +1,35 @@
+import { MinimalPublicUserDTO } from "./UserDTO";
+import { Channel, PublicUserProjection, User } from "../entities";
+
+export class DmChannelDTO {
+ icon: string | null;
+ id: string;
+ last_message_id: string | null;
+ name: string | null;
+ origin_channel_id: string | null;
+ owner_id?: string;
+ recipients: MinimalPublicUserDTO[];
+ type: number;
+
+ static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) {
+ const obj = new DmChannelDTO()
+ obj.icon = channel.icon || null
+ obj.id = channel.id
+ obj.last_message_id = channel.last_message_id || null
+ obj.name = channel.name || null
+ obj.origin_channel_id = origin_channel_id || null
+ obj.owner_id = channel.owner_id
+ obj.type = channel.type
+ obj.recipients = (await Promise.all(channel.recipients!.filter(r => !excluded_recipients.includes(r.user_id)).map(async r => {
+ return await User.findOneOrFail({ where: { id: r.user_id }, select: PublicUserProjection })
+ }))).map(u => new MinimalPublicUserDTO(u))
+ return obj
+ }
+
+ excludedRecipients(excluded_recipients: string[]): DmChannelDTO {
+ return {
+ ...this,
+ recipients: this.recipients.filter(r => !excluded_recipients.includes(r.id))
+ }
+ }
+}
\ No newline at end of file
diff --git a/util/src/dtos/UserDTO.ts b/util/src/dtos/UserDTO.ts
new file mode 100644
index 00000000..f09b5f4e
--- /dev/null
+++ b/util/src/dtos/UserDTO.ts
@@ -0,0 +1,17 @@
+import { User } from "../entities";
+
+export class MinimalPublicUserDTO {
+ avatar?: string | null;
+ discriminator: string;
+ id: string;
+ public_flags: number;
+ username: string;
+
+ constructor(user: User) {
+ this.avatar = user.avatar
+ this.discriminator = user.discriminator
+ this.id = user.id
+ this.public_flags = user.public_flags
+ this.username = user.username
+ }
+}
\ No newline at end of file
diff --git a/util/src/dtos/index.ts b/util/src/dtos/index.ts
new file mode 100644
index 00000000..13702342
--- /dev/null
+++ b/util/src/dtos/index.ts
@@ -0,0 +1,2 @@
+export * from "./DmChannelDTO";
+export * from "./UserDTO";
\ No newline at end of file
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index fc954f63..ea632778 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -1,12 +1,13 @@
-import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
-import { Message } from "./Message";
-import { User } from "./User";
+import { PublicUserProjection, User } from "./User";
import { HTTPError } from "lambert-server";
-import { emitEvent, getPermission, Snowflake } from "../util";
-import { ChannelCreateEvent } from "../interfaces";
+import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial } from "../util";
+import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
import { Recipient } from "./Recipient";
+import { DmChannelDTO } from "../dtos";
+import { Message } from "./Message";
export enum ChannelType {
GUILD_TEXT = 0, // a text channel within a server
@@ -31,6 +32,9 @@ export class Channel extends BaseClass {
@Column({ nullable: true })
name?: string;
+ @Column({ type: 'text', nullable: true })
+ icon?: string | null;
+
@Column({ type: "simple-enum", enum: ChannelType })
type: ChannelType;
@@ -38,13 +42,8 @@ export class Channel extends BaseClass {
recipients?: Recipient[];
@Column({ nullable: true })
- @RelationId((channel: Channel) => channel.last_message)
last_message_id: string;
- @JoinColumn({ name: "last_message_id" })
- @ManyToOne(() => Message)
- last_message?: Message;
-
@Column({ nullable: true })
@RelationId((channel: Channel) => channel.guild)
guild_id?: string;
@@ -100,7 +99,6 @@ export class Channel extends BaseClass {
@Column({ nullable: true })
topic?: string;
- // TODO: DM channel
static async createChannel(
channel: Partial<Channel>,
user_id: string = "0",
@@ -153,15 +151,126 @@ export class Channel extends BaseClass {
new Channel(channel).save(),
!opts?.skipEventEmit
? emitEvent({
- event: "CHANNEL_CREATE",
- data: channel,
- guild_id: channel.guild_id,
- } as ChannelCreateEvent)
+ event: "CHANNEL_CREATE",
+ data: channel,
+ guild_id: channel.guild_id,
+ } as ChannelCreateEvent)
: Promise.resolve(),
]);
return channel;
}
+
+ static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) {
+ recipients = recipients.unique().filter((x) => x !== creator_user_id);
+ const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });
+
+ // TODO: check config for max number of recipients
+ if (otherRecipientsUsers.length !== recipients.length) {
+ throw new HTTPError("Recipient/s not found");
+ }
+
+ const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM;
+
+ let channel = null;
+
+ const channelRecipients = [...recipients, creator_user_id]
+
+ const userRecipients = await Recipient.find({ where: { user_id: creator_user_id }, relations: ["channel", "channel.recipients"] })
+
+ for (let ur of userRecipients) {
+ let re = ur.channel.recipients!.map(r => r.user_id)
+ if (re.length === channelRecipients.length) {
+ if (containsAll(re, channelRecipients)) {
+ if (channel == null) {
+ channel = ur.channel
+ await ur.assign({ closed: false }).save()
+ }
+ }
+ }
+ }
+
+ if (channel == null) {
+ name = trimSpecial(name);
+
+ channel = await new Channel({
+ name,
+ type,
+ owner_id: (type === ChannelType.DM ? undefined : creator_user_id),
+ created_at: new Date(),
+ last_message_id: null,
+ recipients: channelRecipients.map((x) => new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })),
+ }).save();
+ }
+
+
+ const channel_dto = await DmChannelDTO.from(channel)
+
+ if (type === ChannelType.GROUP_DM) {
+
+ for (let recipient of channel.recipients!) {
+ await emitEvent({
+ event: "CHANNEL_CREATE",
+ data: channel_dto.excludedRecipients([recipient.user_id]),
+ user_id: recipient.user_id
+ })
+ }
+ } else {
+ await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id });
+ }
+
+ return channel_dto.excludedRecipients([creator_user_id])
+ }
+
+ static async removeRecipientFromChannel(channel: Channel, user_id: string) {
+ await Recipient.delete({ channel_id: channel.id, user_id: user_id })
+ channel.recipients = channel.recipients?.filter(r => r.user_id !== user_id)
+
+ if (channel.recipients?.length === 0) {
+ await Channel.deleteChannel(channel);
+ await emitEvent({
+ event: "CHANNEL_DELETE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ user_id: user_id
+ });
+ return
+ }
+
+ await emitEvent({
+ event: "CHANNEL_DELETE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ user_id: user_id
+ });
+
+ //If the owner leave we make the first recipient in the list the new owner
+ if (channel.owner_id === user_id) {
+ channel.owner_id = channel.recipients!.find(r => r.user_id !== user_id)!.user_id //Is there a criteria to choose the new owner?
+ await emitEvent({
+ event: "CHANNEL_UPDATE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ channel_id: channel.id
+ });
+ }
+
+ await channel.save()
+
+ await emitEvent({
+ event: "CHANNEL_RECIPIENT_REMOVE", data: {
+ channel_id: channel.id,
+ user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection })
+ }, channel_id: channel.id
+ } as ChannelRecipientRemoveEvent);
+ }
+
+ static async deleteChannel(channel: Channel) {
+ await Message.delete({ channel_id: channel.id }) //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util
+ //TODO before deleting the channel we should check and delete other relations
+ await Channel.delete({ id: channel.id })
+ }
+
+ isDm() {
+ return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM
+ }
}
export interface ChannelPermissionOverwrite {
diff --git a/util/src/entities/Recipient.ts b/util/src/entities/Recipient.ts
index 2a27b29f..bb280588 100644
--- a/util/src/entities/Recipient.ts
+++ b/util/src/entities/Recipient.ts
@@ -19,5 +19,8 @@ export class Recipient extends BaseClass {
@ManyToOne(() => require("./User").User)
user: import("./User").User;
+ @Column({ default: false })
+ closed: boolean;
+
// TODO: settings/mute/nick/added at/encryption keys/read_state
}
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 736704f8..cef88777 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -124,7 +124,7 @@ export class User extends BaseClass {
flags: string; // UserFlags
@Column()
- public_flags: string;
+ public_flags: number;
@JoinColumn({ name: "relationship_ids" })
@OneToMany(() => Relationship, (relationship: Relationship) => relationship.from)
diff --git a/util/src/index.ts b/util/src/index.ts
index f3bd9e9b..fc00d46b 100644
--- a/util/src/index.ts
+++ b/util/src/index.ts
@@ -4,6 +4,7 @@ import "reflect-metadata";
export * from "./util/index";
export * from "./interfaces/index";
export * from "./entities/index";
+export * from "./dtos/index";
// import Config from "../util/Config";
// import db, { MongooseCache, toObject } from "./util/Database";
diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index aff50300..03099bbb 100644
--- a/util/src/interfaces/Event.ts
+++ b/util/src/interfaces/Event.ts
@@ -127,6 +127,22 @@ export interface ChannelPinsUpdateEvent extends Event {
};
}
+export interface ChannelRecipientAddEvent extends Event {
+ event: "CHANNEL_RECIPIENT_ADD";
+ data: {
+ channel_id: string;
+ user: User;
+ };
+}
+
+export interface ChannelRecipientRemoveEvent extends Event {
+ event: "CHANNEL_RECIPIENT_REMOVE";
+ data: {
+ channel_id: string;
+ user: User;
+ };
+}
+
export interface GuildCreateEvent extends Event {
event: "GUILD_CREATE";
data: Guild & {
@@ -436,6 +452,8 @@ export type EventData =
| ChannelUpdateEvent
| ChannelDeleteEvent
| ChannelPinsUpdateEvent
+ | ChannelRecipientAddEvent
+ | ChannelRecipientRemoveEvent
| GuildCreateEvent
| GuildUpdateEvent
| GuildDeleteEvent
@@ -482,6 +500,8 @@ export enum EVENTEnum {
ChannelUpdate = "CHANNEL_UPDATE",
ChannelDelete = "CHANNEL_DELETE",
ChannelPinsUpdate = "CHANNEL_PINS_UPDATE",
+ ChannelRecipientAdd = "CHANNEL_RECIPIENT_ADD",
+ ChannelRecipientRemove = "CHANNEL_RECIPIENT_REMOVE",
GuildCreate = "GUILD_CREATE",
GuildUpdate = "GUILD_UPDATE",
GuildDelete = "GUILD_DELETE",
@@ -525,6 +545,8 @@ export type EVENT =
| "CHANNEL_UPDATE"
| "CHANNEL_DELETE"
| "CHANNEL_PINS_UPDATE"
+ | "CHANNEL_RECIPIENT_ADD"
+ | "CHANNEL_RECIPIENT_REMOVE"
| "GUILD_CREATE"
| "GUILD_UPDATE"
| "GUILD_DELETE"
diff --git a/util/src/util/Array.ts b/util/src/util/Array.ts
new file mode 100644
index 00000000..27f7c961
--- /dev/null
+++ b/util/src/util/Array.ts
@@ -0,0 +1,3 @@
+export function containsAll(arr: any[], target: any[]) {
+ return target.every(v => arr.includes(v));
+}
\ No newline at end of file
diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts
index 9d87253a..44852f1e 100644
--- a/util/src/util/Permissions.ts
+++ b/util/src/util/Permissions.ts
@@ -92,6 +92,7 @@ export class Permissions extends BitField {
}
overwriteChannel(overwrites: ChannelPermissionOverwrite[]) {
+ if (!overwrites) return this
if (!this.cache) throw new Error("permission chache not available");
overwrites = overwrites.filter((x) => {
if (x.type === 0 && this.cache.roles?.some((r) => r.id === x.id)) return true;
diff --git a/util/src/util/index.ts b/util/src/util/index.ts
index 4e92f017..2de26fc7 100644
--- a/util/src/util/index.ts
+++ b/util/src/util/index.ts
@@ -12,3 +12,4 @@ export * from "./RabbitMQ";
export * from "./Regex";
export * from "./Snowflake";
export * from "./String";
+export * from "./Array";
|