/* stylelint-disable */

.emojione-nature {
  background-image: url('/packages/emojione/nature-sprites.png');

  &._1f42b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 0%;
  }

  &._2600 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 0%;
  }

  &._2602 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 8.333333333333334%;
  }

  &._2603 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 8.333333333333334%;
  }

  &._2604 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 0%;
  }

  &._2614 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 8.333333333333334%;
  }

  &._2618 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 16.666666666666668%;
  }

  &._2728 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 16.666666666666668%;
  }

  &._2744 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 16.666666666666668%;
  }

  &._1f436 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 0%;
  }

  &._1f431 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 8.333333333333334%;
  }

  &._1f42d {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 16.666666666666668%;
  }

  &._1f439 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 25%;
  }

  &._1f430 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 25%;
  }

  &._1f98a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 25%;
  }

  &._1f99d {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 25%;
  }

  &._1f43b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 0%;
  }

  &._1f43c {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 8.333333333333334%;
  }

  &._1f998 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 16.666666666666668%;
  }

  &._1f9a1 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 25%;
  }

  &._1f428 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 33.333333333333336%;
  }

  &._1f42f {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 33.333333333333336%;
  }

  &._1f981 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 33.333333333333336%;
  }

  &._1f42e {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 33.333333333333336%;
  }

  &._1f437 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 33.333333333333336%;
  }

  &._1f43d {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 0%;
  }

  &._1f438 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 8.333333333333334%;
  }

  &._1f435 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 16.666666666666668%;
  }

  &._1f648 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 25%;
  }

  &._1f649 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 33.333333333333336%;
  }

  &._1f64a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 41.666666666666664%;
  }

  &._1f412 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 41.666666666666664%;
  }

  &._1f414 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 41.666666666666664%;
  }

  &._1f427 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 41.666666666666664%;
  }

  &._1f426 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 41.666666666666664%;
  }

  &._1f424 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 41.666666666666664%;
  }

  &._1f423 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 0%;
  }

  &._1f425 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 8.333333333333334%;
  }

  &._1f986 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 16.666666666666668%;
  }

  &._1f9a2 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 25%;
  }

  &._1f985 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 33.333333333333336%;
  }

  &._1f989 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 41.666666666666664%;
  }

  &._1f99c {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 50%;
  }

  &._1f99a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 50%;
  }

  &._1f987 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 50%;
  }

  &._1f43a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 50%;
  }

  &._1f417 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 50%;
  }

  &._1f434 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 50%;
  }

  &._1f984 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 50%;
  }

  &._1f41d {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 0%;
  }

  &._1f41b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 8.333333333333334%;
  }

  &._1f98b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 16.666666666666668%;
  }

  &._1f40c {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 25%;
  }

  &._1f41a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 33.333333333333336%;
  }

  &._1f41e {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 41.666666666666664%;
  }

  &._1f41c {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 50%;
  }

  &._1f997 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 58.333333333333336%;
  }

  &._1f577 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 58.333333333333336%;
  }

  &._1f578 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 58.333333333333336%;
  }

  &._1f982 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 58.333333333333336%;
  }

  &._1f99f {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 58.333333333333336%;
  }

  &._1f9a0 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 58.333333333333336%;
  }

  &._1f422 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 58.333333333333336%;
  }

  &._1f40d {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 58.333333333333336%;
  }

  &._1f98e {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 0%;
  }

  &._1f996 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 8.333333333333334%;
  }

  &._1f995 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 16.666666666666668%;
  }

  &._1f419 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 25%;
  }

  &._1f991 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 33.333333333333336%;
  }

  &._1f990 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 41.666666666666664%;
  }

  &._1f980 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 50%;
  }

  &._1f99e {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 58.333333333333336%;
  }

  &._1f421 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 66.66666666666667%;
  }

  &._1f420 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 66.66666666666667%;
  }

  &._1f41f {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 66.66666666666667%;
  }

  &._1f42c {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 66.66666666666667%;
  }

  &._1f433 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 66.66666666666667%;
  }

  &._1f40b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 66.66666666666667%;
  }

  &._1f988 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 66.66666666666667%;
  }

  &._1f40a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 66.66666666666667%;
  }

  &._1f405 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 66.66666666666667%;
  }

  &._1f406 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 0%;
  }

  &._1f993 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 8.333333333333334%;
  }

  &._1f98d {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 16.666666666666668%;
  }

  &._1f418 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 25%;
  }

  &._1f98f {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 33.333333333333336%;
  }

  &._1f99b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 41.666666666666664%;
  }

  &._1f42a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 50%;
  }

  &._2601 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 58.333333333333336%;
  }

  &._1f992 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 66.66666666666667%;
  }

  &._1f999 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 75%;
  }

  &._1f403 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 75%;
  }

  &._1f402 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 75%;
  }

  &._1f404 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 75%;
  }

  &._1f40e {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 75%;
  }

  &._1f416 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 75%;
  }

  &._1f40f {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 75%;
  }

  &._1f411 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 75%;
  }

  &._1f410 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 75%;
  }

  &._1f98c {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 75%;
  }

  &._1f415 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 0%;
  }

  &._1f429 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 8.333333333333334%;
  }

  &._1f408 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 16.666666666666668%;
  }

  &._1f413 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 25%;
  }

  &._1f983 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 33.333333333333336%;
  }

  &._1f54a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 41.666666666666664%;
  }

  &._1f407 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 50%;
  }

  &._1f401 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 58.333333333333336%;
  }

  &._1f400 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 66.66666666666667%;
  }

  &._1f43f {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 75%;
  }

  &._1f994 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 83.33333333333333%;
  }

  &._1f43e {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 83.33333333333333%;
  }

  &._1f409 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 83.33333333333333%;
  }

  &._1f432 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 83.33333333333333%;
  }

  &._1f335 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 83.33333333333333%;
  }

  &._1f384 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 83.33333333333333%;
  }

  &._1f332 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 83.33333333333333%;
  }

  &._1f333 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 83.33333333333333%;
  }

  &._1f334 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 83.33333333333333%;
  }

  &._1f331 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 83.33333333333333%;
  }

  &._1f33f {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 83.33333333333333%;
  }

  &._1f340 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 0%;
  }

  &._1f38d {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 8.333333333333334%;
  }

  &._1f38b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 16.666666666666668%;
  }

  &._1f343 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 25%;
  }

  &._1f342 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 33.333333333333336%;
  }

  &._1f341 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 41.666666666666664%;
  }

  &._1f344 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 50%;
  }

  &._1f33e {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 58.333333333333336%;
  }

  &._1f490 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 66.66666666666667%;
  }

  &._1f337 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 75%;
  }

  &._1f339 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 83.33333333333333%;
  }

  &._1f940 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 91.66666666666667%;
  }

  &._1f33a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 91.66666666666667%;
  }

  &._1f338 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 91.66666666666667%;
  }

  &._1f33c {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 91.66666666666667%;
  }

  &._1f33b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 91.66666666666667%;
  }

  &._1f31e {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 91.66666666666667%;
  }

  &._1f31d {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 91.66666666666667%;
  }

  &._1f31b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 91.66666666666667%;
  }

  &._1f31c {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 91.66666666666667%;
  }

  &._1f31a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 91.66666666666667%;
  }

  &._1f315 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 91.66666666666667%;
  }

  &._1f316 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 91.66666666666667%;
  }

  &._1f317 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 0%;
  }

  &._1f318 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 8.333333333333334%;
  }

  &._1f311 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 16.666666666666668%;
  }

  &._1f312 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 25%;
  }

  &._1f313 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 33.333333333333336%;
  }

  &._1f314 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 41.666666666666664%;
  }

  &._1f319 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 50%;
  }

  &._1f30e {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 58.333333333333336%;
  }

  &._1f30d {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 66.66666666666667%;
  }

  &._1f30f {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 75%;
  }

  &._1f4ab {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 83.33333333333333%;
  }

  &._2b50 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 91.66666666666667%;
  }

  &._1f31f {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 0% 100%;
  }

  &._26a1 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 7.6923076923076925% 100%;
  }

  &._1f4a5 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 15.384615384615385% 100%;
  }

  &._1f525 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 23.076923076923077% 100%;
  }

  &._1f32a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 30.76923076923077% 100%;
  }

  &._1f308 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 38.46153846153846% 100%;
  }

  &._1f324 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 46.15384615384615% 100%;
  }

  &._26c5 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 53.84615384615385% 100%;
  }

  &._1f325 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 61.53846153846154% 100%;
  }

  &._1f326 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 69.23076923076923% 100%;
  }

  &._1f327 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 76.92307692307692% 100%;
  }

  &._26c8 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 84.61538461538461% 100%;
  }

  &._1f329 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 92.3076923076923% 100%;
  }

  &._1f328 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 100% 0%;
  }

  &._26c4 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 100% 8.333333333333334%;
  }

  &._1f32c {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 100% 16.666666666666668%;
  }

  &._1f4a8 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 100% 25%;
  }

  &._1f4a7 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 100% 33.333333333333336%;
  }

  &._1f4a6 {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 100% 41.666666666666664%;
  }

  &._1f30a {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 100% 50%;
  }

  &._1f32b {
    background-repeat: no-repeat;
    background-size: 1400% 1300%;
    background-position: 100% 58.333333333333336%;
  }

}


