diff --git a/assets/public/client/157ccfe93c91db60adddd48a75b69205.svg b/assets/public/client/157ccfe93c91db60adddd48a75b69205.svg
new file mode 100644
index 00000000..b22e4b02
--- /dev/null
+++ b/assets/public/client/157ccfe93c91db60adddd48a75b69205.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/public/client/3ce20f03ff35478349612e0c51869921.svg b/assets/public/client/3ce20f03ff35478349612e0c51869921.svg
new file mode 100644
index 00000000..2a31cb83
--- /dev/null
+++ b/assets/public/client/3ce20f03ff35478349612e0c51869921.svg
@@ -0,0 +1,41 @@
+
\ No newline at end of file
diff --git a/assets/public/client/6ec2f46e4eb4f79c4a34.js b/assets/public/client/6ec2f46e4eb4f79c4a34.js
new file mode 100644
index 00000000..c7ef988b
--- /dev/null
+++ b/assets/public/client/6ec2f46e4eb4f79c4a34.js
@@ -0,0 +1,37 @@
+"use strict";(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push([[91677,52173,20632],{562341:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0});t.default=void 0;!function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=l(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n)}(r(667294));var n,o=i(r(294184)),a=i(r(913225));function i(e){return e&&e.__esModule?e:{default:e}}function l(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(l=function(e){return e?r:t})(e)}function u(e,t,r,o){n||(n="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103);var a=e&&e.defaultProps,i=arguments.length-3
+;if(t||0===i||(t={children:void 0}),1===i)t.children=o;else if(i>1){for(var l=new Array(i),u=0;u{Object.defineProperty(t,"__esModule",{value:!0});t.default=h;!function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=s(t);if(r&&r.has(e))return r.get(e)
+;var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n)}(r(667294));var n,o,a,i=c(r(294184)),l=c(r(410208)),u=c(r(517675)),f=r(770348),d=c(r(233593));function c(e){return e&&e.__esModule?e:{default:e}}function s(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(s=function(e){return e?r:t})(e)}function p(e,t,r,n){a||(a="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103);var o=e&&e.defaultProps,i=arguments.length-3;if(t||0===i||(t={children:void 0}),1===i)t.children=n;else if(i>1){for(var l=new Array(i),u=0;u{Object.defineProperty(t,"__esModule",{value:!0});t.openGuildBannerUpsellModal=function(e){var t=e.analyticsLocations,r=e.analyticsLocation,n=e.guild,o=e.isGIF,a=e.banner,i=o?(0,
+l.minimumRequiredTierForGuildFeature)(d.GuildFeatures.ANIMATED_BANNER):(0,l.minimumRequiredTierForGuildFeature)(d.GuildFeatures.BANNER);if(null==i)return;(0,u.default)({analyticsLocations:t,analyticsSourceLocation:r,guild:n,headerProps:{title:c.default.Messages.GUILD_BANNER_UPSELL_MODAL_TITLE,subtitle:_(i,o),image:v(h,{guild:n,banner:a})},perkIntro:c.default.Messages.GUILD_BANNER_UPSELL_MODAL_PERK_INTRO,perks:o?(0,f.animatedGuildBannerUpsellPerks)():(0,f.guildBannerUpsellPerks)()})};!function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=y(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n)}(r(667294))
+;var n,o=p(r(562341)),a=p(r(15932)),i=r(772575),l=r(682937),u=p(r(58941)),f=r(665984),d=r(770348),c=p(r(247001)),s=p(r(562275));function p(e){return e&&e.__esModule?e:{default:e}}function y(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(y=function(e){return e?r:t})(e)}function v(e,t,r,o){n||(n="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103);var a=e&&e.defaultProps,i=arguments.length-3;if(t||0===i||(t={children:void 0}),1===i)t.children=o;else if(i>1){for(var l=new Array(i),u=0;u{Object.defineProperty(t,"__esModule",{value:!0});t.default=function(e,t){var c=null!=t?t:""+u.AnalyticsSections.PREMIUM_GUILD_UPSELL_MODAL;null!=e.targetBoostedGuildTier&&(c+=" - Tier "+e.targetBoostedGuildTier);l.default.track(u.AnalyticEvents.OPEN_MODAL,{type:c,location:e.analyticsSourceLocation});var s=e.openInPopoutEnabled,y=function(e,t){if(null==e)return{};var r,n,o={},a=Object.keys(e);for(n=0;n=0||(o[r]=e[r])}return o}(e,d),h=o.default.getWindowOpen(u.PopoutWindowKeys.CHANNEL_CALL_POPOUT)&&null!=s&&s;h&&(0,a.default)(u.PopoutWindowKeys.CHANNEL_CALL_POPOUT)
+;var _=h?f.POPOUT_MODAL_CONTEXT:f.DEFAULT_MODAL_CONTEXT;(0,i.openModalLazy)(v(regeneratorRuntime.mark((function e(){var t,o;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:e.next=2;return Promise.all([r.e(40532),r.e(35666),r.e(81747),r.e(39217),r.e(10961),r.e(24917),r.e(20802),r.e(93624),r.e(73892),r.e(4562),r.e(94322),r.e(23046),r.e(21543),r.e(87709),r.e(54538),r.e(53301),r.e(23148),r.e(47243),r.e(78833),r.e(73864),r.e(23535),r.e(52837)]).then(r.bind(r,977291));case 2:t=e.sent;o=t.default;return e.abrupt("return",(function(e){return n.createElement(o,p({},y,e))}));case 5:case"end":return e.stop()}}),e)}))),{onCloseCallback:function(){l.default.track(u.AnalyticEvents.MODAL_DISMISSED,{type:c,location:e.analyticsSourceLocation})},contextKey:_})};var n=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=s(t);if(r&&r.has(e))return r.get(e)
+;var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n);return n}(r(667294)),o=c(r(902305)),a=c(r(736632)),i=r(194322),l=c(r(308920)),u=r(770348),f=r(371621),d=["openInPopoutEnabled"];function c(e){return e&&e.__esModule?e:{default:e}}function s(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(s=function(e){return e?r:t})(e)}function p(){p=Object.assign||function(e){for(var t=1;t{Object.defineProperty(t,"__esModule",{value:!0});t.default=function(e){var t=e.guild,r=e.isBannerVisible,n=e.disableBoostClick;return t.hasFeature(v.GuildFeatures.VERIFIED)||t.hasFeature(v.GuildFeatures.PARTNERED)?b(T,{guild:t,disableColor:!r}):b(N,{guild:t,isBannerVisible:r,disableBoostClick:n})};var n,o=m(r(667294)),a=g(r(294184)),i=r(536211),l=r(75639),u=g(r(209382)),f=g(r(876874)),d=g(r(728429)),c=g(r(16941)),s=g(r(189950)),p=g(r(730977)),y=m(r(682937)),v=r(770348),h=g(r(247001)),_=g(r(625337));function g(e){return e&&e.__esModule?e:{default:e}}function O(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(O=function(e){return e?r:t})(e)}function m(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=O(t);if(r&&r.has(e))return r.get(e)
+;var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n);return n}function E(){E=Object.assign||function(e){for(var t=1;t1){for(var l=new Array(i),u=0;u{Object.defineProperty(t,"__esModule",{value:!0});t.adjustImageDimensionsForAspectRatio=function(e,t,r){switch(e){case o.UploadTypes.AVATAR:return{width:t,height:r};case o.UploadTypes.BANNER:return s(o.BANNER_ASPECT_RATIO,o.MAX_BANNER_OVERLAY_HEIGHT,t,r);case o.UploadTypes.BANNER_V2:return s(o.BANNER_V2_ASPECT_RATIO,o.MAX_BANNER_OVERLAY_HEIGHT,t,r)
+;case o.UploadTypes.GUILD_BANNER:return s(o.GUILD_BANNER_ASPECT_RATIO,o.MAX_GUILD_BANNER_OVERLAY_HEIGHT,t,r);case o.UploadTypes.VIDEO_BACKGROUND:return s(o.VIDEO_BACKGROUND_ASPECT_RATIO,o.MAX_VIDEO_OVERLAY_HEIGHT,t,r);case o.UploadTypes.SCHEDULED_EVENT_IMAGE:return s(o.SCHEDULED_EVENT_IMAGE_ASPECT_RATIO,o.MAX_SCHEDULED_EVENT_IMAGE_OVERLAY_HEIGHT,t,r)}};t.calculateDragBoundaries=function(e,t,r){var n={top:0,bottom:0,left:0,right:0},o=e-r.width,a=t-r.height;if(0!==o){n.left=-Math.abs(o/2);n.right=o/2}if(0!==a){n.bottom=-Math.abs(a/2);n.top=a/2}return n};t.calculateOverlaySize=function(e,t,r,n){switch(e){case o.UploadTypes.AVATAR:var a=Math.min(t,r);return{width:a,height:a};case o.UploadTypes.BANNER:var i=Math.min(t,o.EDITING_CONTAINER_WIDTH);return{width:i,height:i*(1/o.BANNER_ASPECT_RATIO)};case o.UploadTypes.BANNER_V2:var l=Math.min(t,o.EDITING_CONTAINER_WIDTH);return{width:l,height:l*(1/o.BANNER_V2_ASPECT_RATIO)};case o.UploadTypes.GUILD_BANNER:
+var u=Math.min(t,o.EDITING_CONTAINER_WIDTH);return{width:u,height:Math.min(u*(9/16),n)};case o.UploadTypes.VIDEO_BACKGROUND:var f=Math.min(t,o.EDITING_CONTAINER_WIDTH);return{width:f,height:f*(9/16)};case o.UploadTypes.SCHEDULED_EVENT_IMAGE:var d=Math.min(t,o.EDITING_CONTAINER_WIDTH);return{width:d,height:.4*d}}};t.cropGIF=function(e,t,r,n,o){return d.apply(this,arguments)};t.cropStaticImage=c;t.downsizeEmoji=function(e){var t=e.naturalWidth/e.naturalHeight,r=a.EMOJI_MAX_SIZE,n=a.EMOJI_MAX_SIZE;e.naturalWidth>e.naturalHeight?r/=t:n*=t;var o={height:r,width:n};return c(e,{width:e.width,height:e.height},{x:0,y:0},o)};t.getBoundedCoordinates=function(e,t,r){return{x:(0,n.clamp)(e,r.left,r.right),y:(0,n.clamp)(t,r.bottom,r.top)}};var n=r(496486),o=r(173708),a=r(770348);function i(e,t,r,n,o,a,i){try{var l=e[a](i),u=l.value}catch(e){r(e);return}l.done?t(u):Promise.resolve(u).then(n,o)}function l(e){return function(){var t=this,r=arguments;return new Promise((function(n,o){var a=e.apply(t,r)
+;function l(e){i(a,n,o,l,u,"next",e)}function u(e){i(a,n,o,l,u,"throw",e)}l(void 0)}))}}function u(e){return new Promise((function(t){var r=new FileReader;r.onload=function(e){var r,n=null===(r=e.target)||void 0===r?void 0:r.result;t("string"==typeof n?n:"")};r.readAsDataURL(e)}))}function f(e,t,r,n){var o=e.naturalWidth/e.width,a=t.width/2,i=t.height/2,l=(e.width/2-a-r.x)*o,u=(e.height/2-i-r.y)*o,f=t.width*o,d=t.height*o;return{x:l,y:u,scaledCropWidth:f,scaledCropHeight:d,canvasWidth:Math.min(f,n.width),canvasHeight:Math.min(d,n.height)}}function d(){return(d=l(regeneratorRuntime.mark((function e(t,n,a,i,l){var d,c,s,p,y,v,h,_;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:d=f(n,a,i,l),c=d.x,s=d.y,p=d.scaledCropWidth,y=d.scaledCropHeight;e.next=3;return t.arrayBuffer();case 3:v=e.sent;h=new Worker(new URL(r.p+r.u(2174),r.b));_=new Promise((function(e,t){h.onmessage=function(r){var n=r.data;if(n.type===o.MessageTypes.CROP_GIF_COMPLETE){
+e(u(new Blob([n.result])));h.terminate()}else if(n.type===o.MessageTypes.CROP_GIF_ERROR){t(new Error("Error cropping GIF"));h.terminate()}}}));h.postMessage({type:o.MessageTypes.CROP_GIF_START,gif:new Uint8Array(v),x:0|c,y:0|s,width:0|p,height:0|y});return e.abrupt("return",{result:_,cancelFn:function(){return h.terminate()}});case 8:case"end":return e.stop()}}),e)})))).apply(this,arguments)}function c(e,t,r,n){var o=f(e,t,r,n),a=o.x,i=o.y,l=o.scaledCropWidth,u=o.scaledCropHeight,d=o.canvasWidth,c=o.canvasHeight,s=document.createElement("canvas");s.width=d;s.height=c;var p=s.getContext("2d");null!=p&&p.drawImage(e,a,i,l,u,0,0,s.width,s.height);return s.toDataURL("image/png")}function s(e,t,r,n){var a=r,i=n;if(r>o.EDITING_CONTAINER_WIDTH){a=o.EDITING_CONTAINER_WIDTH;i=n*(o.EDITING_CONTAINER_WIDTH/r)}return r/n{Object.defineProperty(t,"__esModule",{value:!0});t.default=m;!function(e,t){if(!t&&e&&e.__esModule)return e
+;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=g(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n)}(r(667294));var n,o=r(536211),a=_(r(37039)),i=r(192605),l=_(r(58941)),u=_(r(30098)),f=_(r(790597)),d=_(r(956117)),c=r(645758),s=_(r(782732)),p=r(770348),y=r(665984),v=_(r(247001)),h=_(r(460590));function _(e){return e&&e.__esModule?e:{default:e}}function g(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(g=function(e){return e?r:t})(e)}function O(e,t,r,o){n||(n="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103);var a=e&&e.defaultProps,i=arguments.length-3;if(t||0===i||(t={children:void 0}),1===i)t.children=o;else if(i>1){
+for(var l=new Array(i),u=0;u{Object.defineProperty(t,"__esModule",{value:!0});t.default=b;var n,o=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=m(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n);return n
+}(r(667294)),a=r(536211),i=O(r(954140)),l=O(r(37039)),u=O(r(766332)),f=O(r(314182)),d=O(r(728429)),c=O(r(956117)),s=O(r(114655)),p=O(r(308920)),y=O(r(838583)),v=r(770348),h=r(173708),_=O(r(247001)),g=O(r(167633));function O(e){return e&&e.__esModule?e:{default:e}}function m(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(m=function(e){return e?r:t})(e)}function E(e,t,r,o){n||(n="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103);var a=e&&e.defaultProps,i=arguments.length-3;if(t||0===i||(t={children:void 0}),1===i)t.children=o;else if(i>1){for(var l=new Array(i),u=0;u{Object.defineProperty(t,"__esModule",{value:!0});t.default=function(e){var t=e.type,r=e.analyticsPage,n=e.analyticsSection,l=e.isGIF,u=e.banner;if([i.UploadTypes.BANNER,i.UploadTypes.BANNER_V2,i.UploadTypes.AVATAR].includes(t)&&l)return f(a.default,{analyticsSection:n,type:t});if(t===i.UploadTypes.GUILD_BANNER)return f(o.default,{analyticsSection:n,analyticsPage:r,isGIF:l,banner:u});return null};!function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=u(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n)}(r(667294));var n,o=l(r(415563)),a=l(r(171914)),i=r(173708);function l(e){return e&&e.__esModule?e:{
+default:e}}function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(u=function(e){return e?r:t})(e)}function f(e,t,r,o){n||(n="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103);var a=e&&e.defaultProps,i=arguments.length-3;if(t||0===i||(t={children:void 0}),1===i)t.children=o;else if(i>1){for(var l=new Array(i),u=0;u{Object.defineProperty(t,"__esModule",{value:!0});t.default=s;!function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=d(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null
+;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n)}(r(667294));var n,o=f(r(294184)),a=r(496394),i=f(r(41024)),l=f(r(114655)),u=f(r(477002));function f(e){return e&&e.__esModule?e:{default:e}}function d(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(d=function(e){return e?r:t})(e)}function c(e,t,r,o){n||(n="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103);var a=e&&e.defaultProps,i=arguments.length-3;if(t||0===i||(t={children:void 0}),1===i)t.children=o;else if(i>1){for(var l=new Array(i),u=0;u{Object.defineProperty(t,"__esModule",{value:!0});t.default=c;var n,o,a=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=u(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n);return n}(r(667294)),i=(n=r(468205))&&n.__esModule?n:{default:n},l=["width","height","color","foreground"];function u(e){
+if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(u=function(e){return e?r:t})(e)}function f(){f=Object.assign||function(e){for(var t=1;t1){for(var l=new Array(i),u=0;u=0||(o[r]=e[r])}return o}(e,l);return a.createElement("svg",f({},(0,i.default)(p),{width:r,height:o,viewBox:"0 0 24 24"}),d("path",{fillRule:"evenodd",clipRule:"evenodd",className:s,d:"M6 2C3.79086 2 2 3.79086 2 6V18C2 20.2091 3.79086 22 6 22H18C20.2091 22 22 20.2091 22 18V6C22 3.79086 20.2091 2 18 2H6ZM10 8C10 6.8952 9.1032 6 8 6C6.8944 6 6 6.8952 6 8C6 9.1056 6.8944 10 8 10C9.1032 10 10 9.1056 10 8ZM9 14L6 18H18L15 11L11 16L9 14Z",fill:c}))}c.displayName="ImagePlaceholder"},250003:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0});t.default=s;var n,o=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=f(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}
+n.default=e;r&&r.set(e,n);return n}(r(667294)),a=u(r(468811)),i=u(r(468205)),l=["width","height","color","foreground"];function u(e){return e&&e.__esModule?e:{default:e}}function f(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(f=function(e){return e?r:t})(e)}function d(){d=Object.assign||function(e){for(var t=1;t1){for(var l=new Array(i),u=0;u=0||(o[r]=e[r])}return o}(e,l),v=o.useRef(a.default.v4());return o.createElement("svg",d({},(0,i.default)(y),{width:r,height:u,viewBox:"0 0 24 24"}),c("g",{clipPath:"url(#"+v.current+")"},void 0,c("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M4.92871 13.4149L10.5857 19.0709L18.3639 11.2927C19.7781 9.87859 22.6066 6.48376 21.1923 2.80748C17.5153 1.3932 14.1213 4.22173 12.7074 5.63625L4.92871 13.4149ZM16.064 9.93309C17.1686 9.93309 18.064 9.03766 18.064 7.93309C18.064 6.82852 17.1686 5.93309 16.064 5.93309C14.9594 5.93309 14.064 6.82852 14.064 7.93309C14.064 9.03766 14.9594 9.93309 16.064 9.93309Z",fill:s,className:p}),c("path",{
+d:"M3.41357 16.7844C2.34946 17.8496 2.00004 22 2.00004 22C2.00004 22 6.15125 21.6521 7.21627 20.5869C7.71243 20.0915 7.96638 19.4494 8 18.8004L5.21285 18.7866L5.19829 16C4.54947 16.0336 3.90973 16.2881 3.41357 16.7844Z",fill:s,className:p}),c("path",{d:"M9.17144 9.17151H3.51459L1.74684 10.9393L6.34302 11.9999L9.17144 9.17151Z",fill:s,className:p}),c("path",{d:"M14.8283 14.8283V20.4852L13.0606 22.2529L11.9999 17.6568L14.8283 14.8283Z",fill:s,className:p})),c("defs",{},void 0,c("clipPath",{id:v.current},void 0,c("rect",{width:"24",height:"24",fill:p}))))}s.displayName="Rocket"},378444:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0});t.FullScreenLayers=M;t.TransitionStates=void 0;t.closeFullScreenLayer=P;t.openFullScreenLayer=function(e,t){void 0===t&&(t=Object.freeze({}));var r=t,n=r.layerKey,o=r.Layer,i=null!=n?n:(0,a.default)();E.setState((function(t){return{fullScreenLayers:[].concat(_(t.fullScreenLayers),[{key:i,transitionState:O.ENTERING,LayerComponent:null!=o?o:c.default,
+render:e}])}}));return i};t.useFullScreenLayerStore=void 0;var n,o=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var r=y(t);if(r&&r.has(e))return r.get(e);var n={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var i=o?Object.getOwnPropertyDescriptor(e,a):null;i&&(i.get||i.set)?Object.defineProperty(n,a,i):n[a]=e[a]}n.default=e;r&&r.set(e,n);return n}(r(667294)),a=p(r(873955)),i=r(570322),l=p(r(219548)),u=r(836233),f=r(728526),d=r(8916),c=p(r(248685)),s=p(r(801791));function p(e){return e&&e.__esModule?e:{default:e}}function y(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,r=new WeakMap;return(y=function(e){return e?r:t})(e)}function v(){v=Object.assign||function(e){for(var t=1;t1){for(var l=new Array(i),u=0;ue.length)&&(t=e.length);for(var r=0,n=new Array(t);r{"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.default=O;!function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var o=p(t);if(o&&o.has(e))return o.get(e);var a={},r=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var l in e)if("default"!==l&&Object.prototype.hasOwnProperty.call(e,l)){var n=r?Object.getOwnPropertyDescriptor(e,l):null;n&&(n.get||n.set)?Object.defineProperty(a,l,n):a[l]=e[l]}a.default=e;o&&o.set(e,a)}(o(667294));var a,r=o(342758),l=c(o(956117)),n=o(772575),i=o(645758),d=o(354479),u=o(3400),f=c(o(247001)),s=c(o(375778));function c(e){return e&&e.__esModule?e:{default:e}}function p(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,o=new WeakMap;return(p=function(e){return e?o:t})(e)}function v(e,t,o,r){
+a||(a="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103);var l=e&&e.defaultProps,n=arguments.length-3;if(t||0===n||(t={children:void 0}),1===n)t.children=r;else if(n>1){for(var i=new Array(n),d=0;d{e.exports=o.p+"3ce20f03ff35478349612e0c51869921.svg"}}]);
+//# sourceMappingURL=b9a30304301fe244a8c3.js.map
\ No newline at end of file
diff --git a/assets/public/client/service-worker.js b/assets/public/client/service-worker.js
new file mode 100644
index 00000000..ed724b60
--- /dev/null
+++ b/assets/public/client/service-worker.js
@@ -0,0 +1,37 @@
+self.addEventListener('push', (event) => {
+ if (!event.data) return;
+
+ const payload = event.data.json();
+ const channel = payload.data?.channel || {};
+
+ const isInGuild = channel.type === "GUILD_TEXT" || channel.type === "GUILD_VOICE";
+ const channelType = isInGuild ? channel.type === "GUILD_TEXT" ? "Text" : "Voice" : "";
+ const title = `${payload.data?.author ?? "Unknown"}${isInGuild ? ` (#${channel.name}, ${channelType} Channels)` : ""}`;
+
+ const options = {
+ body: payload.data?.content || "",
+ icon: payload.data?.avatar || null,
+ tag: `channel-${channel.id}`,
+ data: { channelId: channel.id, isInGuild },
+ };
+
+ event.waitUntil(self.registration.showNotification(title, options));
+});
+
+self.addEventListener('notificationclick', (event) => {
+ event.notification.close();
+
+ const payload = event.notification.data;
+ const targetUrl = `/channels/${!payload.isInGuild ? "@me/" : ""}${payload.channelId}` || "/";
+
+ event.waitUntil(
+ clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
+ for (const client of clientList) {
+ if (client.url === targetUrl && "focus" in client) {
+ return client.focus();
+ }
+ }
+ return clients.openWindow(targetUrl);
+ })
+ );
+});
\ No newline at end of file
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index db79ff71..d64d7437 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -16,7 +16,7 @@
along with this program. If not, see .
*/
-import { handleMessage, postHandleMessage, route } from "@spacebar/api";
+import { handleMessage, postHandleMessage, pushMessage, route } from "@spacebar/api";
import {
ApiError,
Attachment,
@@ -519,6 +519,7 @@ router.post(
]);
// no await as it shouldnt block the message send function and silently catch error
+ pushMessage(message).catch((e) => console.error("[Message] sending push notifications failed", e));
postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e));
return res.json(
diff --git a/src/api/routes/users/@me/devices.ts b/src/api/routes/users/@me/devices.ts
index 8cfacd81..3cf7172c 100644
--- a/src/api/routes/users/@me/devices.ts
+++ b/src/api/routes/users/@me/devices.ts
@@ -37,12 +37,15 @@ router.post(
}),
async (req: Request, res: Response) => {
const body = req.body as DeviceNotificationSchema;
- if (body.provider != "webpush") throw new HTTPError("Provider is not supported", 400);
+ if (body.provider != "webpush")
+ throw new HTTPError("Provider is not supported", 400);
- if (!Config.get().webPush.enabled || !vapidConfigured) throw new HTTPError("WebPush notifications are not configured", 400);
+ if (!Config.get().webPush.enabled || !vapidConfigured)
+ throw new HTTPError("WebPush notifications are not configured", 400);
const subscription = body.webpush_subscription;
- if (!subscription) throw new HTTPError("Subscription is missing or invalid", 400);
+ if (!subscription)
+ throw new HTTPError("Subscription is missing or invalid", 400);
const endpoint = subscription.endpoint.trim();
try {
diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 6ba66985..f9482fe3 100644
--- a/src/api/util/handlers/Message.ts
+++ b/src/api/util/handlers/Message.ts
@@ -42,6 +42,8 @@ import {
handleFile,
Permissions,
normalizeUrl,
+ sendNotification,
+ PushSubscription,
} from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { In, Or, Equal, IsNull } from "typeorm";
@@ -388,6 +390,70 @@ export async function handleMessage(opts: MessageOptions): Promise {
return message;
}
+export async function pushMessage(message: Message) {
+ const channel = await Channel.findOneOrFail({
+ where: { id: message.channel_id },
+ relations: ["recipients"],
+ });
+ if (!channel) return;
+
+ let users = new Set();
+ const permission = await getPermission(message.author_id, message.guild_id, message.channel_id);
+
+ if (channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) {
+ const ids = channel.recipients
+ ?.map(({ user_id }) => user_id)
+ .filter((user_id) => user_id != message.author_id);
+ users = new Set(ids);
+ } else {
+ if (!!message.content?.match(EVERYONE_MENTION) && permission?.has("MENTION_EVERYONE")) {
+ const ids = (await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id);
+ users = new Set(ids);
+ } else {
+ users = new Set([
+ ...(message.mention_roles.length
+ ? await Member.find({
+ where: [
+ ...message.mention_roles.map((role) => {
+ return { roles: { id: role.id } };
+ }),
+ ],
+ })
+ : []
+ ).map((member) => member.id),
+ ...message.mentions.map((user) => user.id),
+ ]);
+ }
+ }
+
+ const userIds = [...users];
+ if (userIds.length === 0) return;
+
+ // murdle: Online users shouldn't get a notification
+ const sessions = await Session.find({
+ where: { user_id: Or(...userIds.map((id) => Equal(id))) },
+ });
+ sessions.forEach(({ user_id }) => users.delete(user_id));
+
+ const remainingUserIds = [...users];
+ if (remainingUserIds.length === 0) return;
+
+ const subscriptions = await PushSubscription.find({
+ where: { user_id: Or(...remainingUserIds.map((id) => Equal(id))) },
+ });
+ for (const sub of subscriptions) {
+ await sendNotification(sub, {
+ type: "message",
+ data: {
+ content: message.content ? message.content.slice(0, 200) : "",
+ avatar: message.author?.avatar ? `${Config.get().cdn.endpointPublic}/avatars/${message.author_id}/${message.author?.avatar}` : "",
+ author: message.author?.username,
+ channel: { id: channel.id, type: channel.type }
+ }
+ });
+ }
+}
+
// TODO: cache link result in db
export async function postHandleMessage(message: Message) {
const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown
diff --git a/src/util/interfaces/Notification.ts b/src/util/interfaces/Notification.ts
new file mode 100644
index 00000000..a7943af2
--- /dev/null
+++ b/src/util/interfaces/Notification.ts
@@ -0,0 +1,4 @@
+export interface Notification {
+ type: "message";
+ data: object;
+}
\ No newline at end of file
diff --git a/src/util/interfaces/index.ts b/src/util/interfaces/index.ts
index 6620ba32..fae8a64d 100644
--- a/src/util/interfaces/index.ts
+++ b/src/util/interfaces/index.ts
@@ -23,3 +23,4 @@ export * from "./GuildWelcomeScreen";
export * from "./Interaction";
export * from "./Presence";
export * from "./Status";
+export * from "./Notification"
\ No newline at end of file
diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts
index 3de05aa2..8184f9b8 100644
--- a/src/util/util/Token.ts
+++ b/src/util/util/Token.ts
@@ -158,8 +158,8 @@ export async function loadOrGenerateKeypair() {
fs.readFile("jwt.key.pub"),
]);
- privateKey = crypto.createPrivateKey(loadedPrivateKey);
- publicKey = crypto.createPublicKey(loadedPublicKey);
+ privateKey = crypto.createPrivateKey({ key: loadedPrivateKey, type: "sec1" });
+ publicKey = crypto.createPublicKey({ key: loadedPublicKey, type: "spki" });
} else {
console.log("[JWT] Generating new keypair");
const res = crypto.generateKeyPairSync("ec", {
diff --git a/src/util/util/WebPush.ts b/src/util/util/WebPush.ts
index 1f663c24..38fdc91d 100644
--- a/src/util/util/WebPush.ts
+++ b/src/util/util/WebPush.ts
@@ -1,4 +1,4 @@
-import { Config } from "@spacebar/util";
+import { Config, Notification } from "@spacebar/util";
import { yellow } from "picocolors";
import { PushSubscription } from "../entities/PushSubscription";
import webpush, { PushSubscription as WebPushSubscription } from "web-push";
@@ -23,8 +23,8 @@ export function configurePush() {
vapidConfigured = true;
}
-export async function sendNotification(subscription: PushSubscription, title: string, body: string) {
- return webpush.sendNotification(parseSubscription(subscription), JSON.stringify({ title, body }), { TTL: 60 }).catch(async (err) => {
+export async function sendNotification(subscription: PushSubscription, body: Notification) {
+ return webpush.sendNotification(parseSubscription(subscription), JSON.stringify(body), { TTL: 60 }).catch(async (err) => {
if (err.statusCode === 404 || err.statusCode === 410) {
console.log(`[WebPush] Deleting subscription due to HTTP error`);
await PushSubscription.delete({ endpoint: subscription.endpoint });