export const findWeeklyUsersRegisteredData = async ({ start, end }) => {

keep keep keep replace keep keep keep keep

import s from 'underscore.string';

import { BaseRaw } from './BaseRaw';

export class UsersRaw extends BaseRaw {
	constructor(...args) {
		super(...args);

		this.defaultFields = {
			__rooms: 0,
		};
	}

	findUsersInRoles(roles, scope, options) {
		roles = [].concat(roles);

		const query = {
			roles: { $in: roles },
		};

		return this.find(query, options);
	}

	findOneByUsername(username, options = null) {
		const query = { username };

		return this.findOne(query, options);
	}

	findOneAgentById(_id, options) {
		const query = {
			_id,
			roles: 'livechat-agent',
		};

		return this.findOne(query, options);
	}

	findUsersInRolesWithQuery(roles, query, options) {
		roles = [].concat(roles);

		Object.assign(query, { roles: { $in: roles } });

		return this.find(query, options);
	}

	findOneByUsernameAndRoomIgnoringCase(username, rid, options) {
		if (typeof username === 'string') {
			username = new RegExp(`^${ s.escapeRegExp(username) }$`, 'i');
		}

		const query = {
			__rooms: rid,
			username,
		};

		return this.findOne(query, options);
	}

	findOneByIdAndLoginHashedToken(_id, token, options = {}) {
		const query = {
			_id,
			'services.resume.loginTokens.hashedToken': token,
		};

		return this.findOne(query, options);
	}

	findByActiveUsersExcept(searchTerm, exceptions, options, searchFields, extraQuery = [], { startsWith = false, endsWith = false } = {}) {
		if (exceptions == null) { exceptions = []; }
		if (options == null) { options = {}; }
		if (Array.isArray(exceptions)) {
			exceptions = [exceptions];
		}

		// if the search term is empty, don't need to have the $or statement (because it would be an empty regex)
		if (searchTerm === '') {
			const query = {
				$and: [
					{
						active: true,
						username: { $exists: true, $nin: exceptions },
					},
					...extraQuery,
				],
			};

			return this.find(query, options);
		}

		const termRegex = new RegExp((startsWith ? '^' : '') + s.escapeRegExp(searchTerm) + (endsWith ? '$' : ''), 'i');

		// const searchFields = forcedSearchFields || settings.get('Accounts_SearchFields').trim().split(',');

		const orStmt = (searchFields || []).reduce(function(acc, el) {
			acc.push({ [el.trim()]: termRegex });
			return acc;
		}, []);

		const query = {
			$and: [
				{
					active: true,
					username: { $exists: true, $nin: exceptions },
					$or: orStmt,
				},
				...extraQuery,
			],
		};

		return this.find(query, options);
	}

	findOneByUsernameIgnoringCase(username, options) {
		if (typeof username === 'string') {
			username = new RegExp(`^${ s.escapeRegExp(username) }$`, 'i');
		}

		const query = { username };

		return this.findOne(query, options);
	}

	isUserInRole(userId, roleName) {
		const query = {
			_id: userId,
			roles: roleName,
		};

		return this.findOne(query, { fields: { roles: 1 } });
	}

	getDistinctFederationDomains() {
		return this.col.distinct('federation.origin', { federation: { $exists: true } });
	}

	async getNextLeastBusyAgent(department) {
		const aggregate = [
			{ $match: { status: { $exists: true, $ne: 'offline' }, statusLivechat: 'available', roles: 'livechat-agent' } },
			{ $lookup: {
				from: 'rocketchat_subscription',
				let: { id: '$_id' },
				pipeline: [{
					$match: {
						$expr: {
							$and: [
								{ $eq: ['$u._id', '$$id'] },
								{ $eq: ['$open', true] },
								{ ...department && { $eq: ['$department', department] } },
							],
						},
					},
				}],
				as: 'subs' },
			},
			{ $lookup: { from: 'rocketchat_livechat_department_agents', localField: '_id', foreignField: 'agentId', as: 'departments' } },
			{ $project: { agentId: '$_id', username: 1, lastRoutingTime: 1, departments: 1, count: { $size: '$subs' } } },
			{ $sort: { count: 1, lastRoutingTime: 1, username: 1 } },
		];

		if (department) {
			aggregate.push({ $unwind: '$departments' });
			aggregate.push({ $match: { 'departments.departmentId': department } });
		}

		aggregate.push({ $limit: 1 });

		const [agent] = await this.col.aggregate(aggregate).toArray();
		if (agent) {
			await this.setLastRoutingTime(agent.agentId);
		}

		return agent;
	}

	async setLastRoutingTime(userId) {
		const result = await this.col.findAndModify(
			{ _id: userId }
			, {
				sort: {
					_id: 1,
				},
			}, {
				$set: {
					lastRoutingTime: new Date(),
				},
			});
		return result.value;
	}

	async getAgentAndAmountOngoingChats(userId) {
		const aggregate = [
			{ $match: { _id: userId, status: { $exists: true, $ne: 'offline' }, statusLivechat: 'available', roles: 'livechat-agent' } },
			{ $lookup: { from: 'rocketchat_subscription', localField: '_id', foreignField: 'u._id', as: 'subs' } },
			{ $project: { agentId: '$_id', username: 1, lastAssignTime: 1, lastRoutingTime: 1, 'queueInfo.chats': { $size: '$subs' } } },
			{ $sort: { 'queueInfo.chats': 1, lastAssignTime: 1, lastRoutingTime: 1, username: 1 } },
		];

		const [agent] = await this.col.aggregate(aggregate).toArray();
		return agent;
	}

	findAllResumeTokensByUserId(userId) {
		return this.col.aggregate([
			{
				$match: {
					_id: userId,
				},
			},
			{
				$project: {
					tokens: {
						$filter: {
							input: '$services.resume.loginTokens',
							as: 'token',
							cond: {
								$ne: ['$$token.type', 'personalAccessToken'],
							},
						},
					},
				},
			},
			{ $unwind: '$tokens' },
			{ $sort: { 'tokens.when': 1 } },
			{ $group: { _id: '$_id', tokens: { $push: '$tokens' } } },
		]).toArray();
	}

	findActiveByUsernameOrNameRegexWithExceptionsAndConditions(termRegex, exceptions, conditions, options) {
		if (exceptions == null) { exceptions = []; }
		if (conditions == null) { conditions = {}; }
		if (options == null) { options = {}; }
		if (!Array.isArray(exceptions)) {
			exceptions = [exceptions];
		}

		const query = {
			$or: [{
				username: termRegex,
			}, {
				name: termRegex,
			}, {
				nickname: termRegex,
			}],
			active: true,
			type: {
				$in: ['user', 'bot'],
			},
			$and: [{
				username: {
					$exists: true,
				},
			}, {
				username: {
					$nin: exceptions,
				},
			}],
			...conditions,
		};

		return this.find(query, options);
	}

	countAllAgentsStatus({ departmentId = undefined }) {
		const match = {
			$match: {
				roles: { $in: ['livechat-agent'] },
			},
		};
		const group = {
			$group: {
				_id: null,
				offline: {
					$sum: {
						$cond: [{
							$or: [{
								$and: [
									{ $eq: ['$status', 'offline'] },
									{ $eq: ['$statusLivechat', 'available'] },
								],
							},
							{ $eq: ['$statusLivechat', 'not-available'] },
							],
						}, 1, 0],
					},
				},
				away: {
					$sum: {
						$cond: [{
							$and: [
								{ $eq: ['$status', 'away'] },
								{ $eq: ['$statusLivechat', 'available'] },
							],
						}, 1, 0],
					},
				},
				busy: {
					$sum: {
						$cond: [{
							$and: [
								{ $eq: ['$status', 'busy'] },
								{ $eq: ['$statusLivechat', 'available'] },
							],
						}, 1, 0],
					},
				},
				available: {
					$sum: {
						$cond: [{
							$and: [
								{ $eq: ['$status', 'online'] },
								{ $eq: ['$statusLivechat', 'available'] },
							],
						}, 1, 0],
					},
				},
			},
		};
		const lookup = {
			$lookup: {
				from: 'rocketchat_livechat_department_agents',
				localField: '_id',
				foreignField: 'agentId',
				as: 'departments',
			},
		};
		const unwind = {
			$unwind: {
				path: '$departments',
				preserveNullAndEmptyArrays: true,
			},
		};
		const departmentsMatch = {
			$match: {
				'departments.departmentId': departmentId,
			},
		};
		const params = [match];
		if (departmentId && departmentId !== 'undefined') {
			params.push(lookup);
			params.push(unwind);
			params.push(departmentsMatch);
		}
		params.push(group);
		return this.col.aggregate(params).toArray();
	}

	getTotalOfRegisteredUsersByDate({ start, end, options = {} }) {
		const params = [
			{
				$match: {
					createdAt: { $gte: start, $lte: end },
					roles: { $ne: 'anonymous' },
				},
			},
			{
				$group: {
					_id: {
						$concat: [
							{ $substr: ['$createdAt', 0, 4] },
							{ $substr: ['$createdAt', 5, 2] },
							{ $substr: ['$createdAt', 8, 2] },
						],
					},
					users: { $sum: 1 },
				},
			},
			{
				$group: {
					_id: {
						$toInt: '$_id',
					},
					users: { $sum: '$users' },
				},
			},
			{
				$project: {
					_id: 0,
					date: '$_id',
					users: 1,
					type: 'users',
				},
			},
		];
		if (options.sort) {
			params.push({ $sort: options.sort });
		}
		if (options.count) {
			params.push({ $limit: options.count });
		}
		return this.col.aggregate(params).toArray();
	}

	updateStatusText(_id, statusText) {
		const update = {
			$set: {
				statusText,
			},
		};

		return this.update({ _id }, update);
	}

	updateStatusByAppId(appId, status) {
		const query = {
			appId,
			status: { $ne: status },
		};

		const update = {
			$set: {
				status,
			},
		};

		return this.update(query, update, { multi: true });
	}

	openAgentsBusinessHoursByBusinessHourId(businessHourIds) {
		const query = {
			roles: 'livechat-agent',
		};

		const update = {
			$set: {
				statusLivechat: 'available',
			},
			$addToSet: {
				openBusinessHours: { $each: businessHourIds },
			},
		};

		return this.update(query, update, { multi: true });
	}

	addBusinessHourByAgentIds(agentIds = [], businessHourId) {
		const query = {
			_id: { $in: agentIds },
			roles: 'livechat-agent',
		};

		const update = {
			$set: {
				statusLivechat: 'available',
			},
			$addToSet: {
				openBusinessHours: businessHourId,
			},
		};

		return this.update(query, update, { multi: true });
	}

	removeBusinessHourByAgentIds(agentIds = [], businessHourId) {
		const query = {
			_id: { $in: agentIds },
			roles: 'livechat-agent',
		};

		const update = {
			$pull: {
				openBusinessHours: businessHourId,
			},
		};

		return this.update(query, update, { multi: true });
	}

	openBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment = [], businessHourId) {
		const query = {
			_id: { $nin: agentIdsWithDepartment },
		};

		const update = {
			$set: {
				statusLivechat: 'available',
			},
			$addToSet: {
				openBusinessHours: businessHourId,
			},
		};

		return this.update(query, update, { multi: true });
	}

	closeBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment = [], businessHourId) {
		const query = {
			_id: { $nin: agentIdsWithDepartment },
		};

		const update = {
			$pull: {
				openBusinessHours: businessHourId,
			},
		};

		return this.update(query, update, { multi: true });
	}

	closeAgentsBusinessHoursByBusinessHourIds(businessHourIds) {
		const query = {
			roles: 'livechat-agent',
		};

		const update = {
			$pull: {
				openBusinessHours: { $in: businessHourIds },
			},
		};

		return this.update(query, update, { multi: true });
	}

	updateLivechatStatusBasedOnBusinessHours(userIds = []) {
		const query = {
			$or: [{ openBusinessHours: { $exists: false } }, { openBusinessHours: { $size: 0 } }],
			roles: 'livechat-agent',
			...Array.isArray(userIds) && userIds.length > 0 && { _id: { $in: userIds } },
		};

		const update = {
			$set: {
				statusLivechat: 'not-available',
			},
		};

		return this.update(query, update, { multi: true });
	}

	async isAgentWithinBusinessHours(agentId) {
		return await this.find({
			_id: agentId,
			openBusinessHours: {
				$exists: true,
				$not: { $size: 0 },
			},
		}).count() > 0;
	}

	removeBusinessHoursFromAllUsers() {
		const query = {
			roles: 'livechat-agent',
			openBusinessHours: {
				$exists: true,
			},
		};

		const update = {
			$unset: {
				openBusinessHours: 1,
			},
		};

		return this.update(query, update, { multi: true });
	}

	resetTOTPById(userId) {
		return this.col.updateOne({
			_id: userId,
		}, {
			$unset: {
				'services.totp': 1,
			},
		});
	}

	removeResumeService(userId) {
		return this.col.updateOne({
			_id: userId,
		}, {
			$unset: {
				'services.resume': 1,
			},
		});
	}
}


import { Box, Button, ButtonGroup, Flex, Margins } from '@rocket.chat/fuselage';
import React from 'react';

import ConnectionStatusAlert from '../../components/connectionStatus/ConnectionStatusAlert';
import { useRoute } from '../../contexts/RouterContext';
import { useTranslation } from '../../contexts/TranslationContext';
import { useWipeInitialPageLoading } from '../../hooks/useWipeInitialPageLoading';

function NotFoundPage() {
	useWipeInitialPageLoading();

	const t = useTranslation();
	const homeRoute = useRoute('home');

	const handleGoToPreviousPageClick = () => {
		window.history.back();
	};

	const handleGoHomeClick = () => {
		homeRoute.push();
	};

	return <>
		<ConnectionStatusAlert />
		<Flex.Container direction='column' justifyContent='center' alignItems='center'>
			<Box is='section' width='full' minHeight='sh' textAlign='center' backgroundColor='neutral-800' style={{
				backgroundImage: 'url(\'/images/404.svg\')',
				backgroundRepeat: 'no-repeat',
				backgroundPosition: 'center',
				backgroundSize: 'cover',
			}}>
				<Flex.Item>
					<Box>
						<Margins all='x12'>
							<Box fontWeight='p2' fontSize='x64' color='alternative'>404</Box>

							<Box fontScale='h1' color='alternative'>
								{t('Oops_page_not_found')}
							</Box>

							<Box fontScale='p1' color='alternative'>
								{t('Sorry_page_you_requested_does_not_exist_or_was_deleted')}
							</Box>
						</Margins>

						<ButtonGroup align='center' margin='x64'>
							<Button type='button' primary onClick={handleGoToPreviousPageClick}>{t('Return_to_previous_page')}</Button>
							<Button type='button' primary onClick={handleGoHomeClick}>{t('Return_to_home')}</Button>
						</ButtonGroup>
					</Box>
				</Flex.Item>
			</Box>
		</Flex.Container>
	</>;
}

export default NotFoundPage;


import { CustomUserStatus } from '../../../models/server/raw';

export async function findCustomUserStatus({ query = {}, pagination: { offset, count, sort } }) {
	const cursor = await CustomUserStatus.find(query, {
		sort: sort || { name: 1 },
		skip: offset,
		limit: count,
	});

	const total = await cursor.count();

	const statuses = await cursor.toArray();

	return {
		statuses,
		count: statuses.length,
		offset,
		total,
	};
}


import { API } from '../../../../../../app/api/server';
import { runEndpointsAsUser } from '../../../../livechat-enterprise/server/lib/runEndpointsAsUser';

const endpointsToRunAsUser = {
	'livechat/inquiries.list': ['get'],
};

runEndpointsAsUser({ originalAPIRoutes: API.v1._routes, endpointsToRunAsUser });


const linkMarked = (href: string | null, _title: string | null, text: string): string =>

`<a href="${href}" target="_blank" rel="nofollow">${text}</a> `;

const paragraphMarked = (text: string): string => text;

const listItemMarked = (text: string): string => {

const cleanText = text.replace(/<p.*?>|<\/p>/gi, '');

keep keep keep keep replace keep keep keep keep keep

`<a href="${href}" target="_blank" rel="nofollow noopener noreferrer">${text}</a> `;

import { Box } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts';
import type { FC, MouseEvent } from 'react';
import React from 'react';

import GenericModal from '../../../components/GenericModal';
import MarkdownText from '../../../components/MarkdownText';
import AnnouncementComponent from './AnnouncementComponent';

type AnnouncementParams = {
	announcement: string;
	announcementDetails?: () => void;
};

const Announcement: FC<AnnouncementParams> = ({ announcement, announcementDetails }) => {
	const t = useTranslation();
	const setModal = useSetModal();
	const closeModal = useMutableCallback(() => setModal(null));
	const handleClick = (e: MouseEvent<HTMLAnchorElement>): void => {
		if ((e.target as HTMLAnchorElement).href) {
			return;
		}

		if (window?.getSelection()?.toString() !== '') {
			return;
		}

		announcementDetails
			? announcementDetails()
			: setModal(
					<GenericModal icon={null} title={t('Announcement')} confirmText={t('Close')} onConfirm={closeModal} onClose={closeModal}>
						<Box>
							<MarkdownText content={announcement} />
						</Box>
					</GenericModal>,
			  );
	};

	return announcement ? (
		<AnnouncementComponent onClickOpen={(e: MouseEvent<HTMLAnchorElement>): void => handleClick(e)}>
			<MarkdownText variant='inlineWithoutBreaks' content={announcement} withTruncatedText />
		</AnnouncementComponent>
	) : null;
};

export default Announcement;


import { useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts';
import type { ReactElement, ReactNode } from 'react';
import React, { Suspense, useEffect } from 'react';

import { SideNav } from '../../../app/ui-utils/client';
import PageSkeleton from '../../components/PageSkeleton';

type AccountRouterProps = {
	children?: ReactNode;
};

const AccountRouter = ({ children }: AccountRouterProps): ReactElement => {
	const [routeName] = useCurrentRoute();
	const defaultRoute = useRoute('profile');

	useEffect(() => {
		if (routeName !== 'account-index') {
			return;
		}

		defaultRoute.replace();
	}, [routeName, defaultRoute]);

	useEffect(() => {
		SideNav.setFlex('accountFlex');
		SideNav.openFlex(() => undefined);
	}, []);

	return children ? <Suspense fallback={<PageSkeleton />}>{children}</Suspense> : <PageSkeleton />;
};

export default AccountRouter;


import './cors';
import { Meteor } from 'meteor/meteor';

import { settings } from '../../settings/server';

Meteor.startup(function () {
	settings.watch('Force_SSL', (value) => {
		Meteor.absoluteUrl.defaultOptions.secure = Boolean(value);
	});
});


keep keep keep keep add keep keep keep keep

import { Box } from '@rocket.chat/fuselage';
import { useSetting } from '@rocket.chat/ui-contexts';
import dompurify from 'dompurify';
import { marked } from 'marked';
import type { ComponentProps, FC } from 'react';
import React, { useMemo } from 'react';

import { renderMessageEmoji } from '../lib/utils/renderMessageEmoji';

type MarkdownTextParams = {
	content: string;
	variant: 'inline' | 'inlineWithoutBreaks' | 'document';
	preserveHtml: boolean;
	parseEmoji: boolean;
	withTruncatedText: boolean;
} & ComponentProps<typeof Box>;

const documentRenderer = new marked.Renderer();
const inlineRenderer = new marked.Renderer();
const inlineWithoutBreaks = new marked.Renderer();

marked.Lexer.rules.gfm = {
	...marked.Lexer.rules.gfm,
	strong: /^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
	em: /^__(?=\S)([\s\S]*?\S)__(?!_)|^_(?=\S)([\s\S]*?\S)_(?!_)/,
};

const linkMarked = (href: string | null, _title: string | null, text: string): string =>
	`<a href="${href}" target="_blank" rel="nofollow">${text}</a> `;
const paragraphMarked = (text: string): string => text;
const brMarked = (): string => ' ';
const listItemMarked = (text: string): string => {
	const cleanText = text.replace(/<p.*?>|<\/p>/gi, '');
	return `<li>${cleanText}</li>`;
};
const horizontalRuleMarked = (): string => '';

documentRenderer.link = linkMarked;
documentRenderer.listitem = listItemMarked;

inlineRenderer.link = linkMarked;
inlineRenderer.paragraph = paragraphMarked;
inlineRenderer.listitem = listItemMarked;
inlineRenderer.hr = horizontalRuleMarked;

inlineWithoutBreaks.link = linkMarked;
inlineWithoutBreaks.paragraph = paragraphMarked;
inlineWithoutBreaks.br = brMarked;
inlineWithoutBreaks.listitem = listItemMarked;
inlineWithoutBreaks.hr = horizontalRuleMarked;

const defaultOptions = {
	gfm: true,
	headerIds: false,
};

const options = {
	...defaultOptions,
	renderer: documentRenderer,
};

const inlineOptions = {
	...defaultOptions,
	renderer: inlineRenderer,
};

const inlineWithoutBreaksOptions = {
	...defaultOptions,
	renderer: inlineWithoutBreaks,
};

const getRegexp = (schemeSetting: string): RegExp => {
	const schemes = schemeSetting ? schemeSetting.split(',').join('|') : '';
	return new RegExp(`^(${schemes}):`, 'gim');
};

const MarkdownText: FC<Partial<MarkdownTextParams>> = ({
	content,
	variant = 'document',
	withTruncatedText = false,
	preserveHtml = false,
	parseEmoji = false,
	...props
}) => {
	const sanitizer = dompurify.sanitize;

	let markedOptions: marked.MarkedOptions;

	const schemes = useSetting('Markdown_SupportSchemesForLink') as string;

	switch (variant) {
		case 'inline':
			markedOptions = inlineOptions;
			break;
		case 'inlineWithoutBreaks':
			markedOptions = inlineWithoutBreaksOptions;
			break;
		case 'document':
		default:
			markedOptions = options;
	}

	const __html = useMemo(() => {
		const html = ((): any => {
			if (content && typeof content === 'string') {
				const markedHtml = /inline/.test(variant)
					? marked.parseInline(new Option(content).innerHTML, markedOptions)
					: marked.parse(new Option(content).innerHTML, markedOptions);

				if (parseEmoji) {
					// We are using the old emoji parser here. This could come
					// with additional processing use, but is the workaround available right now.
					// Should be replaced in the future with the new parser.
					return renderMessageEmoji({ html: markedHtml });
				}

				return markedHtml;
			}
		})();

		return preserveHtml ? html : html && sanitizer(html, { ADD_ATTR: ['target'], ALLOWED_URI_REGEXP: getRegexp(schemes) });
	}, [preserveHtml, sanitizer, content, variant, markedOptions, parseEmoji, schemes]);

	return __html ? (
		<Box
			dangerouslySetInnerHTML={{ __html }}
			withTruncatedText={withTruncatedText}
			withRichContent={variant === 'inlineWithoutBreaks' ? 'inlineWithoutBreaks' : true}
			{...props}
		/>
	) : null;
};

export default MarkdownText;


import { Box } from '@rocket.chat/fuselage';
import type { FC } from 'react';
import React from 'react';

import IconButton from './IconButton';
import TextButton from './TextButton';

const Toolbox: FC = ({ children }) => (
	<>
		<Box display='flex' w='full' justifyContent='space-between' alignItems='center'>
			{children}
		</Box>
	</>
);

export default Object.assign(Toolbox, {
	IconButton,
	TextButton,
});


import { Box } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';

import GenericModal from '../../../components/GenericModal';
import AppPermissionsList from './components/AppPermissionsList';

type AppPermissionsReviewModalProps = {
	appPermissions: Array<{ name: string; required?: boolean }>;
	onCancel: () => void;
	onConfirm: (permissionsGranted: any) => void;
};

const AppPermissionsReviewModal = ({ appPermissions, onCancel, onConfirm }: AppPermissionsReviewModalProps): ReactElement => {
	const t = useTranslation();

	return (
		<GenericModal
			variant='warning'
			title={t('Apps_Permissions_Review_Modal_Title')}
			onCancel={onCancel}
			onConfirm={(): void => onConfirm(appPermissions)}
			onClose={onCancel}
			confirmText={t('Agree')}
		>
			<Box fontScale='p2' mbe={20}>
				{t('Apps_Permissions_Review_Modal_Subtitle')}
			</Box>
			<Box is='ol' type='1' style={{ listStyleType: 'decimal' }} mis='x24'>
				<AppPermissionsList appPermissions={appPermissions} />
			</Box>
		</GenericModal>
	);
};

export default AppPermissionsReviewModal;


import { test, expect } from './utils/test';
import { HomeChannel } from './page-objects';
import { createTargetChannel } from './utils';

test.use({ storageState: 'admin-session.json' });

test.describe.serial('channel-management', () => {
	let poHomeChannel: HomeChannel;
	let targetChannel: string;

	test.beforeAll(async ({ api }) => {
		targetChannel = await createTargetChannel(api);
	});

	test.beforeEach(async ({ page }) => {
		poHomeChannel = new HomeChannel(page);

		await page.goto('/home');
	});

	test('expect add "user1" to "targetChannel"', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.btnTabMembers.click();
		await poHomeChannel.tabs.members.showAllUsers();
		await poHomeChannel.tabs.members.addUser('user1');

		await expect(poHomeChannel.toastSuccess).toBeVisible();
	});

	test('expect create invite to the room', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.btnTabMembers.click();
		await poHomeChannel.tabs.members.inviteUser();

		await expect(poHomeChannel.toastSuccess).toBeVisible();
	});

	test('expect mute "user1"', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.btnTabMembers.click();
		await poHomeChannel.tabs.members.showAllUsers();
		await poHomeChannel.tabs.members.muteUser('user1');
	});

	test('expect set "user1" as owner', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.btnTabMembers.click();
		await poHomeChannel.tabs.members.showAllUsers();
		await poHomeChannel.tabs.members.setUserAsOwner('user1');
	});

	test('expect set "user1" as moderator', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.btnTabMembers.click();
		await poHomeChannel.tabs.members.showAllUsers();
		await poHomeChannel.tabs.members.setUserAsModerator('user1');
	});

	test('expect edit topic of "targetChannel"', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.btnRoomInfo.click();
		await poHomeChannel.tabs.room.btnEdit.click();
		await poHomeChannel.tabs.room.inputTopic.fill('hello-topic-edited');
		await poHomeChannel.tabs.room.btnSave.click();
	});

	test('expect edit announcement of "targetChannel"', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.btnRoomInfo.click();
		await poHomeChannel.tabs.room.btnEdit.click();
		await poHomeChannel.tabs.room.inputAnnouncement.fill('hello-announcement-edited');
		await poHomeChannel.tabs.room.btnSave.click();
	});

	test('expect edit description of "targetChannel"', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.btnRoomInfo.click();
		await poHomeChannel.tabs.room.btnEdit.click();
		await poHomeChannel.tabs.room.inputDescription.fill('hello-description-edited');
		await poHomeChannel.tabs.room.btnSave.click();
	});

	test('expect edit name of "targetChannel"', async ({ page }) => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.btnRoomInfo.click();
		await poHomeChannel.tabs.room.btnEdit.click();
		await poHomeChannel.tabs.room.inputName.fill(`NAME-EDITED-${targetChannel}`);
		await poHomeChannel.tabs.room.btnSave.click();
		await poHomeChannel.sidenav.openChat(`NAME-EDITED-${targetChannel}`);

		await expect(page).toHaveURL(`/channel/NAME-EDITED-${targetChannel}`);
	});

	test.skip('expect edit notification preferences of "targetChannel"', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.kebab.click({ force: true });
		await poHomeChannel.tabs.btnNotificationPreferences.click({ force: true });
		await poHomeChannel.tabs.notificationPreferences.updateAllNotificationPreferences();
		await poHomeChannel.tabs.notificationPreferences.btnSave.click();

		await expect(poHomeChannel.toastSuccess).toBeVisible();
	});

	test.skip('expect all notification preferences of "targetChannel" to be "Mentions"', async () => {
		await poHomeChannel.sidenav.openChat(targetChannel);
		await poHomeChannel.tabs.kebab.click({ force: true });
		await poHomeChannel.tabs.btnNotificationPreferences.click({ force: true });

		await expect(poHomeChannel.tabs.notificationPreferences.getPreferenceByDevice('Desktop')).toContainText('Mentions');
		await expect(poHomeChannel.tabs.notificationPreferences.getPreferenceByDevice('Mobile')).toContainText('Mentions');
		await expect(poHomeChannel.tabs.notificationPreferences.getPreferenceByDevice('Email')).toContainText('Mentions');
	});
});


return preserveHtml ? html : html && sanitizer(html, { ADD_ATTR: ['target'], ALLOWED_URI_REGEXP: getRegexp(schemes) });

}, [preserveHtml, sanitizer, content, variant, markedOptions, parseEmoji, schemes]);

// Add a hook to make all links open a new window

dompurify.addHook('afterSanitizeAttributes', (node) => {

// set all elements owning target to target=_blank

node.setAttribute('rel', 'nofollow noopener noreferrer');


import { useMutableCallback } from '@rocket.chat/fuselage-hooks';

import { updateChart } from '../../../../../app/livechat/client/lib/chartHandler';

export const useUpdateChartData = ({ context, canvas, init, t }) =>
	useMutableCallback(async (label, data) => {
		if (!context.current) {
			context.current = await init(canvas.current, context.current, t);
		}
		await updateChart(context.current, label, data);
	});


import crypto from 'crypto';
import fs from 'fs';
import { resolve } from 'path';

import yaml from 'js-yaml';
import { v4 as uuidv4 } from 'uuid';
import { Settings } from '@rocket.chat/models';

import { settings, settingsRegistry } from '../../../../../settings/server';
import type { IFederationBridgeRegistrationFile } from '../../../domain/IFederationBridge';

const EVERYTHING_REGEX = '.*';
const LISTEN_RULES = EVERYTHING_REGEX;

export class RocketChatSettingsAdapter {
	public initialize(): void {
		this.addFederationSettings();
		this.watchChangesAndUpdateRegistrationFile();
		this.updateSettingsWithProvidedConfigFileIfNecessary();
	}

	public getApplicationServiceId(): string {
		return settings.get('Federation_Matrix_id');
	}

	public getApplicationHomeServerToken(): string {
		return settings.get('Federation_Matrix_hs_token');
	}

	public getApplicationApplicationServiceToken(): string {
		return settings.get('Federation_Matrix_as_token');
	}

	public getBridgeUrl(): string {
		return settings.get('Federation_Matrix_bridge_url');
	}

	public getBridgePort(): number {
		const [, , port = '3300'] = this.getBridgeUrl().split(':');

		return parseInt(port);
	}

	public getHomeServerUrl(): string {
		return settings.get('Federation_Matrix_homeserver_url');
	}

	public getHomeServerDomain(): string {
		return settings.get('Federation_Matrix_homeserver_domain');
	}

	public getBridgeBotUsername(): string {
		return settings.get('Federation_Matrix_bridge_localpart');
	}

	public async disableFederation(): Promise<void> {
		await Settings.updateValueById('Federation_Matrix_enabled', false);
	}

	public isFederationEnabled(): boolean {
		return settings.get('Federation_Matrix_enabled') === true;
	}

	public areEphemeralEventsEnabled(): boolean {
		return this.isTypingStatusEnabled();
	}

	public isTypingStatusEnabled(): boolean {
		return this.getRegistrationFileFromHomeserver()?.enableEphemeralEvents === true;
	}

	public onFederationEnabledStatusChanged(
		callback: (
			enabled: boolean,
			appServiceId: string,
			homeServerUrl: string,
			homeServerDomain: string,
			bridgeUrl: string,
			bridgePort: number,
			homeServerRegistrationFile: IFederationBridgeRegistrationFile,
		) => Promise<void>,
	): () => void {
		return settings.watchMultiple<boolean>(
			[
				'Federation_Matrix_enabled',
				'Federation_Matrix_id',
				'Federation_Matrix_hs_token',
				'Federation_Matrix_as_token',
				'Federation_Matrix_homeserver_url',
				'Federation_Matrix_homeserver_domain',
				'Federation_Matrix_bridge_url',
				'Federation_Matrix_bridge_localpart',
			],
			([enabled]) =>
				Promise.await(
					callback(
						enabled === true,
						this.getApplicationServiceId(),
						this.getHomeServerUrl(),
						this.getHomeServerDomain(),
						this.getBridgeUrl(),
						this.getBridgePort(),
						this.generateRegistrationFileObject(),
					),
				),
		);
	}

	public generateRegistrationFileObject(): IFederationBridgeRegistrationFile {
		return {
			id: this.getApplicationServiceId(),
			homeserverToken: this.getApplicationHomeServerToken(),
			applicationServiceToken: this.getApplicationApplicationServiceToken(),
			bridgeUrl: this.getBridgeUrl(),
			botName: this.getBridgeBotUsername(),
			enableEphemeralEvents: this.areEphemeralEventsEnabled(),
			listenTo: {
				users: [
					{
						exclusive: false,
						regex: LISTEN_RULES,
					},
				],
				rooms: [
					{
						exclusive: false,
						regex: LISTEN_RULES,
					},
				],
				aliases: [
					{
						exclusive: false,
						regex: LISTEN_RULES,
					},
				],
			},
		};
	}

	private async updateRegistrationFile(): Promise<void> {
		const registrationFile = this.generateRegistrationFileObject();

		await Settings.updateValueById(
			'Federation_Matrix_registration_file',
			yaml.dump({
				'id': registrationFile.id,
				'hs_token': registrationFile.homeserverToken,
				'as_token': registrationFile.applicationServiceToken,
				'url': registrationFile.bridgeUrl,
				'sender_localpart': registrationFile.botName,
				'namespaces': registrationFile.listenTo,
				'de.sorunome.msc2409.push_ephemeral': registrationFile.enableEphemeralEvents,
			}),
		);
	}

	private watchChangesAndUpdateRegistrationFile(): void {
		settings.watchMultiple(
			[
				'Federation_Matrix_id',
				'Federation_Matrix_hs_token',
				'Federation_Matrix_as_token',
				'Federation_Matrix_homeserver_url',
				'Federation_Matrix_homeserver_domain',
				'Federation_Matrix_bridge_url',
				'Federation_Matrix_bridge_localpart',
			],
			this.updateRegistrationFile.bind(this),
		);
	}

	private addFederationSettings(): void {
		const preExistingConfiguration = this.getRegistrationFileFromHomeserver();

		settingsRegistry.addGroup('Federation', function () {
			this.section('Matrix Bridge', function () {
				this.add('Federation_Matrix_enabled', Boolean(preExistingConfiguration), {
					readonly: false,
					type: 'boolean',
					i18nLabel: 'Federation_Matrix_enabled',
					i18nDescription: 'Federation_Matrix_enabled_desc',
					alert: 'Federation_Matrix_Enabled_Alert',
					public: true,
				});

				const uniqueId = settings.get('uniqueID') || uuidv4().slice(0, 15).replace(new RegExp('-', 'g'), '_');
				const homeserverToken = crypto.createHash('sha256').update(`hs_${uniqueId}`).digest('hex');
				const applicationServiceToken = crypto.createHash('sha256').update(`as_${uniqueId}`).digest('hex');

				this.add('Federation_Matrix_id', preExistingConfiguration?.id || `rocketchat_${uniqueId}`, {
					readonly: true,
					type: 'string',
					i18nLabel: 'Federation_Matrix_id',
					i18nDescription: 'Federation_Matrix_id_desc',
				});

				this.add('Federation_Matrix_hs_token', preExistingConfiguration?.homeserverToken || homeserverToken, {
					readonly: true,
					type: 'string',
					i18nLabel: 'Federation_Matrix_hs_token',
					i18nDescription: 'Federation_Matrix_hs_token_desc',
				});

				this.add('Federation_Matrix_as_token', preExistingConfiguration?.applicationServiceToken || applicationServiceToken, {
					readonly: true,
					type: 'string',
					i18nLabel: 'Federation_Matrix_as_token',
					i18nDescription: 'Federation_Matrix_as_token_desc',
				});

				this.add('Federation_Matrix_homeserver_url', preExistingConfiguration?.rocketchat?.homeServerUrl || 'http://localhost:8008', {
					type: 'string',
					i18nLabel: 'Federation_Matrix_homeserver_url',
					i18nDescription: 'Federation_Matrix_homeserver_url_desc',
					alert: 'Federation_Matrix_homeserver_url_alert',
				});

				this.add('Federation_Matrix_homeserver_domain', preExistingConfiguration?.rocketchat?.domainName || 'local.rocket.chat', {
					type: 'string',
					i18nLabel: 'Federation_Matrix_homeserver_domain',
					i18nDescription: 'Federation_Matrix_homeserver_domain_desc',
					alert: 'Federation_Matrix_homeserver_domain_alert',
				});

				this.add('Federation_Matrix_bridge_url', preExistingConfiguration?.bridgeUrl || 'http://host.docker.internal:3300', {
					type: 'string',
					i18nLabel: 'Federation_Matrix_bridge_url',
					i18nDescription: 'Federation_Matrix_bridge_url_desc',
				});

				this.add('Federation_Matrix_bridge_localpart', preExistingConfiguration?.botName || 'rocket.cat', {
					type: 'string',
					i18nLabel: 'Federation_Matrix_bridge_localpart',
					i18nDescription: 'Federation_Matrix_bridge_localpart_desc',
				});

				this.add('Federation_Matrix_registration_file', '', {
					readonly: true,
					hidden: Boolean(preExistingConfiguration),
					type: 'code',
					i18nLabel: 'Federation_Matrix_registration_file',
					i18nDescription: 'Federation_Matrix_registration_file_desc',
					alert: 'Federation_Matrix_registration_file_Alert',
				});
			});
		});
	}

	private getRegistrationFileFromHomeserver(): Record<string, any> | undefined {
		try {
			const registrationYaml = fs.readFileSync(this.getFilePathForHomeserverConfig(), 'utf8');

			const parsedFile = yaml.load(registrationYaml as string) as Record<string, any>;
			return {
				applicationServiceToken: parsedFile.as_token,
				bridgeUrl: parsedFile.url,
				botName: parsedFile.sender_localpart,
				homeserverToken: parsedFile.hs_token,
				id: parsedFile.id,
				listenTo: parsedFile.namespaces,
				enableEphemeralEvents: parsedFile['de.sorunome.msc2409.push_ephemeral'],
				rocketchat: { domainName: parsedFile.rocketchat?.homeserver_domain, homeServerUrl: parsedFile.rocketchat?.homeserver_url },
			};
		} catch (e) {
			// no-op
		}
	}

	private getFilePathForHomeserverConfig(): string {
		return process.env.NODE_ENV === 'development'
			? '../../../../../matrix-federation-config/registration.yaml'
			: resolve(process.cwd(), '../../../matrix-federation-config/registration.yaml');
	}

	private updateSettingsWithProvidedConfigFileIfNecessary(): void {
		const existingConfiguration = this.getRegistrationFileFromHomeserver();
		if (!existingConfiguration) {
			return;
		}

		Promise.await(Settings.updateValueById('Federation_Matrix_enabled', true));
		Promise.await(Settings.updateValueById('Federation_Matrix_id', existingConfiguration.id));
		Promise.await(Settings.updateValueById('Federation_Matrix_hs_token', existingConfiguration.homeserverToken));
		Promise.await(Settings.updateValueById('Federation_Matrix_as_token', existingConfiguration.applicationServiceToken));
		Promise.await(Settings.updateValueById('Federation_Matrix_homeserver_url', existingConfiguration.rocketchat?.homeServerUrl));
		Promise.await(Settings.updateValueById('Federation_Matrix_homeserver_domain', existingConfiguration.rocketchat?.domainName));
		Promise.await(Settings.updateValueById('Federation_Matrix_bridge_url', existingConfiguration.bridgeUrl));
		Promise.await(Settings.updateValueById('Federation_Matrix_bridge_localpart', existingConfiguration.botName));
		Promise.await(Settings.update({ _id: 'Federation_Matrix_registration_file' }, { $set: { hidden: Boolean(existingConfiguration) } }));
	}
}


import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';

import { FederationRoomServiceListener } from '../application/RoomServiceListener';
import { FederationRoomServiceSender } from '../application/sender/RoomServiceSender';
import { MatrixBridge } from './matrix/Bridge';
import { MatrixEventsHandler } from './matrix/handlers';
import type { MatrixBaseEventHandler } from './matrix/handlers/BaseEvent';
import {
	MatrixRoomCreatedHandler,
	MatrixRoomEventRedactedHandler,
	MatrixRoomJoinRulesChangedHandler,
	MatrixRoomMembershipChangedHandler,
	MatrixRoomMessageSentHandler,
	MatrixRoomNameChangedHandler,
	MatrixRoomTopicChangedHandler,
} from './matrix/handlers/Room';
import { InMemoryQueue } from './queue/InMemoryQueue';
import { RocketChatMessageAdapter } from './rocket-chat/adapters/Message';
import { RocketChatRoomAdapter } from './rocket-chat/adapters/Room';
import { RocketChatSettingsAdapter } from './rocket-chat/adapters/Settings';
import { RocketChatUserAdapter } from './rocket-chat/adapters/User';
import type { IFederationBridge } from '../domain/IFederationBridge';
import { FederationHooks } from './rocket-chat/hooks';
import { FederationRoomSenderConverter } from './rocket-chat/converters/RoomSender';
import { FederationRoomInternalHooksValidator } from '../application/sender/RoomInternalHooksValidator';
import { RocketChatFileAdapter } from './rocket-chat/adapters/File';
import { FederationMessageServiceListener } from '../application/MessageServiceListener';
import { MatrixMessageReactedHandler } from './matrix/handlers/Message';
import { FederationMessageServiceSender } from '../application/sender/MessageServiceSender';
import { FederationUserServiceListener } from '../application/UserServiceListener';
import { MatrixUserTypingStatusChangedHandler } from './matrix/handlers/User';
import { FederationUserServiceSender } from '../application/sender/UserServiceSender';
import { RocketChatNotificationAdapter } from './rocket-chat/adapters/Notification';

export class FederationFactory {
	public static buildRocketSettingsAdapter(): RocketChatSettingsAdapter {
		return new RocketChatSettingsAdapter();
	}

	public static buildRocketRoomAdapter(): RocketChatRoomAdapter {
		return new RocketChatRoomAdapter();
	}

	public static buildRocketUserAdapter(): RocketChatUserAdapter {
		return new RocketChatUserAdapter();
	}

	public static buildRocketMessageAdapter(): RocketChatMessageAdapter {
		return new RocketChatMessageAdapter();
	}

	public static buildRocketFileAdapter(): RocketChatFileAdapter {
		return new RocketChatFileAdapter();
	}

	public static buildRocketNotificationAdapter(): RocketChatNotificationAdapter {
		return new RocketChatNotificationAdapter();
	}

	public static buildFederationQueue(): InMemoryQueue {
		return new InMemoryQueue();
	}

	public static buildRoomServiceReceiver(
		rocketRoomAdapter: RocketChatRoomAdapter,
		rocketUserAdapter: RocketChatUserAdapter,
		rocketMessageAdapter: RocketChatMessageAdapter,
		rocketFileAdapter: RocketChatFileAdapter,
		rocketSettingsAdapter: RocketChatSettingsAdapter,
		rocketNotificationAdapter: RocketChatNotificationAdapter,
		bridge: IFederationBridge,
	): FederationRoomServiceListener {
		return new FederationRoomServiceListener(
			rocketRoomAdapter,
			rocketUserAdapter,
			rocketMessageAdapter,
			rocketFileAdapter,
			rocketSettingsAdapter,
			rocketNotificationAdapter,
			bridge,
		);
	}

	public static buildRoomServiceSender(
		rocketRoomAdapter: RocketChatRoomAdapter,
		rocketUserAdapter: RocketChatUserAdapter,
		rocketFileAdapter: RocketChatFileAdapter,
		rocketMessageAdapter: RocketChatMessageAdapter,
		rocketSettingsAdapter: RocketChatSettingsAdapter,
		rocketNotificationAdapter: RocketChatNotificationAdapter,
		bridge: IFederationBridge,
	): FederationRoomServiceSender {
		return new FederationRoomServiceSender(
			rocketRoomAdapter,
			rocketUserAdapter,
			rocketFileAdapter,
			rocketMessageAdapter,
			rocketSettingsAdapter,
			rocketNotificationAdapter,
			bridge,
		);
	}

	public static buildUserServiceSender(
		rocketRoomAdapter: RocketChatRoomAdapter,
		rocketUserAdapter: RocketChatUserAdapter,
		rocketFileAdapter: RocketChatFileAdapter,
		rocketSettingsAdapter: RocketChatSettingsAdapter,
		bridge: IFederationBridge,
	): FederationUserServiceSender {
		return new FederationUserServiceSender(rocketRoomAdapter, rocketUserAdapter, rocketFileAdapter, rocketSettingsAdapter, bridge);
	}

	public static buildMessageServiceSender(
		rocketRoomAdapter: RocketChatRoomAdapter,
		rocketUserAdapter: RocketChatUserAdapter,
		rocketSettingsAdapter: RocketChatSettingsAdapter,
		rocketMessageAdapter: RocketChatMessageAdapter,
		bridge: IFederationBridge,
	): FederationMessageServiceSender {
		return new FederationMessageServiceSender(rocketRoomAdapter, rocketUserAdapter, rocketSettingsAdapter, rocketMessageAdapter, bridge);
	}

	public static buildMessageServiceReceiver(
		rocketRoomAdapter: RocketChatRoomAdapter,
		rocketUserAdapter: RocketChatUserAdapter,
		rocketMessageAdapter: RocketChatMessageAdapter,
		rocketFileAdapter: RocketChatFileAdapter,
		rocketSettingsAdapter: RocketChatSettingsAdapter,
		bridge: IFederationBridge,
	): FederationMessageServiceListener {
		return new FederationMessageServiceListener(
			rocketRoomAdapter,
			rocketUserAdapter,
			rocketMessageAdapter,
			rocketFileAdapter,
			rocketSettingsAdapter,
			bridge,
		);
	}

	public static buildUserServiceReceiver(
		rocketRoomAdapter: RocketChatRoomAdapter,
		rocketUserAdapter: RocketChatUserAdapter,
		rocketFileAdapter: RocketChatFileAdapter,
		rocketNotificationAdapter: RocketChatNotificationAdapter,
		rocketSettingsAdapter: RocketChatSettingsAdapter,
		bridge: IFederationBridge,
	): FederationUserServiceListener {
		return new FederationUserServiceListener(
			rocketRoomAdapter,
			rocketUserAdapter,
			rocketFileAdapter,
			rocketNotificationAdapter,
			rocketSettingsAdapter,
			bridge,
		);
	}

	public static buildRoomInternalHooksValidator(
		rocketRoomAdapter: RocketChatRoomAdapter,
		rocketUserAdapter: RocketChatUserAdapter,
		rocketFileAdapter: RocketChatFileAdapter,
		rocketSettingsAdapter: RocketChatSettingsAdapter,
		bridge: IFederationBridge,
	): FederationRoomInternalHooksValidator {
		return new FederationRoomInternalHooksValidator(rocketRoomAdapter, rocketUserAdapter, rocketFileAdapter, rocketSettingsAdapter, bridge);
	}

	public static buildFederationBridge(rocketSettingsAdapter: RocketChatSettingsAdapter, queue: InMemoryQueue): IFederationBridge {
		return new MatrixBridge(
			rocketSettingsAdapter.getApplicationServiceId(),
			rocketSettingsAdapter.getHomeServerUrl(),
			rocketSettingsAdapter.getHomeServerDomain(),
			rocketSettingsAdapter.getBridgeUrl(),
			rocketSettingsAdapter.getBridgePort(),
			rocketSettingsAdapter.generateRegistrationFileObject(),
			queue.addToQueue.bind(queue),
		);
	}

	public static buildFederationEventHandler(
		roomServiceReceive: FederationRoomServiceListener,
		messageServiceReceiver: FederationMessageServiceListener,
		userServiceReceiver: FederationUserServiceListener,
		rocketSettingsAdapter: RocketChatSettingsAdapter,
	): MatrixEventsHandler {
		return new MatrixEventsHandler(
			FederationFactory.getEventHandlers(roomServiceReceive, messageServiceReceiver, userServiceReceiver, rocketSettingsAdapter),
		);
	}

	public static getEventHandlers(
		roomServiceReceiver: FederationRoomServiceListener,
		messageServiceReceiver: FederationMessageServiceListener,
		userServiceReceiver: FederationUserServiceListener,
		rocketSettingsAdapter: RocketChatSettingsAdapter,
	): MatrixBaseEventHandler[] {
		return [
			new MatrixRoomCreatedHandler(roomServiceReceiver),
			new MatrixRoomMembershipChangedHandler(roomServiceReceiver, rocketSettingsAdapter),
			new MatrixRoomMessageSentHandler(roomServiceReceiver, rocketSettingsAdapter),
			new MatrixRoomJoinRulesChangedHandler(roomServiceReceiver),
			new MatrixRoomNameChangedHandler(roomServiceReceiver),
			new MatrixRoomTopicChangedHandler(roomServiceReceiver),
			new MatrixRoomEventRedactedHandler(roomServiceReceiver),
			new MatrixMessageReactedHandler(messageServiceReceiver),
			new MatrixUserTypingStatusChangedHandler(userServiceReceiver),
		];
	}

	public static setupListeners(
		roomServiceSender: FederationRoomServiceSender,
		roomInternalHooksValidator: FederationRoomInternalHooksValidator,
		messageServiceSender: FederationMessageServiceSender,
	): void {
		FederationFactory.setupActions(roomServiceSender, messageServiceSender);
		FederationFactory.setupValidators(roomInternalHooksValidator);
	}

	public static setupActions(roomServiceSender: FederationRoomServiceSender, messageServiceSender: FederationMessageServiceSender): void {
		FederationHooks.afterUserLeaveRoom((user: IUser, room: IRoom) =>
			roomServiceSender.afterUserLeaveRoom(FederationRoomSenderConverter.toAfterUserLeaveRoom(user._id, room._id)),
		);
		FederationHooks.onUserRemovedFromRoom((user: IUser, room: IRoom, userWhoRemoved: IUser) =>
			roomServiceSender.onUserRemovedFromRoom(
				FederationRoomSenderConverter.toOnUserRemovedFromRoom(user._id, room._id, userWhoRemoved._id),
			),
		);
		FederationHooks.afterMessageReacted((message: IMessage, user: IUser, reaction: string) =>
			messageServiceSender.sendExternalMessageReaction(message, user, reaction),
		);
		FederationHooks.afterMessageunReacted((message: IMessage, user: IUser, reaction: string) =>
			messageServiceSender.sendExternalMessageUnReaction(message, user, reaction),
		);
		FederationHooks.afterMessageDeleted((message: IMessage, roomId: string) => roomServiceSender.afterMessageDeleted(message, roomId));
		FederationHooks.afterMessageUpdated((message: IMessage, roomId: string, userId: string) =>
			roomServiceSender.afterMessageUpdated(message, roomId, userId),
		);
		FederationHooks.afterMessageSent((message: IMessage, roomId: string, userId: string) =>
			roomServiceSender.sendExternalMessage(FederationRoomSenderConverter.toSendExternalMessageDto(userId, roomId, message)),
		);
	}

	public static setupValidators(roomInternalHooksValidator: FederationRoomInternalHooksValidator): void {
		FederationHooks.canAddFederatedUserToNonFederatedRoom((user: IUser | string, room: IRoom) =>
			roomInternalHooksValidator.canAddFederatedUserToNonFederatedRoom(user, room),
		);
		FederationHooks.canAddFederatedUserToFederatedRoom((user: IUser | string, inviter: IUser, room: IRoom) =>
			roomInternalHooksValidator.canAddFederatedUserToFederatedRoom(user, inviter, room),
		);
		FederationHooks.canCreateDirectMessageFromUI((members: (IUser | string)[]) =>
			roomInternalHooksValidator.canCreateDirectMessageFromUI(members),
		);
	}

	public static removeCEListeners(): void {
		FederationHooks.removeCEValidation();
	}
}


<GenericModal icon={null} title={t('Announcement')} confirmText={t('Close')} onConfirm={closeModal} onClose={closeModal}>

keep keep keep keep replace keep keep keep

<MarkdownText content={announcement} parseEmoji />


import { settings } from '../../../settings/server';

export const isFederationEnabled = () => settings.get('FEDERATION_Enabled');


import { addMigration } from '../../lib/migrations';
import { upsertPermissions } from '../../../app/authorization/server/functions/upsertPermissions';

addMigration({
	version: 256,
	up() {
		upsertPermissions();
	},
});


import { isLivechatCustomFieldsProps, isPOSTLivechatCustomFieldParams, isPOSTLivechatCustomFieldsParams } from '@rocket.chat/rest-typings';

import { API } from '../../../../api/server';
import { findGuest } from '../lib/livechat';
import { Livechat } from '../../lib/Livechat';
import { findLivechatCustomFields, findCustomFieldById } from '../lib/customFields';

API.v1.addRoute(
	'livechat/custom.field',
	{ validateParams: isPOSTLivechatCustomFieldParams },
	{
		async post() {
			const { token, key, value, overwrite } = this.bodyParams;

			const guest = await findGuest(token);
			if (!guest) {
				throw new Error('invalid-token');
			}

			if (!(await Livechat.setCustomFields({ token, key, value, overwrite }))) {
				return API.v1.failure();
			}

			return API.v1.success({ field: { key, value, overwrite } });
		},
	},
);

API.v1.addRoute(
	'livechat/custom.fields',
	{ validateParams: isPOSTLivechatCustomFieldsParams },
	{
		async post() {
			const { token } = this.bodyParams;
			const guest = await findGuest(token);
			if (!guest) {
				throw new Error('invalid-token');
			}

			const fields = await Promise.all(
				this.bodyParams.customFields.map(async (customField) => {
					const data = Object.assign({ token }, customField);
					if (!(await Livechat.setCustomFields(data))) {
						throw new Error('error-setting-custom-field');
					}

					return { Key: customField.key, value: customField.value, overwrite: customField.overwrite };
				}),
			);

			return API.v1.success({ fields });
		},
	},
);

API.v1.addRoute(
	'livechat/custom-fields',
	{ authRequired: true, permissionsRequired: ['view-l-room'], validateParams: isLivechatCustomFieldsProps },
	{
		async get() {
			const { offset, count } = this.getPaginationItems();
			const { sort } = this.parseJsonQuery();
			const { text } = this.queryParams;

			const customFields = await findLivechatCustomFields({
				text,
				pagination: {
					offset,
					count,
					sort,
				},
			});

			return API.v1.success(customFields);
		},
	},
);

API.v1.addRoute(
	'livechat/custom-fields/:_id',
	{ authRequired: true, permissionsRequired: ['view-l-room'] },
	{
		async get() {
			const { customField } = await findCustomFieldById({ customFieldId: this.urlParams._id });

			return API.v1.success({
				customField,
			});
		},
	},
);


<AnnouncementComponent onClickOpen={(e: MouseEvent<HTMLAnchorElement>): void => handleClick(e)}>

<MarkdownText variant='inlineWithoutBreaks' content={announcement} withTruncatedText />

keep keep keep keep replace keep keep keep keep

<MarkdownText variant='inlineWithoutBreaks' content={announcement} withTruncatedText parseEmoji />


import fs from 'fs';

function hasDockerEnv() {
	try {
		fs.statSync('/.dockerenv');
		return true;
	} catch (err) {
		return false;
	}
}

function hasDockerCGroup() {
	try {
		return fs.readFileSync('/proc/self/cgroup', 'utf8').indexOf('docker') !== -1;
	} catch (err) {
		return false;
	}
}

function check() {
	return hasDockerEnv() || hasDockerCGroup();
}

let _isDocker;
export const isDocker = function () {
	if (_isDocker === undefined) {
		_isDocker = check();
	}

	return _isDocker;
};


	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />

test

Talk to us.

changing page title

	page 0

	page 1

	page 2

	page 3

without changing page title

	page 4

	page 5

	page 6

	page 7


import fastq from 'fastq';

import type { AbstractMatrixEvent } from '../matrix/definitions/AbstractMatrixEvent';

export class InMemoryQueue {
	private instance: any;

	public setHandler(handler: (event: AbstractMatrixEvent) => Promise<void>, concurrency: number): void {
		this.instance = fastq.promise(handler, concurrency);
	}

	public addToQueue(task: Record<string, any>): void {
		if (!this.instance) {
			throw new Error('You need to set the handler first');
		}
		this.instance.push(task).catch(console.error);
	}
}


const status = $('#enabled').prop('checked') ? 'manually_enabled' : 'manually_disabled';

RocketChat.API.post(`rocketlets/${ t.id.get() }/status`, { status }).then((result) => {

if (info.status.indexOf('disabled') !== -1) {

Package.describe({
	name: 'rocketchat:rocketlets',
	version: '1.0.0'
});

Package.onUse(function(api) {
	api.use([
		'ecmascript',
		'rocketchat:lib',
		'rocketchat:api',
		'templating'
	]);

	api.use(['reactive-var', 'kadira:flow-router', 'underscore'], 'client');

	api.addFiles('lib/Rocketlets.js', ['client', 'server']);

	// Storage
	api.addFiles([
		'server/storage/rl-model.js',
		'server/storage/rl-persistence-model.js',
		'server/storage/storage.js',
		'server/storage/index.js'
	], 'server');

	// Bridges
	api.addFiles([
		'server/bridges/activation.js',
		'server/bridges/bridges.js',
		'server/bridges/commands.js',
		'server/bridges/environmental.js',
		'server/bridges/messages.js',
		'server/bridges/persistence.js',
		'server/bridges/rooms.js',
		'server/bridges/settings.js',
		'server/bridges/users.js',
		'server/bridges/index.js'
	], 'server');

	// Communication pieces
	api.addFiles([
		'server/communication/rest.js',
		'server/communication/websockets.js',
		'server/communication/index.js'
	], 'server');

	// RocketChat <-> Rocketlet Data Converters
	api.addFiles([
		'server/converters/messages.js',
		'server/converters/rooms.js',
		'server/converters/settings.js',
		'server/converters/users.js',
		'server/converters/index.js'
	], 'server');

	// Server Orchestrator
	api.addFiles('server/orchestrator.js', 'server');

	// Client communication pieces
	api.addFiles([
		'client/communication/websockets.js',
		'client/communication/index.js'
	], 'client');

	// Client Admin Management
	api.addFiles([
		'client/admin/rocketlets.html',
		'client/admin/rocketlets.js',
		'client/admin/rocketletManage.html',
		'client/admin/rocketletManage.js',
		'client/admin/rocketletInstall.html',
		'client/admin/rocketletInstall.js'
	], 'client');

	api.addFiles('assets/stylesheets/rocketlets.css', 'client');

	// Client orchestrator
	api.addFiles('client/orchestrator.js', 'client');

	// Add what this package actually does export
	api.export('Rocketlets');
});

Npm.depends({
	'busboy': '0.2.13',
	'temporary-rocketlets-server': '0.2.2',
	'temporary-rocketlets-ts-definition': '0.6.33'
});

// TODO: Save whether a rocketlet is enabled or not, that way when the server restarts it doesn't enable one that isn't enabled


/* globals Department, Livechat */

Template.switchDepartment.helpers({
	departments() {
		return Department.find({
			showOnRegistration: true,
			_id: {
				$ne: Livechat.department
			}
		});
	},
	error() {
		return Template.instance().error.get();
	},
	showError() {
		return Template.instance().error.get() ? 'show' : '';
	}
});

Template.switchDepartment.onCreated(function() {
	this.error = new ReactiveVar();
});

Template.switchDepartment.events({
	'submit form'(e, instance) {
		e.stopPropagation();
		e.preventDefault();

		const departmentId = instance.$('.switch-department-select').val();
		if (!departmentId) {
			instance.error.set(t('Please_choose_a_department'));
			return;
		}

		instance.error.set();
		swal({
			text: t('Are_you_sure_do_you_want_end_this_chat_and_switch_department'),
			title: '',
			type: 'warning',
			showCancelButton: true,
			confirmButtonColor: '#DD6B55',
			confirmButtonText: t('Yes'),
			cancelButtonText: t('No'),
			closeOnConfirm: true,
			html: false
		}, () => {
			Meteor.call('livechat:closeByVisitor', visitor.getRoom(), (error) => {
				if (error) {
					return console.log('Error ->', error);
				}

				const guestData = {
					token: visitor.getToken(),
					department: departmentId
				};
				Meteor.call('livechat:setDepartmentForVisitor', guestData, (error) => {
					if (error) {
						return console.log('Error ->', error);
					}
					Livechat.department = departmentId;
					Livechat.showSwitchDepartmentForm = false;
					swal({
						title: t('Department_switched'),
						type: 'success',
						timer: 2000
					});
				});
			});
		});
	},

	'click #btnCancel'() {
		Livechat.showSwitchDepartmentForm = false;
	}
});


import Grid from 'gridfs-stream';
import stream from 'stream';
import fs from 'fs';
import path from 'path';
import mkdirp from 'mkdirp';
import gm from 'gm';
import {exec} from 'child_process';

// Fix problem with usernames being converted to object id
Grid.prototype.tryParseObjectId = function() {
	return false;
};
//TODO: REMOVE RocketChatFile from globals
RocketChatFile = {
	gm,
	enabled: undefined,
	enable() {
		RocketChatFile.enabled = true;
		return RocketChat.settings.updateOptionsById('Accounts_AvatarResize', {
			alert: undefined
		});
	},
	disable() {
		RocketChatFile.enabled = false;
		return RocketChat.settings.updateOptionsById('Accounts_AvatarResize', {
			alert: 'The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server'
		});
	}
};

const detectGM = function() {
	return exec('gm version', Meteor.bindEnvironment(function(error, stdout) {
		if ((error == null) && stdout.indexOf('GraphicsMagick') > -1) {
			RocketChatFile.enable();
			RocketChat.Info.GraphicsMagick = {
				enabled: true,
				version: stdout
			};
		} else {
			RocketChat.Info.GraphicsMagick = {
				enabled: false
			};
		}
		return exec('convert -version', Meteor.bindEnvironment(function(error, stdout) {
			if ((error == null) && stdout.indexOf('ImageMagick') > -1) {
				if (RocketChatFile.enabled !== true) {
					// Enable GM to work with ImageMagick if no GraphicsMagick
					RocketChatFile.gm = RocketChatFile.gm.subClass({
						imageMagick: true
					});
					RocketChatFile.enable();
				}
				return RocketChat.Info.ImageMagick = {
					enabled: true,
					version: stdout
				};
			} else {
				if (RocketChatFile.enabled !== true) {
					RocketChatFile.disable();
				}
				return RocketChat.Info.ImageMagick = {
					enabled: false
				};
			}
		}));
	}));
};

detectGM();

Meteor.methods({
	'detectGM'() {
		detectGM();
	}
});

RocketChatFile.bufferToStream = function(buffer) {
	const bufferStream = new stream.PassThrough();
	bufferStream.end(buffer);
	return bufferStream;
};

RocketChatFile.dataURIParse = function(dataURI) {
	const imageData = dataURI.split(';base64,');
	return {
		image: imageData[1],
		contentType: imageData[0].replace('data:', '')
	};
};

RocketChatFile.addPassThrough = function(st, fn) {
	const pass = new stream.PassThrough();
	fn(pass, st);
	return pass;
};

RocketChatFile.GridFS = class {
	constructor(config = {}) {
		const {name = 'file', transformWrite} = config;

		this.name = name;
		this.transformWrite = transformWrite;
		const mongo = Package.mongo.MongoInternals.NpmModule;
		const db = Package.mongo.MongoInternals.defaultRemoteCollectionDriver().mongo.db;
		this.store = new Grid(db, mongo);
		this.findOneSync = Meteor.wrapAsync(this.store.collection(this.name).findOne.bind(this.store.collection(this.name)));
		this.removeSync = Meteor.wrapAsync(this.store.remove.bind(this.store));
		this.countSync = Meteor.wrapAsync(this.store._col.count.bind(this.store._col));
		this.getFileSync = Meteor.wrapAsync(this.getFile.bind(this));
	}

	findOne(fileName) {
		return this.findOneSync({
			_id: fileName
		});
	}

	remove(fileName) {
		return this.removeSync({
			_id: fileName,
			root: this.name
		});
	}

	createWriteStream(fileName, contentType) {
		const self = this;
		let ws = this.store.createWriteStream({
			_id: fileName,
			filename: fileName,
			mode: 'w',
			root: this.name,
			content_type: contentType
		});
		if (self.transformWrite != null) {
			ws = RocketChatFile.addPassThrough(ws, function(rs, ws) {
				const file = {
					name: self.name,
					fileName,
					contentType
				};
				return self.transformWrite(file, rs, ws);
			});
		}
		ws.on('close', function() {
			return ws.emit('end');
		});
		return ws;
	}

	createReadStream(fileName) {
		return this.store.createReadStream({
			_id: fileName,
			root: this.name
		});
	}

	getFileWithReadStream(fileName) {
		const file = this.findOne(fileName);
		if (file == null) {
			return null;
		}
		const rs = this.createReadStream(fileName);
		return {
			readStream: rs,
			contentType: file.contentType,
			length: file.length,
			uploadDate: file.uploadDate
		};
	}

	getFile(fileName, cb) {
		const file = this.getFileWithReadStream(fileName);
		if (!file) {
			return cb();
		}
		const data = [];
		file.readStream.on('data', Meteor.bindEnvironment(function(chunk) {
			return data.push(chunk);
		}));
		return file.readStream.on('end', Meteor.bindEnvironment(function() {
			return cb(null, {
				buffer: Buffer.concat(data),
				contentType: file.contentType,
				length: file.length,
				uploadDate: file.uploadDate
			});
		}));
	}

	deleteFile(fileName) {
		const file = this.findOne(fileName);
		if (file == null) {
			return undefined;
		}
		return this.remove(fileName);
	}

};

RocketChatFile.FileSystem = class {
	constructor(config = {}) {
		let {absolutePath = '~/uploads'} = config;
		const {transformWrite} = config;

		this.transformWrite = transformWrite;
		if (absolutePath.split(path.sep)[0] === '~') {
			const homepath = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
			if (homepath != null) {
				absolutePath = absolutePath.replace('~', homepath);
			} else {
				throw new Error('Unable to resolve "~" in path');
			}
		}
		this.absolutePath = path.resolve(absolutePath);
		mkdirp.sync(this.absolutePath);
		this.statSync = Meteor.wrapAsync(fs.stat.bind(fs));
		this.unlinkSync = Meteor.wrapAsync(fs.unlink.bind(fs));
		this.getFileSync = Meteor.wrapAsync(this.getFile.bind(this));
	}

	createWriteStream(fileName, contentType) {
		const self = this;
		let ws = fs.createWriteStream(path.join(this.absolutePath, fileName));
		if (self.transformWrite != null) {
			ws = RocketChatFile.addPassThrough(ws, function(rs, ws) {
				const file = {
					fileName,
					contentType
				};
				return self.transformWrite(file, rs, ws);
			});
		}
		ws.on('close', function() {
			return ws.emit('end');
		});
		return ws;
	}

	createReadStream(fileName) {
		return fs.createReadStream(path.join(this.absolutePath, fileName));
	}

	stat(fileName) {
		return this.statSync(path.join(this.absolutePath, fileName));
	}

	remove(fileName) {
		return this.unlinkSync(path.join(this.absolutePath, fileName));
	}

	getFileWithReadStream(fileName) {
		try {
			const stat = this.stat(fileName);
			const rs = this.createReadStream(fileName);
			return {
				readStream: rs,
				// contentType: file.contentType
				length: stat.size
			};
		} catch (error1) {
			return null;
		}
	}

	getFile(fileName, cb) {
		const file = this.getFileWithReadStream(fileName);
		if (!file) {
			return cb();
		}
		const data = [];
		file.readStream.on('data', Meteor.bindEnvironment(function(chunk) {
			return data.push(chunk);
		}));
		return file.readStream.on('end', Meteor.bindEnvironment(function() {
			return {
				buffer: Buffer.concat(data)({
					contentType: file.contentType,
					length: file.length,
					uploadDate: file.uploadDate
				})
			};
		}));
	}

	deleteFile(fileName) {
		try {
			return this.remove(fileName);
		} catch (error1) {
			return null;
		}
	}
};


<template name="selectDropdown">

Invite people to channel

					{{> icon block="rc-input__icon-svg" icon="at"}}

				{{ selectedUsers }}
				<input type="text" class="rc-input__element" placeholder="Type user name" name="users">

		{{#if open}}

			{{ > popupList .}}

		{{/if}}

</template>


// TODO: Save whether a rocketlet is enabled or not, that way when the server restarts it doesn't enable one that isn't enabled

keep keep keep keep keep replace keep keep keep keep

import { SlashCommandContext } from 'temporary-rocketlets-ts-definition/slashcommands';

export class RocketletCommandsBridge {
	constructor(orch) {
		this.orch = orch;
		this.disabledCommands = new Map();
	}

	doesCommandExist(command, rocketletId) {
		console.log(`The Rocketlet ${ rocketletId } is checking if "${ command }" command exists.`);

		if (typeof command !== 'string' || command.length === 0) {
			return false;
		}

		const cmd = command.toLowerCase();
		return typeof RocketChat.slashCommands.commands[cmd] === 'object' || this.disabledCommands.has(cmd);
	}

	enableCommand(command, rocketletId) {
		console.log(`The Rocketlet ${ rocketletId } is attempting to enable the command: "${ command }"`);

		if (typeof command !== 'string' || command.trim().length === 0) {
			throw new Error('Invalid command parameter provided, must be a string.');
		}

		const cmd = command.toLowerCase();
		if (!this.disabledCommands.has(cmd)) {
			throw new Error(`The command is not currently disabled: "${ cmd }"`);
		}

		RocketChat.slashCommands.commands[cmd] = this.disabledCommands.get(cmd);
		this.disabledCommands.delete(cmd);

		this.orch.getNotifier().commandUpdated(cmd);
	}

	disableCommand(command, rocketletId) {
		console.log(`The Rocketlet ${ rocketletId } is attempting to disable the command: "${ command }"`);

		if (typeof command !== 'string' || command.trim().length === 0) {
			throw new Error('Invalid command parameter provided, must be a string.');
		}

		const cmd = command.toLowerCase();
		if (typeof RocketChat.slashCommands.commands[cmd] === 'undefined') {
			throw new Error(`Command does not exist in the system currently (or it is disabled): "${ cmd }"`);
		}

		this.disabledCommands.set(cmd, RocketChat.slashCommands.commands[cmd]);
		delete RocketChat.slashCommands.commands[cmd];

		this.orch.getNotifier().commandDisabled(cmd);
	}

	// command: { command, paramsExample, i18nDescription, executor: function }
	modifyCommand(command, rocketletId) {
		console.log(`The Rocketlet ${ rocketletId } is attempting to modify the command: "${ command }"`);

		this._verifyCommand(command);

		const cmd = command.toLowerCase();
		if (typeof RocketChat.slashCommands.commands[cmd] === 'undefined') {
			throw new Error(`Command does not exist in the system currently (or it is disabled): "${ cmd }"`);
		}

		const item = RocketChat.slashCommands.commands[cmd];
		item.params = command.paramsExample ? command.paramsExample : item.params;
		item.description = command.i18nDescription ? command.i18nDescription : item.params;
		item.callback = this._rocketletCommandExecutor.bind(this);

		RocketChat.slashCommands.commands[cmd] = item;
		this.orch.getNotifier().commandUpdated(cmd);
	}

	registerCommand(command, rocketletId) {
		console.log(`The Rocketlet ${ rocketletId } is registering the command: "${ command.command }"`);

		this._verifyCommand(command);

		const item = {
			command: command.command.toLowerCase(),
			params: command.paramsExample,
			description: command.i18nDescription,
			callback: this._rocketletCommandExecutor.bind(this)
		};

		RocketChat.slashCommands.commands[command.command.toLowerCase()] = item;
		this.orch.getNotifier().commandAdded(command.command.toLowerCase());
	}

	unregisterCommand(command, rocketletId) {
		console.log(`The Rocketlet ${ rocketletId } is unregistering the command: "${ command }"`);

		if (typeof command !== 'string' || command.trim().length === 0) {
			throw new Error('Invalid command parameter provided, must be a string.');
		}

		const cmd = command.toLowerCase();
		if (typeof RocketChat.slashCommands.commands[cmd] === 'undefined' || !this.disabledCommands.has(cmd)) {
			// There is no need to fail if the command doesn't exist already
			return;
		}

		this.disabledCommands.delete(cmd);
		delete RocketChat.slashCommands.commands[cmd];

		this.orch.getNotifier().commandRemoved(cmd);
	}

	_verifyCommand(command) {
		if (typeof command !== 'object') {
			throw new Error('Invalid Slash Command parameter provided, it must be a valid ISlashCommand object.');
		}

		if (typeof command.command !== 'string') {
			throw new Error('Invalid Slash Command parameter provided, it must be a valid ISlashCommand object.');
		}

		if (command.paramsExample && typeof command.paramsExample !== 'string') {
			throw new Error('Invalid Slash Command parameter provided, it must be a valid ISlashCommand object.');
		}

		if (command.i18nDescription && typeof command.i18nDescription !== 'string') {
			throw new Error('Invalid Slash Command parameter provided, it must be a valid ISlashCommand object.');
		}

		if (typeof command.executor !== 'function') {
			throw new Error('Invalid Slash Command parameter provided, it must be a valid ISlashCommand object.');
		}
	}

	_rocketletCommandExecutor(command, parameters, message) {
		const user = this.orch.getConverters().get('users').convertById(Meteor.userId());
		const room = this.orch.getConverters().get('rooms').convertById(message.rid);
		const params = parameters.length === 0 || parameters === ' ' ? [] : parameters.split(' ');

		const context = new SlashCommandContext(Object.freeze(user), Object.freeze(room), Object.freeze(params));
		this.orch.getManager().getCommandManager().executeCommand(command, context);
	}
}


<template name="roomNotFound">

			{{> burger}}

				{{_ "Room_not_found"}}

				{{#with data}}
					{{#if $eq type 'c'}}
						{{{_ 'No_channel_with_name_%s_was_found' name}}}
					{{/if}}
					{{#if $eq type 'p'}}
						{{{_ 'No_group_with_name_%s_was_found' name}}}
					{{/if}}
					{{#if $eq type 'd'}}
						{{#if sameUser }}
							{{{_ 'Cannot_open_conversation_with_yourself' }}}
						{{else}}
							{{{_ 'No_user_with_username_%s_was_found' name}}}
						{{/if}}
					{{/if}}
				{{/with}}

</template>


Package.describe({
	name: 'rocketchat:slashcommands-help',
	version: '0.0.1',
	summary: 'Command handler for the /help command',
	git: ''
});

Package.onUse(function(api) {

	api.use([
		'ecmascript',
		'check',
		'rocketchat:lib'
	]);

	api.use('templating', 'client');

	api.addFiles('server.js', 'server');
});